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 b i 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 * b z dwoma wartościami int jest liczone jako int, nawet jeśli wynik trafia do zmiennej typu long. Przy 100000 * 99999 wychodzi błędna, a nawet ujemna liczba. Najpierw podziel (a / nwd(a, b) * b) albo zrób z jednego czynnika long. To samo dotyczy t * t <= n, gdy n jest blisko Integer.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 011 zamiast 110. I odwrotnie: przy wczytywaniu pierwszy znak to najwyższa pozycja. Dlatego nigdy nie testuj tylko liczbami takimi jak 101, które czyta się tak samo od przodu i od tyłu.

  • Znak zamiast cyfry

    '7' to char o wartości liczbowej 55, a nie 7. Jeśli liczysz z text.charAt(i), dostajesz bzdury. Wartość cyfry daje text.charAt(i) - '0'. Tak samo c == 1 to 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