NWD, liczby pierwsze i inne klasyczne algorytmy w Javie
Zaktualizowano 22 października 2026
Klasyczne algorytmy, takie jak NWD według Euklidesa, liczby pierwsze, systemy liczbowe, cyfry kontrolne i wydawanie reszty, są starsze niż jakikolwiek komputer, a mimo to pojawiają się prawie na każdym egzaminie. W Javie są krótkie, łatwo prześledzić je na kartce i pokazują, ile czasu działania oszczędza dobry pomysł.
NWD i NWW algorytmem Euklidesa
Największy wspólny dzielnik (NWD) da się znaleźć bez zgadywania. Euklides wykorzystuje to, że NWD liczb a i b jest taki sam jak NWD liczb b i a % b. Zastępujesz więc (a, b) przez (b, a % b) tak długo, aż b będzie równe 0. Wtedy NWD jest w a.
(1071, 462) 1071 % 462 = 147(462, 147) 462 % 147 = 21(147, 21) 147 % 21 = 0(21, 0) b wynosi 0, NWD = 21
Jeśli a jest mniejsze od b, pierwszy krok tylko zamienia je miejscami. Liczby maleją szybko: liczba kroków rośnie tylko z liczbą cyfr mniejszej liczby, czyli O(log n).
Z tego wynika najmniejsza wspólna wielokrotność: NWW(a, b) = a / NWD(a, b) · b. Najpierw dziel, bo inaczej a · b szybko się przepełni.
public class Main { public static int gcd(int a, int b) { while (b != 0) { int r = a % b; System.out.println("a = " + a + ", b = " + b + ", r = " + r); a = b; b = r; } return a; }
public static long lcm(int a, int b) { return (long) (a / gcd(a, b)) * b; }
public static void main(String[] args) { System.out.println("gcd: " + gcd(1071, 462)); System.out.println("lcm: " + lcm(4, 6)); }}Liczby pierwsze i sito
Liczba pierwsza jest większa od 1 i dzieli się tylko przez 1 i przez samą siebie. Do sprawdzenia wystarczy próbować dzielników do √n: jeśli n = a · b, to jeden z dwóch czynników jest co najwyżej równy √n. Zamiast t <= Math.sqrt(n) pisze się zwykle t * t <= n. Test potrzebuje wtedy O(√n) kroków zamiast O(n).
Jeśli potrzebujesz wszystkich liczb pierwszych do n, szybsze jest sito Eratostenesa. Tworzysz tablicę typu boolean i dla każdej liczby pierwszej i wykreślasz jej wielokrotności, zaczynając od i · i. To, co na końcu nie jest wykreślone, jest liczbą pierwszą. Sito potrzebuje O(n log log n) kroków, w praktyce prawie liniowo, ale wymaga tablicy o rozmiarze n.
import java.util.ArrayList;import java.util.List;
public class Main { public static boolean isPrime(int n) { if (n < 2) { return false; } for (int t = 2; t * t <= n; t++) { if (n % t == 0) { return false; } } return true; }
public static List<Integer> sieve(int n) { boolean[] composite = new boolean[n + 1]; List<Integer> primes = new ArrayList<>(); for (int i = 2; i <= n; i++) { if (!composite[i]) { primes.add(i); for (long k = (long) i * i; k <= n; k += i) { composite[(int) k] = true; } } } return primes; }
public static void main(String[] args) { System.out.println(isPrime(91) + " " + isPrime(97)); System.out.println(sieve(50)); }}Systemy liczbowe
Liczba w systemie o podstawie b ma wagi pozycji 1, b, b², b³, …, licząc od prawej. 1101 dwójkowo to 1·8 + 1·4 + 0·2 + 1·1 = 13.
- Dziesiętny → podstawa b (metoda reszt): dziel wielokrotnie przez
bi zapisuj reszty. Pierwsza reszta to ostatnia cyfra, dlatego na końcu odwróć kolejność. - Podstawa b → dziesiętny (schemat Hornera): idź od lewej do prawej i licz
wartość = wartość · b + cyfra.
Obie metody potrzebują tylu kroków, ile cyfr ma liczba, czyli O(log n). System szesnastkowy używa cyfr 0–9 i A–F. Java ma do tego gotowe metody, takie jak Integer.toHexString i Integer.parseInt(text, podstawa). Na egzaminie musisz jednak umieć zrobić to sam.
public class Main { public static String toBinary(int n) { if (n == 0) { return "0"; } StringBuilder bits = new StringBuilder(); while (n > 0) { bits.append(n % 2); n = n / 2; } return bits.reverse().toString(); }
public static int fromBinary(String bits) { int value = 0; for (int i = 0; i < bits.length(); i++) { value = value * 2 + (bits.charAt(i) - '0'); } return value; }
public static void main(String[] args) { System.out.println(toBinary(13)); System.out.println(fromBinary("1101")); System.out.println(Integer.toHexString(255)); System.out.println(Integer.parseInt("FF", 16)); }}Szyfr Cezara i cyfry kontrolne
W szyfrze Cezara każda litera jest przesuwana o n miejsc. Na typie char można liczyć, bo każdy znak napisu jest wewnętrznie liczbą: c - 'a' to pozycja w alfabecie, a % 26 sprawia, że po z wracasz do a. Odszyfrowanie to przesunięcie o -n. + 26 zapobiega przy tym ujemnym resztom. Bezpieczne to nie jest: istnieje tylko 25 kluczy i można wypróbować wszystkie.
Cyfra kontrolna nie chroni przed atakami, tylko przed literówkami. W algorytmie Luhna (karty płatnicze) co druga cyfra od prawej jest podwajana, a od wyników większych niż 9 odejmuje się 9. Jeśli suma wszystkich cyfr jest podzielna przez 10, numer jest poprawny. Wykrywa to każdą pojedynczą błędną cyfrę i większość zamian dwóch sąsiednich cyfr. Obie metody przechodzą każdy znak raz: O(n).
public class Main { public static String caesar(String text, int shift) { StringBuilder result = new StringBuilder(); for (char c : text.toCharArray()) { if (c >= 'a' && c <= 'z') { result.append((char) ('a' + (c - 'a' + shift + 26) % 26)); } else if (c >= 'A' && c <= 'Z') { result.append((char) ('A' + (c - 'A' + shift + 26) % 26)); } else { result.append(c); } } return result.toString(); }
public static boolean luhn(String digits) { int sum = 0; for (int i = 0; i < digits.length(); i++) { int digit = digits.charAt(digits.length() - 1 - i) - '0'; if (i % 2 == 1) { digit = digit * 2; if (digit > 9) { digit = digit - 9; } } sum += digit; } return sum % 10 == 0; }
public static void main(String[] args) { String secret = caesar("Hello, World!", 3); System.out.println(secret); System.out.println(caesar(secret, -3)); System.out.println(luhn("79927398713")); System.out.println(luhn("79927398710")); }}Algorytm zachłanny i jego granice
Algorytm zachłanny (greedy) w każdym kroku wybiera to, co w danej chwili wygląda najlepiej, i nigdy nie cofa tej decyzji. Przy wydawaniu reszty oznacza to: zawsze największa moneta, która jeszcze się mieści. Przy monetach euro daje to najmniejszą liczbę monet.
Przy innych systemach monet to zawodzi. Przy monetach 4, 3 i 1 algorytm zachłanny wypłaca 6 jako 4 + 1 + 1, choć wystarczy 3 + 3. A przy 5 i 2 w ogóle nie znajduje rozwiązania dla 6, choć 2 + 2 + 2 działa. Algorytm zachłanny jest szybki, ale optymalny tylko dla odpowiednich problemów. Żeby znaleźć najlepsze rozwiązanie w każdym przypadku, potrzeba innych metod, na przykład backtrackingu.
public class Main { public static void change(int amount, int[] coins) { System.out.print(amount + " ="); for (int coin : coins) { while (amount >= coin) { System.out.print(" " + coin); amount -= coin; } } System.out.println(amount == 0 ? "" : " (rest " + amount + ")"); }
public static void main(String[] args) { int[] euro = {200, 100, 50, 20, 10, 5, 2, 1}; change(388, euro);
int[] odd = {4, 3, 1}; change(6, odd); }}
Złożoność w skrócie
- NWD algorytmem Euklidesa: O(log n)
- Test pierwszości do √n: O(√n)
- Sito Eratostenesa: O(n log log n)
- Zamiana na inną podstawę: O(log n)
- Szyfr Cezara, Luhn (tekst długości n): O(n)
- Zachłanne wydawanie reszty z k posortowanymi nominałami: O(k), z
/i%dla każdego nominału
Typowe błędy
Przepełnienie int
a * bz dwoma wartościamiintjest liczone jakoint, nawet jeśli wynik trafia do zmiennej typulong. Przy100000 * 99999wychodzi błędna, a nawet ujemna liczba. Najpierw podziel (a / nwd(a, b) * b) albo zrób z jednego czynnikalong. To samo dotyczyt * t <= n, gdynjest bliskoInteger.MAX_VALUE.Cyfry w złej kolejności
Metoda reszt podaje ostatnią cyfrę jako pierwszą. Jeśli po prostu doklejasz reszty jedna za drugą, dla 6 dostaniesz
011zamiast110. I odwrotnie: przy wczytywaniu pierwszy znak to najwyższa pozycja. Dlatego nigdy nie testuj tylko liczbami takimi jak101, które czyta się tak samo od przodu i od tyłu.Znak zamiast cyfry
'7'tocharo wartości liczbowej 55, a nie 7. Jeśli liczysz ztext.charAt(i), dostajesz bzdury. Wartość cyfry dajetext.charAt(i) - '0'. Tak samoc == 1to coś innego niżc == '1'.
Zadania z tego tematu
Przećwicz to, czego się nauczyłeś, od razu w przeglądarce. Testy natychmiast sprawdzają twoje rozwiązanie.
Powiązane artykuły
- Rekurencja i backtrackingRekurencja i backtracking w Javie prosto wyjaśnione: warunek stopu, stos wywołań, permutacje i wieże Hanoi z przykładami oraz typowymi błędami.Czytaj artykuł
- StringiStringi w Javie w prosty sposób: substring, charAt, split, equals i StringBuilder z przykładami. Do tego typowe błędy, np. porównywanie przez ==.Czytaj artykuł