You are on page 1of 519

Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek
Wersja 2.0 – zakończono XX.XX.2011, odwiedzono 324887 razy.
Zoptymalizowane dla IE i Firefox
przy rozdzielczości 1280x1024

Artykuł opisuje podstawowe algorytmy operujące na różnych strukturach danych.

Spis rozdziałów
Wstęp
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 1 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a


Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Macierze
Podstawowe pojęcia dotyczące macierzy
Reprezentacja macierzy w pamięci komputera
Mnożenie macierzy przez skalar
Dodawanie macierzy
Transponowanie macierzy
Mnożenie macierzy
Potęgowanie macierzy
Liczby Fibonacciego
Eliminacja Gaussa
Eliminacja Gaussa-Crouta
Wyznacznik macierzy
Rozkład LU
Rozkład LU – rozwiązywanie układu równań liniowych
Rozkład LU – macierz odwrotna
Rozkład LU – wyznacznik macierzy
Listy
Podstawowe pojęcia dotyczące list
Reprezentacja list w pamięci komputera
Operacje na listach jednokierunkowych
Operacje na listach dwukierunkowych
Operacje na listach cyklicznych jednokierunkowych
Liniowe przeszukiwanie listy
Przeszukiwanie liniowe z wartownikiem listy dwukierunkowej
Wyszukiwanie największego/najmniejszego elementu listy
Zliczanie elementów listy
Usuwanie z listy duplikatów
Odwracanie listy jednokierunkowej
Podział listy jednokierunkowej na dwie listy
Scalanie dwóch list posortowanych
Sortowanie listy jednokierunkowej przez scalanie
Sortowanie przez wstawianie z wykorzystaniem listy jednokierunkowej
Sortowanie szybkie listy dwukierunkowej
Samoorganizujące się listy
Haszowanie z wykorzystaniem list jednokierunkowych
Zbiory rozłączne – implementacja listowa
Stosy
Podstawowe pojęcia dotyczące stosów
Przeliczanie liczb na zapis w innym systemie pozycyjnym
Odwrotna Notacja Polska
Sortowanie przez wstawianie za pomocą stosów
Kolejki
Podstawowe pojęcia dotyczące kolejek
Kolejki priorytetowe
Drzewa
Podstawowe pojęcia dotyczące drzew
Przechodzenie drzew binarnych – DFS: pre-order, in-order, post-order
Przechodzenie drzew binarnych – BFS
Badanie drzewa binarnego
Prezentacja drzew binarnych
Kopiec binarny

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 2 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Drzewa wyrażeń
Drzewa poszukiwań binarnych – BST
Tworzenie drzewa BST
Równoważenie drzewa BST – algorytm DSW
Proste zastosowania drzew BST
Drzewa AVL
Drzewa Splay
Drzewa Czerwono-Czarne
Kompresja Huffmana
Zbiory rozłączne – implementacja za pomocą drzew
Zbiory
Podstawowe pojęcia dotyczące zbiorów
Reprezentacja zbiorów na listach
Reprezentacja zbiorów w tablicach
Reprezentacja zbiorów w mapach bitowych
Reprezentacja zbiorów w drzewach binarnych
Grafy
Podstawowe pojęcia dotyczące grafów
Reprezentacja grafów w komputerze
Przechodzenie grafów w głąb – DFS
Przechodzenie grafów wszerz – BFS
Transpozycja grafu
Kwadrat grafu
Graf krawędziowy
Stopień grafu
Znajdowanie ścieżki w grafie
Znajdowanie drogi w labiryncie
Spójność grafu
Znajdowanie spójnych składowych w grafie
Znajdowanie silnie spójnych składowych w grafie
Drzewa rozpinające grafu
Znajdowanie mostów w grafie
Znajdowanie punktów artykulacji w grafie
Grafy dwudzielne
Kojarzenie małżeństw
Cykliczność grafu
Znajdowanie cykli w grafie
Istnienie cyklu lub ścieżki Eulera
Znajdowanie cyklu lub ścieżki Eulera
Znajdowanie cyklu lub ścieżki Hamiltona
Sortowanie topologiczne grafu skierowanego
Najkrótsza ścieżka w grafie ważonym – algorytm Dijkstry
Najkrótsza ścieżka w grafie ważonym – algorytm Bellmana-Forda
Najkrótsze ścieżki pomiędzy wszystkimi parami wierzchołków w grafie ważonym - algorytm Floyda-Warshalla
Problem chińskiego listonosza
Problem komiwojażera
Minimalne drzewo rozpinające
Kolorowanie grafu
Znajdowanie klik w grafie
Sieci przepływowe (dział w budowie)
Podstawowe pojęcia dotyczące sieci przepływowych
Maksymalny przepływ w sieci – algorytmy Forda-Fulkersona i Edmondsa-Karpa
Znajdowanie maksymalnych skojarzeń za pomocą wyznaczania maksymalnego przepływu

Spis algorytmów

Przedziały liczbowe i liczby


Algorytm wyszukiwania liczb wg kryterium
Algorytm wyznaczania liczb wg kryterium
Algorytm generacji liczb parzystych
Algorytm wyznaczania liczb podzielnych przez zadane czynniki – wersja nr 1
Algorytm wyznaczania liczb podzielnych przez zadane czynniki – wersja nr 2
Algorytm wyszukiwania liczb niepodzielnych przez zadane liczby
Algorytm wyznaczania n kolejnych wyrazów ciągu arytmetycznego
Algorytm wyznaczania n kolejnych wyrazów całkowitego ciągu arytmetycznego
Algorytm Euklidesa – wersja nr 1
Algorytm Euklidesa – wersja nr 2
Algorytm Euklidesa – wersja binarna
Algorytm wyznaczania liczb względnie pierwszych
Algorytm wyznaczania najmniejszej wspólnej wielokrotności
Algorytm wyznaczania odwrotności modulo
Algorytm wyznaczania liczb pierwszych przez sprawdzanie podzielności
Algorytm wyznaczania liczb pierwszych przez sprawdzanie podzielności – wersja ulepszona
Algorytm sita Eratostenesa
Algorytm ulepszonego sita Eratostenesa – wersja nr 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 3 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Algorytm ulepszonego sita Eratostenesa – wersja nr 2


Algorytm sita liniowego
Algorytm sita Atkina-Bernsteina
Algorytm rozkładu liczby naturalnej na czynniki pierwsze – wersja nr 1
Algorytm rozkładu liczby naturalnej na czynniki pierwsze – wersja nr 2
Algorytm rozkładu liczby naturalnej na czynniki pierwsze metodą Fermata – wersja nr 1
Algorytm rozkładu liczby naturalnej na czynniki pierwsze metodą Fermata – wersja nr 2
Algorytm sprawdzania pierwszości liczby naturalnej – wersja nr 1
Algorytm sprawdzania pierwszości liczby naturalnej – wersja nr 2
Algorytm sprawdzania pierwszości liczby naturalnej – wersja nr 3
Algorytm obliczania a × b mod n
Algorytm obliczania ae mod n
Algorytm testu pierwszości Fermata
Algorytm sprawdzania pierwszości nieparzystej liczby naturalnej testem Millera-Rabina
Algorytm generacji liczb pseudolosowych
Algorytm generatora pseudolosowego Park-Miller
Algorytm inicjowania rejestru generatora pseudolosowego Mersenne Twister
Algorytm generatora pseudolosowego Mersenne Twister
Algorytm mieszania pseudolosowego
Algorytm losowania bez powtórzeń
Algorytm generacji liczb Fibonacciego metodą rekurencyjną
Algorytm generacji liczb Fibonacciego metodą iteracyjną
Algorytm obliczania wartości liczby zapisanej w systemie Fibonacciego
Algorytm przeliczania liczby dziesiętnej na zapis w systemie Fibonacciego
Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 1
Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 2
Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 3

Tablice – wektory
Algorytm wyszukiwania liniowego/sekwencyjnego
Algorytm wyszukiwania liniowego z wartownikiem
Algorytm zliczania liniowego
Algorytm zliczania liniowego z wartownikiem
Algorytm wyszukiwania elementu maksymalnego w zbiorze
Algorytm wyszukiwania pozycji elementu maksymalnego w zbiorze
Algorytm jednoczesnego wyszukiwania max i min w zbiorze
Algorytm sortowania przez wybór
Algorytm znajdowania najczęstszej wartości – wersja nr 1
Algorytm znajdowania najczęstszej wartości – wersja nr 2
Algorytm znajdowania najczęstszej wartości – wersja nr 3
Algorytm znajdowania najczęstszej wartości – wersja nr 4
Algorytm wyszukiwania lidera w zbiorze
Algorytm wyszukiwania binarnego
Algorytm wyszukiwania interpolacyjnego
Algorytm wyszukiwania k-tego największego elementu – wersja nr 1
Algorytm wyszukiwania k-tego największego elementu – wersja nr 2
Algorytm wyszukiwania k-tego największego elementu – wersja nr 3
Algorytm partycjonowania zbioru wg pierwszego elementu
Algorytm szybkiego wyszukiwania k-tego największego elementu
Algorytm szybkiego sortowania i znajdowania mediany
Algorytm szybkiego wyszukiwania mediany
Algorytm Wirtha szybkiego wyszukiwania mediany
Algorytmy obsługi struktury zbiorów rozłącznych w zrealizowanej w tablicy
Łańcuchy znakowe
Algorytm naiwny wyszukiwania wzorca w łańcuchu tekstowym
Algorytm naiwny wyszukiwania wzorca z wartownikami
Algorytm naiwny wyszukiwania maksymalnego prefikso-sufiksu
Algorytm Morrisa-Pratta wyszukiwania maksymalnego prefikso-sufiksu
Algorytm Morrisa-Pratta wyszukiwania wzorca
Algorytm Knutha-Morrisa-Pratta tworzenia tablicy KMPNext
Algorytm Knutha-Morrisa-Pratta wyszukiwania wzorca
Algorytm tworzenia tablicy Last[] dla algorytmu Boyera-Moore'a
Uproszczony algorytm Boyera-Moore'a wyszukiwania wzorca
Algorytm etapu nr 1 tworzenia tablicy BMNext[ ] dla pełnego algorytmu Boyera-Moore'a
Algorytm etapu nr 2 tworzenia tablicy BMNext[ ] dla pełnego algorytmu Boyera-Moore'a
Pełny algorytm Boyera-Moore'a wyszukiwania wzorca
Algorytm Karpa-Rabina wyszukiwania wzorca
Algorytm zliczania wyrazów
Algorytm podziału łańcucha na słowa
Algorytm wyszukiwania najdłuższego słowa w łańcuchu
Algorytm wyszukiwania najdłuższego wspólnego podłańcucha
Algorytm dynamiczny wyszukiwania najdłuższego wspólnego podłańcucha – wersja nr 1
Algorytm dynamiczny wyszukiwania najdłuższego wspólnego podłańcucha – wersja nr 2
Algorytm znajdowania długości najdłuższego wspólnego podciągu – wersja rekurencyjna
Algorytm znajdowania najdłuższego wspólnego podciągu – wersja rekurencyjna
Algorytm znajdowania długości najdłuższego wspólnego podciągu – wersja dynamiczna
Algorytm znajdowania najdłuższego wspólnego podciągu – wersja dynamiczna
Algorytm wyszukiwania najkrótszego wspólnego nadłańcucha
Algorytm wyszukiwania najdłuższego sufiksu pasującego do prefiksu w drugim łańcuchu
Algorytm wyszukiwania słów podwójnych w łańcuchu – wersja nr 1
Algorytm wyszukiwania słów podwójnych w łańcuchu – wersja nr 2
Algorytm naiwny wyszukiwania palindromów
Algorytm Manachera wyszukiwania palindromów
Algorytm gry MasterMind – komputer kontra człowiek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 4 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Algorytm gry MasterMind – człowiek kontra komputer


Algorytm szyfrowania tekstu kodem Cezara
Algorytm deszyfrowania tekstu zaszyfrowanego kodem Cezara
Algorytm szyfrowania z pseudolosowym odstępem
Algorytm deszyfrowania z pseudolosowym odstępem
Algorytm szyfrowania podstawieniowego ze zamianą liter w parach
Algorytm szyfrowania podstawieniowego za pomocą tablicy
Algorytm szyfrowania podstawieniowego z pseudolosowym mieszaniem
Algorytm rozszyfrowywania podstawieniowego z pseudolosowym mieszaniem
Algorytm tworzenia kluczy RSA
Algorytm szyfrowania kluczem publicznym RSA
Algorytm rozszyfrowywania kluczem prywatnym RSA
Algorytm dodawania dwóch dowolnie dużych, nieujemnych liczb całkowitych
Algorytm mnożenia dowolnie dużej liczby nieujemnej przez małą liczbę nieujemną
Algorytm mnożenia dwóch dowolnie dużych nieujemnych liczb całkowitych
Algorytm potęgowania dowolnie dużej liczby nieujemnej przez małą liczbę nieujemną
Algorytm obliczania dużych liczb Fibonacciego
Algorytm wstawiania łańcucha do tablicy haszowanej
Algorytm wyszukiwania łańcucha w tablicy haszowanej
Macierze
Algorytm mnożenia macierzy przez skalar
Algorytm dodawania macierzy
Algorytm transponowania macierzy
Algorytm transponowania macierzy kwadratowej w miejscu
Algorytm mnożenia macierzy
Algorytm potęgowania macierzy – wersja naiwna
Algorytm potęgowania macierzy – wersja ulepszona
Algorytm obliczania n-tej liczby Fibonacciego za pomocą szybkiego potęgowania macierzy Q
Algorytm eliminacji Gaussa
Algorytm eliminacji Gaussa-Crouta
Algorytm Doolitle'a rozkładu LU w miejscu
Algorytm rozkładu LU w miejscu
Algorytm rozwiązywania układu liniowego po rozkładzie LU
Algorytm obliczania macierzy odwrotnej przy pomocy rozkładu LU
Algorytm obliczania wyznacznika za pomocą rozkładu LU
Listy
Algorytm przechodzenia przez listę jednokierunkową
Algorytm wyznaczania liczby elementów na liście jednokierunkowej
Algorytm dołączania nowego elementu na początek listy jednokierunkowej
Algorytm usuwania elementu z początku listy jednokierunkowej
Algorytm dołączania nowego elementu do końca listy jednokierunkowej
Algorytm usuwania elementu z końca listy jednokierunkowej
Algorytm dołączania nowego elementu przed wybranym elementem listy jednokierunkowej
Algorytm dołączania nowego elementu za wybranym elementem listy jednokierunkowej
Algorytm usuwania wybranego elementu listy jednokierunkowej
Algorytm przechodzenia przez listę dwukierunkową
Algorytm dołączania nowego elementu na początek listy dwukierunkowej
Algorytm dołączania nowego elementu do końca listy dwukierunkowej
Algorytm dołączania nowego elementu przed wybranym elementem listy dwukierunkowej
Algorytm dołączania nowego elementu za wybranym elementem listy dwukierunkowej
Algorytm usuwania wybranego elementu listy dwukierunkowej
Algorytm usuwania elementu z początku listy dwukierunkowej
Algorytm usuwania elementu z końca listy dwukierunkowej
Algorytm przechodzenia przez listę cykliczną listę jednokierunkową
Algorytm wyznaczania liczby elementów listy cyklicznej jednokierunkowej
Algorytm wstawiania następnika punktu wejścia do listy cyklicznej jednokierunkowej
Algorytm usuwania następnika punktu wejścia do listy cyklicznej jednokierunkowej
Algorytm wyszukiwania liniowego na liście niecyklicznej
Algorytm wyszukiwania liniowego na liście cyklicznej
Algorytm przeszukiwania liniowego z wartownikiem dla listy dwukierunkowej
Algorytm wyszukiwania na liście elementu największego
Algorytm zliczania wystąpień danej wartości na liście
Algorytm usuwania duplikatów z listy jednokierunkowej
Algorytm usuwania duplikatów z listy dwukierunkowej
Algorytm usuwania duplikatów z posortowanej listy jednokierunkowej
Algorytm odwracania kolejności elementów na liście jednokierunkowej
Algorytm podziału listy jednokierunkowej na dwie podlisty
Algorytm scalania dwóch jednokierunkowych list posortowanych w jednokierunkową listę posortowaną
Algorytm sortowania listy jednokierunkowej przez scalanie
Algorytm sortowania przez wstawianie z wykorzystaniem listy jednokierunkowej
Algorytm szybkiego sortowania listy dwukierunkowej
Algorytm rekurencyjnego partycjonowania listy
Algorytm wyszukiwania z przesuwaniem na początek listy
Algorytm wyszukiwania ze zliczaniem znalezionych elementów i sortowaniem listy
Algorytm wyszukiwania z przemieszczaniem elementów
Algorytm wstawiania danych do tablicy haszowanej metodą porcjowania
Algorytm wyszukiwania danych w tablicy haszowanej metodą porcjowania
Algorytmy dla struktury zbiorów rozłącznych zrealizowanej za pomocą list dwukierunkowych – wersja 1
Algorytmy dla struktury zbiorów rozłącznych zrealizowanej za pomocą list dwukierunkowych – wersja 2
Algorytmy dla struktury zbiorów rozłącznych zrealizowanej za pomocą list dwukierunkowych – wersja 3
Stosy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 5 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Stos w tablicy – algorytm sprawdzania, czy stos jest pusty


Stos w tablicy – algorytm odczytu szczytu stosu
Stos w tablicy – algorytm zapisu na stos
Stos w tablicy – algorytm usuwania ze stosu
Stos na liście – algorytm sprawdzania, czy stos jest pusty
Stos na liście – algorytm odczytu szczytu stosu
Stos na liście – algorytm zapisu na stos
Stos na liście – algorytm usuwania ze stosu
Algorytm przeliczania liczb na zapis w innym systemie pozycyjnym
Algorytm obliczania wartości wyrażenia ONP
Algorytm przekształcania wyrażenia na postać ONP
Algorytm sortowania przez wstawianie z wykorzystaniem stosów
Kolejki
Kolejka w tablicy – algorytm sprawdzania, czy kolejka jest pusta
Kolejka w tablicy – algorytm odczytu kolejki
Kolejka w tablicy – algorytm zapisu do kolejki
Kolejka w tablicy – algorytm usuwania z kolejki
Kolejka na liście – algorytm sprawdzania, czy kolejka jest pusta
Kolejka na liście – algorytm odczytu kolejki
Kolejka na liście – algorytm zapisu do kolejki
Kolejka na liście – algorytm usuwania z kolejki
Kolejka cykliczna – algorytm sprawdzania, czy kolejka jest pusta
Kolejka cykliczna – algorytm odczytu kolejki
Kolejka cykliczna – algorytm zapisu do kolejki
Kolejka cykliczna – algorytm usuwania z kolejki
Kolejka priorytetowa – algorytm zapisu do kolejki
Drzewa
Algorytm rekurencyjny DFS:preorder dla drzewa binarnego
Algorytm stosowy DFS:preorder dla drzewa binarnego
Algorytm rekurencyjny DFS:inorder dla drzewa binarnego
Algorytm stosowy DFS:inorder dla drzewa binarnego
Algorytm rekurencyjny DFS:postorder dla drzewa binarnego
Algorytm stosowy DFS:postorder dla drzewa binarnego
Algorytm kolejkowy BFS dla drzewa binarnego
Algorytm drukowania drzewa binarnego w oknie konsoli
Algorytm dodawania nowego elementu do kopca
Algorytm usuwania szczytu kopca
Algorytm tworzenia drzewa wyrażeń z wyrażenia ONP
Algorytm wyszukiwania węzła w drzewie BST
Algorytm wyszukiwania w drzewie BST węzła o najmniejszym kluczu
Algorytm wyszukiwania w drzewie BST węzła o największym kluczu
Algorytm znajdowania następnika węzła w drzewie BST
Algorytm znajdowania poprzednika węzła w drzewie BST
Algorytm wstawiania węzła do drzewa BST
Algorytm usuwania węzła z drzewa BST
Algorytm rotacji w prawo drzewa BST
Algorytm rotacji w lewo drzewa BST
Algorytm wyznaczania wartości 2|log x|
Algorytm DSW – zrównoważanie drzewa BST
Algorytm rotacji RR dla drzewa AVL
Algorytm rotacji LL dla drzewa AVL
Algorytm rotacji RL dla drzewa AVL
Algorytm rotacji LR dla drzewa AVL
Algorytm wstawiania węzła do drzewa AVL
Algorytm usuwania węzła z drzewa AVL
Algorytm operacji splay
Algorytm wyszukiwania węzła w drzewie Splay
Algorytm wstawiania węzła do drzewa Splay
Algorytm usuwania węzła o kluczu k z drzewa Splay
Algorytm wstawiania węzła do drzewa czerwono-czarnego
Algorytm usuwania węzła z drzewa czerwono-czarnego
Algorytm tworzenia drzewa kodu przystankowego
Algorytm dekodowania wiadomości w kodzie bezprzystankowym
Algorytm rekurencyjnego kodowania kodem bezprzystankowym
Algorytm tworzenia listy węzłów drzewa kodu bezprzystankowego dla algorytmu Huffmana
Algorytm tworzenia drzewa kodu Huffmana
Algorytmy dla struktury zbiorów rozłącznych zrealizowanej za pomocą drzew – wersja 1
Algorytmy dla struktury zbiorów rozłącznych zrealizowanej za pomocą drzew – wersja 2

Zbiory
Algorytmy operacji dla zbiorów reprezentowanych listami jednokierunkowymi
Algorytmy operacji dla zbiorów reprezentowanych tablicami dynamicznymi
Algorytmy operacji dla zbiorów reprezentowanych tablicami haszowanymi
Algorytmy operacji dla zbiorów reprezentowanych mapami bitowymi
Algorytmy operacji dla zbiorów reprezentowanych drzewami binarnymi

Grafy
Algorytm rekurencyjny DFS – wersja poglądowa
Algorytm rekurencyjny DFS dla macierzy sąsiedztwa
Algorytm rekurencyjny DFS dla macierzy incydencji
Algorytm rekurencyjny DFS dla list sąsiedztwa

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 6 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Algorytm stosowy DFS – wersja poglądowa


Algorytm stosowy DFS dla list sąsiedztwa
Algorytm BFS – wersja poglądowa
Algorytm BFS dla macierzy sąsiedztwa
Algorytm BFS dla list sąsiedztwa
Algorytm transponowania grafu zadanego listami sąsiedztwa
Algorytm transponowania grafu zadanego macierzą incydencji
Algorytm obliczania kwadratu grafu zadanego macierzą sąsiedztwa
Algorytm obliczania kwadratu grafu zadanego listami sąsiedztwa
Algorytm tworzenia grafu krawędziowego
Algorytm wyznaczania stopnia grafu nieskierowanego
Algorytm wyznaczania stopnia grafu skierowanego
Algorytm znajdowania ścieżki przejściem DFS – wersja nr 1
Algorytm znajdowania ścieżki przejściem DFS – wersja nr 2
Algorytm znajdowania ścieżki przejściem BFS
Algorytm badania spójności grafu nieskierowanego
Algorytm budowania grafu podstawowego z grafu nieskierowanego
Algorytm budowania grafu podstawowego w grafie nieskierowanym
Algorytm wyszukiwania spójnych składowych w grafie nieskierowanym
Algorytm wyszukiwania spójnych składowych w grafie nieskierowanym za pomocą zbiorów rozłącznych
Algorytm naiwny wyszukiwania silnie spójnych składowych w grafie skierowanym
Algorytm Korsaraju wyszukiwania silnie spójnych składowych w grafie skierowanym
Algorytm Tarjana wyszukiwania silnie spójnych składowych w grafie skierowanym
Algorytm naiwny wyszukiwania mostów w grafie nieskierowanym
Algorytm Tarjana wyszukiwania mostów w grafie nieskierowanym
Algorytm naiwny wyszukiwania punktów artykulacji w grafie nieskierowanym
Algorytm Tarjana wyszukiwania punktów artykulacji w grafie nieskierowanym
Algorytm tworzenia drzewa rozpinającego w głąb
Algorytm tworzenia drzewa rozpinającego wszerz
Algorytm sprawdzania dwudzielności grafu za pomocą BFS
Algorytm kojarzenia małżeństw
Algorytm sprawdzania cykliczności spójnego grafu nieskierowanego
Algorytm sprawdzania cykliczności niespójnego grafu nieskierowanego
Algorytm sprawdzania cykliczności grafu skierowanego
Algorytm znajdowania cykli w grafie nieskierowanym
Algorytm znajdowania cykli w grafie skierowanym
Algorytm badający istnienie ścieżki lub cyklu Eulera w grafie nieskierowanym
Algorytm badający istnienie ścieżki lub cyklu Eulera w grafie skierowanym
Algorytm znajdowania cyklu Eulera w spójnym grafie nieskierowanym
Algorytm Fleury'ego znajdowania cyklu lub ścieżki Eulera w grafie nieskierowanym
Algorytm Hierholzera znajdowania cyklu lub ścieżki Eulera w grafie skierowanym
Algorytm wyszukiwania ścieżek i cykli Hamiltona
Algorytm sortowania topologicznego acyklicznego grafu skierowanego
Algorytm sortowania topologicznego acyklicznego grafu skierowanego z wykorzystaniem DFS
Algorytm Dijkstry obliczania najkrótszych ścieżek i kosztów dojścia w grafie ważonym – wersja poglądowa
Algorytm Dijkstry z kolejką priorytetową w postaci kopca
Algorytm Bellmana-Forda znajdowania najkrótszych ścieżek w spójnym, skierowanym grafie ważonym
Algorytm Floyda-Warshalla znajdowania najmniejszych kosztów dojścia pomiędzy wszystkimi parami wierzchołków grafu ważonego
Algorytm rozszerzony Floyda-Warshalla
Algorytm ogólny dla problemu chińskiego listonosza
Algorytm rozwiązujący problem komiwojażera dla małych grafów
Algorytm Kruskala znajdowania minimalnego drzewa rozpinającego w spójnym, ważonym grafie nieskierowanym
Algorytm Prima znajdowania minimalnego drzewa rozpinającego w spójnym, ważonym grafie nieskierowanym
Algorytm optymalnego kolorowania grafu
Algorytm zachłannego kolorowania grafu
Algorytm LF (largest first) kolorowania grafu
Algorytm Brona-Kerboscha znajdowania wszystkich klik maksymalnych w grafie nieskierowanym - wersja nr 1
Algorytm Brona-Kerboscha znajdowania wszystkich klik maksymalnych w grafie nieskierowanym - wersja nr 2
Algorytm Brona-Kerboscha znajdowania kliki największej w grafie nieskierowanym

Sieci przepływowe
Algorytm Edmondsa-Karpa wyszukujący maksymalny przepływ – wersja z macierzami sąsiedztwa
Algorytm Edmondsa-Karpa wyszukujący maksymalny przepływ – wersja z listami sąsiedztwa

Literatura
Wprowadzenie do algorytmów, T.H.Cormen, C.E.Leiserson, R.L.Rivest, WNT 1997,2004
Algorytmy + Struktury danych = Programy, N.Wirth, WNT 2001
Algorytmy i struktury danych, L.Banachowski, K.Diks, W.Rytter, WNT 2006
Język C++, Stroustroup, WNT 2002
C++, 50 efektywnych sposobów na udoskonalenie Twoich programów, S.Meyers, HELION 2003.
Język C++ bardziej efektywny, S.Meyers, WNT 1998
STL w praktyce. 50 sposobów efektywnego wykorzystania, S.Meyers, HELION 2004
Symfonia C++, J.Grębosz, Oficyna Kallimach, Kraków 1999
Język ANSI C, B.Kernighan, D.Ritchie, WNT 2004
Wydajne programowanie – Extreme Programming, K.Beck, A.Cynthia, Mikom 2005
Jak pisać efektywne przypadki zastosowań, A.Cockburn, WNT 2004
7 nawyków skutecznego działania, S.Covey, REBIS 2002
Aspekty kombinatoryki, V.Bryant, WNT 1977
Matematyka Konkretna, R.L.Graham, D.E.Knuth, O.Patashnik, PWN 1996
Kombinatoryka dla programistów, W.Lipski

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 7 / 519
Algorytmy i Struktury Danych (C)2014 I-LO w Tarnowie 2014-10-03

Analiza kombinatoryczna, W.Lipski, W.Marek, PWN 1986


Metody numeryczne, Z.Fortuna, B.Macukow, J.Wąsowski, WNT Warszawa, 1982, 2005
Przegląd metod i algorytmów numerycznych, M.Dryja, J. i M.Jankowscy, WNT 1988
Wprowadzenie do teorii grafów, R.J.Wilson, PWN 1985
Wstęp do matematyki, H.Rasiowa, PWN 1971, 1984, 1998
Teoria mnogości, K.Kuratowski, A.Mostowski, PWN 1978
Sztuka Programowania Komputerów – The Art of Computer Programming, D.E.Knuth, Addison-Wesley Publishing Company
Perły Programowania – Programming Pearls, J.Bentley, Addison-Wesley Publishing Company 2000
Struktury Danych i Projektowanie Programów w C++ – Data Structures and Program Design in C++, R.L. Kruse, A.J. Ryba, Prentice Hall
Klejnoty Teorii Łańcuchów Tekstowych – Jewels of stringology, W.Rytter, M.Crochemore, World Scientific 2003
Inżynieria Oprogramowania – Software Engineering, R.Pressman, McGraw-Hill 1997.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/index.php 8 / 519
Algorytmy i Struktury Danych - Wstęp 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wstęp
Podrozdziały
Algorytmy i struktury danych
Języki programowania
Aplikacja konsoli
Prezentacja algorytmu

Algorytmy i struktury danych


Informacja jest przechowywana przez komputer w różnych strukturach danych. Niniejszy artykuł opisuje podstawowe algorytmy
operujące na różnych strukturach danych, z którymi najczęściej spotyka się programista we współczesnej informatyce. Dobór
właściwej struktury danych dla określonego algorytmu jest jedną z podstawowych umiejętności, które musi opanować informatyk.
Od tego doboru zależy wiele rzeczy – efektywność algorytmu, a zatem czas jego wykonania na określonym komputerze,
wymagania pamięciowe, złożoność operacji itp. Mam nadzieję, że podane tutaj informacje pomogą w takim doborze.

Języki programowania
Omawiane w artykule algorytmy są zawsze przedstawione w postaci listy kroków wraz ze specyfikacją danych wejściowych i
wyjściowych. Przykładowe implementacje algorytmów zrealizowaliśmy w trzech wybranych środowiskach programowania:
Lazarus (Pascal)
Code::Blocks (C++)
Free Basic

Wszystkie trzy są darmowe, pracują w środowisku Windows/Linux (w Linuxie instaluje się tylko same kompilatory Free Pascala
oraz Free Basica (kompilator C++ jest zawsze dostępny w Linuxie) a jako IDE używać Geany) i można je legalnie pobrać w sieci z
witryny producenta, co polecamy niezwłocznie uczynić. Przykłady programów w tych językach nie są celem tego opracowania (jak
sądzą niektórzy czytelnicy). Są nim algorytmy i na nich głównie skupiamy uwagę. Znając działanie algorytmu można go
zaimplementować w dowolnym języku programowania. Dlatego nie żądajcie od nas przesyłania przykładów w dialektach
powyższych języków, czy też w innych językach programowania – tym nie zajmujemy się z braku czasu oraz wszechwiedzy.
Niekiedy dodaliśmy również przykład w języku skryptowym JavaScript, który pozwala czytelnikowi interaktywnie sprawdzić
omawiane zagadnienia. Jednakże w przypadku JavaScript nie bawimy się w prezentację kodu w artykule. Dostęp do kodu
JavaScript można w prosty sposób uzyskać przeglądając źródło strony WWW, co potrafi wykonać praktycznie każda współczesna
przeglądarka.

Aplikacja konsoli
Wszystkie przykłady programów będą uruchomione w trybie konsoli. Cechą charakterystyczną tego trybu jest okno tekstowe (w
Linuxie jest to okno terminala), które służy zarówno do wprowadzania jak i do wyprowadzania danych. Zaletą jest prostota
komunikacji z użytkownikiem. W języku Pascal wykorzystuje się polecenia writeln i write do zapisu oraz readln i read do
odczytu danych. W języku C++ można w tym charakterze wykorzystywać obiekty biblioteki STL cout oraz cin. W języku Basic
stosowane są polecenia print i input.
Dane wprowadzane do programu pracującego w trybie konsoli mogą pochodzić z kilku źródeł. Pierwszym z nich jest oczywiście
klawiatura. Jednakże wpisywanie długich ciągów liczb, szczególnie gdy muszą się one powtarzać przy testowaniu działania
programu, jest niewygodne. W takiej sytuacji mamy dwa wyjścia:

1. Dane wklejamy do okna konsoli poprzez schowek Windows. W notatniku przygotowujemy sobie odpowiednie liczby do
wprowadzenia do programu, następnie kopiujemy je do schowka Windows i wklejamy do okna konsoli. W tym celu klikamy
w okienko konsoli prawym przyciskiem myszki i z menu kontekstowego wybieramy polecenie Wklej. Wybierając polecenie
Oznacz, a następnie zaznaczając myszką obszar w oknie konsoli i wciskając na koniec klawisz Enter, można skopiować
do schowka treść okna konsoli. Poniższy tekst pochodzi z takiej właśnie operacji:
C:\>date
Bieżąca data: 2008-03-05

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0001.php 9 / 519
Algorytmy i Struktury Danych - Wstęp 2014-10-03

Wprowadź nową datę: (rr-mm-dd)

C:\>time
Bieżąca godzina: 22:10:08,84
Wprowadź nową godzinę:

C:\>

2. Przy uruchamianiu programu przekierowujemy jego standardowe wejście lub wyjście do pliku zamiast na konsolę. W tym
celu wystarczy wydać polecenie:
nazwa_programu < plik_wejścia > plik_wyjścia
Załóżmy na przykład, iż napisaliśmy program o nazwie szukaj.exe. Jeśli dane wejściowe dla programu przygotowaliśmy w
pliku d_we.txt, a dane wyjściowe chcemy otrzymać w pliku d_wy.txt, to wydajemy następujące polecenie:
szukaj < d_we.txt > d_wy.txt
Program uruchamiamy w opisany powyżej sposób z okna konsoli. W tym celu należy kliknąć przycisk Start i w opcji
uruchom wpisać polecenie cmd. Polecenie to otwiera okno konsoli i umożliwia użytkownikowi wprowadzanie z klawiatury
różnych rozkazów systemu operacyjnego. Przy pomocy polecenia cd przechodzimy do katalogu z projektem i wydajemy
wyżej opisane polecenia. Program, który ma być uruchamiany w takim trybie nie powinien przy zakończeniu czekać na
reakcję użytkownika – po prostu czyta dane, przetwarza je, wyprowadza wyniki i kończy działanie. Tak właśnie pracują
nasze przykłady.
Jeśli często wykorzystujesz uruchamianie z przekierowaniem standardowego wejścia/wyjścia do tych samych plików
z danymi, to rozważ stworzenie prostego batcha – pliku skryptowego z rozszerzeniem bat lub cmd, w którym po
prostu umieszcza się kolejne polecenia do wykonania. Batch zapisuje się następnie w katalogu projektu pod jakąś
krótką nazwą – ja stosuję !.cmd i w konsoli wystarczy wpisać !, aby uruchomić kolejne polecenia z pliku !.cmd.
Proste i wygodne. Przykładowy batch !.cmd może wyglądać tak:
@echo off
cls
echo Test aplikacji SZUKAJ.EXE
echo Odczyt danych, przetwarzanie i zapis wynikow, prosze czekac...
echo.
szukaj < s_we.txt > d_wy.txt
echo Koniec przetwarzania.
type d_wy.txt
echo.
Prezentowane w artykule programy są maksymalnie uproszczone – odczytują wymagane dane wejściowe,
przetwarzają je i wypisują wyniki beż żadnych dodatkowych upiększeń. Mogą one być materiałem do dopracowania
n a lekcji lub kole informatycznym – np. uczniowie dopisują do nich odpowiedni interfejs komunikacji z
użytkownikiem. Wszystko zależy zatem od nauczyciela. Aplikacje te należy uruchamiać z poziomu okna konsoli
(Start → Uruchom → cmd), ponieważ nie będą czekały na reakcję użytkownika przy zakończeniu działania –
uruchomienie ich z poziomu Windows spowoduje zamknięcie okienka konsoli przy zakończeniu programu, zatem
zapewne nie zdążysz nic przeczytać. W takiej postaci programy tworzy się zwykle na wszelkiego rodzaju
konkursach, w tym na olimpiadzie informatycznej. Dlatego i my wybieramy ten sposób.
Jeśli jednak chciałbyś pracować w środowisku Windows, to możesz dodać na końcu programu następujące
polecenia wstrzymujące zamknięcie okna konsoli, aż do naciśnięcia klawisza Enter:

Pascal : readln;
C++ : system("pause");
Basic : Sleep

Prezentacja algorytmu
Wszystkie algorytmy opisane w tym artykule są przedstawione w postaci listy kroków oraz implementacji w trzech językach
programowania: FreePascal, Code::Blocks oraz FreeBasic. Lista kroków zawsze jest poprzedzona specyfikacją algorytmu
zbudowaną z następujących trzech sekcji:

Dane wejściowe – określa dane, które algorytm będzie przetwarzał. W opisie algorytmu pomijamy odczyt tych danych –
po prostu zakładamy, iż będą one w jakiś sposób dostępne.
Dane wyjściowe – określa wynik pracy algorytmu, czyli co algorytm wyprodukuje podczas swojej pracy.
Dane pomocnicze: – jeśli algorytm wymaga dodatkowych zmiennych lub funkcji, to tutaj zostają one opisane. Są to
wewnętrzne zmienne algorytmu.
Na liście kroków używamy następujących konstrukcji algorytmicznych:

zmienna ← wyrażenie
Operacja przypisania. Wartość wyrażenia zostaje umieszczona w zmiennej.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0001.php 10 / 519
Algorytmy i Struktury Danych - Wstęp 2014-10-03

zmienna1 ↔ zmienna2
Zamiana zawartości. Po operacji zmienna1 będzie zawierała to co zmienna2, a zmienna2 to co zmienna1.

Jeśli warunek, to oparacja1, inaczej operacja2


Jest to typowa instrukcja warunkowa. Jeśli warunek będzie spełniony, to zostanie wykonana operacja1 (operacja2 nie
będzie wykonana). Jeśli warunek nie będzie spełniony, to zostanie wykonana operacja 2 (teraz operacja1 nie będzie
wykonana). Jeśli brak członu inaczej, to algorytm przechodzi do następnego kroku. Konstrukcja ta umożliwia
tworzenie rozgałęzień w algorytmie. Czasami zamiast pojedynczej operacji podajemy ich ciąg rozdzielony
przecinkami.

Dla zmienna = a,b,...,n wykonuj operacja


Dla zmienna = a,b,...,n wykonuj kroki Kxx...Kyy
Jest to konstrukcja pętli iteracyjnej. Dla kolejnych wartości zmiennej równych a,b..., c wykonywana jest wskazana
operacja lub wykonywane są kroki w zakresie od Kxx do Kyy. Kroki wykonywane są kolejno od góry do dołu. Gdy
pętla się zakończy wykonywany jest krok Kyy+1 – znajdujący się za ostatnią z powtarzanych instrukcji. Dodatkowo
w obrębie pętli stosujemy wcięcia, aby poprawić czytelność tej konstrukcji.

Dopóki warunek, wykonuj operacja


Dopóki warunek, wykonuj kroki Kxx...Kyy
Jest to konstrukcja pętli warunkowej. Jeśli warunek jest prawdziwy, to wykonywana jest operacja lub kolejne kroki od
Kxx do Kyy. Kroki wykonywane są kolejno od góry do dołu. Po ostatnim kroku Kyy warunek jest ponownie
testowany i w przypadku jego spełnienia, kroki Kxx ... Kyy są wykonywane ponownie. Gdy pętla się zakończy
wykonywany jest krok Kyy+1 – za ostatnią z powtarzanych instrukcji. W przypadku, gdy warunek jest nieprawdziwy
przed wejściem do pętli, nie zostanie wykonany żaden krok od Kxx do Kyy – algorytm przejdzie do kroku Kyy+1.

Idź do Kxx
Następnym krokiem wykonywanym przez algorytm będzie krok Kxx.

Następny obieg pętli Kxx


Algorytm przejdzie do wykonania kolejnego obiegu pętli zdefiniowanej w kroku Kxx. Przed rozpoczęciem obiegu
sprawdzane są odpowiednie warunki:
– dla pętli warunkowej warunek kontynuacji musi być prawdziwy
– dla pętli iteracyjnej zmienna sterująca otrzymuje kolejną wartość i, jeśli mieści się ona w zakresie, to pętla
wykonuje kolejny obieg.

Pisz wyrażenie
Algorytm wyprowadza na wyjście wartość wyrażenia.

Czytaj zmienna [, zmienna]


Algorytm odczytuje z wejścia dane dla podanej zmiennej. Tam, gdzie dane wejściowe są oczywiste i opisane w
specyfikacji, pomijamy ich odczyt w algorytmie.

Zakończ
Kończy działanie algorytmu

Zakończ z wynikiem x
Kończy działanie algorytmu z wynikiem x – ta postać zakończenia jest używana w algorytmach funkcji.

W wyrażeniach stosujemy następujące operatory:

+-×: podstawowe operacje arytmetyczne


√ pierwiastek kwadratowy
mod reszta z dzielenia całkowitoliczbowego
div dzielenie całkowite
operacja logiczna alternatywy
operacja logiczna koniunkcji
operacja logiczna sumy symetrycznej
¬ operacja logiczna negacji
and bitowa operacja koniunkcji
or bitowa operacja alternatywy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0001.php 11 / 519
Algorytmy i Struktury Danych - Wstęp 2014-10-03

not bitowa operacja negacji


xor bitowa operacja sumy modulo 2
shr przesunięcie bitowe w prawo
shl przesunięcie bitowe w lewo
[w] część całkowita z w
w zaokrąglenie w dół do najbliższej liczby całkowitej
w zaokrąglenie w górę do najbliższej liczby całkowitej
<≤=≠≥> operatory porównań
→ wskazanie pola struktury
. odwołanie do pola struktury
Zapraszam do lektury
mgr Jerzy Wałaszek

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0001.php 12 / 519
Algorytmy i Struktury Danych - Przedziały liczbowe i liczby 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Przedziały liczbowe i liczby


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Podejście pierwsze
Liczby parzyste i nieparzyste Podejście drugie
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

W tym dziale zajmujemy się problemami wyszukiwania liczb, które spełniają pewne kryteria. Znalezione liczby mogą być później
wykorzystywane w charakterze danych wejściowych dla algorytmów wyższego poziomu. Istnieją zasadniczo dwa podejścia do rozwiązania
problemu.

Podejście pierwsze
Metoda brutalna (ang. brutal force) polega na przejściu przez wszystkie wartości w danym przedziale liczbowym i sprawdzeniu
każdej z nich, czy spełnia wymagane kryterium. Jeśli tak, to znaleziona liczba jest przekazywana na wyjście.

Algorytm wyszukiwania liczb wg kryterium


Wejście
a – początek przedziału, a Z
b – koniec przedziału, b Z, a < b
Wyjście:
Liczby z przedziału <a,b>, które spełniają zadane kryterium
Dane pomocnicze:
i – kolejne liczby w przedziale <a,b>, i Z
Lista kroków:
K01: Dla i = a, a+1, ..., b ; tworzymy pętlę przechodzącą przez kolejne wartości w przedziale
wykonuj: ; testujemy kolejną liczbą. Jeśli spełnia kryterium, to
Jeśli i spełnia kryterium, przekazujemy ją na wyjście
to pisz i

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0002.php 13 / 519
Algorytmy i Struktury Danych - Przedziały liczbowe i liczby 2014-10-03

K02: Zakończ

Uwagi
Podejście brutalne stosujemy zwykle w początkowej fazie projektowania algorytmu. Jeśli kryterium nie zależy od ilości liczb w
przedziale, to metoda posiada klasę czasowej złożoności obliczeniowej równą O(n). Zaletą jest prostota algorytmu. Wadą jest
konieczność testowania wszystkich liczb w przedziale.

Podejście drugie
Jeśli postać kryterium pozwala nam przewidzieć, obliczyć wartości, które go spełniają, możemy zastosować inne rozwiązanie:
Wyznaczamy pierwszą liczbę w przedziale <a,b>, która spełnia kryterium. Następnie w pętli sprawdzamy, czy
wyznaczona liczba mieści się w przedziale <a,b>. Jeśli tak, przesyłamy ją na wyjście, po czym wyznaczamy
kolejną liczbę i wracamy na początek pętli. Pętlę kontynuujemy, aż wygenerowana liczba wyjdzie poza przedział
<a,b>.

Algorytm wyznaczania liczb spełniających kryterium


Wejście
a – początek przedziału, a Z
b – koniec przedziału, b Z, a < b
Wyjście:
Liczby z przedziału <a,b>, które spełniają zadane kryterium
Dane pomocnicze:
i – liczby w przedziale <a,b> spełniające kryterium, i Z
Lista kroków:
K01: i ← pierwsza liczba w <a,b> spełniająca ; ustalamy początkową wartość
kryterium
K02: Dopóki i ≤ b, wykonuj kroki K03...K04 ; tworzymy pętlę generującą wynikowe liczby w
<a,b>
K03: Pisz i ; wyprowadzamy liczbę spełniającą kryterium
K04: i ← następna liczba spełniająca kryterium ; wyznaczamy następną liczbę
K05: Zakończ

Uwagi
Takie podejście pozwala nam wyeliminować puste przebiegi pętli – wykonuje się ona tylko tyle razy, ile jest
potrzebne na wygenerowanie liczb spełniających kryterium. W efekcie algorytm działa dużo szybciej. W pewnych
sytuacjach możemy nawet obniżyć klasę czasowej złożoności obliczeniowej poniżej O(n). Wadą rozwiązania jest
konieczność analizy problemu, co czasami może być bardzo trudne.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0002.php 14 / 519
Algorytmy i Struktury Danych - Przedziały liczbowe i liczby 2014-10-03

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0002.php 15 / 519
Algorytmy i Struktury Danych - Liczby parzyste i nieparzyste 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby parzyste i nieparzyste


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Liczby parzyste
Liczby parzyste i nieparzyste Liczby nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
W przedziale całkowitym <a,b> wyszukaj wszystkie liczby parzyste.

Liczby parzyste
W wielu algorytmach musimy wygenerować liczby parzyste z zadanego przedziału <a,b> liczb całkowitych. Tego typu zadanie
rozwiązujemy stosując podejście nr 2. Ponieważ granice przedziału a i b mogą być dowolnymi liczbami całkowitymi, musimy
najpierw znaleźć najmniejszą liczbę parzystą z przedziału <a,b>.

1. Jeśli a jest parzyste, to najmniejszą liczbą parzystą w tym przedziale będzie właśnie a.
2. Jeśli a nie jest parzyste, to najmniejszą liczbą parzystą będzie a + 1.

Parzystość a sprawdzimy badając resztę z dzielenia a przez 2. Jeśli reszta jest zerowa, to a jest liczbą parzystą. Jeśli a nie jest
liczbą parzystą, to

1. Reszta wynosi 1 dla a > 0


2. Reszta wynosi -1 dla a < 0

Z powyższego wnioskujemy, iż pierwszą liczbę parzystą w przedziale całkowitym <a,b> otrzymamy następująco:

i=a
Jeśli reszta z dzielenia a przez 2 jest różna od 0, to zwiększ i o 1

Następna liczba parzysta jest zawsze o 2 większa. Podsumowując otrzymujemy następujący, prosty algorytm generacji liczb
parzystych w przedziale całkowitym <a,b>:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0003.php 16 / 519
Algorytmy i Struktury Danych - Liczby parzyste i nieparzyste 2014-10-03

Algorytm generacji liczb parzystych


Wejście
a – początek przedziału, a Z
b – koniec przedziału, b Z, a < b
Wyjście:
Kolejne liczby parzyste zawarte w przedziale <a,b>
Zmienna pomocnicza
i – przebiega przez kolejne liczby parzyste w przedziale <a,b>, i Z
Lista kroków:
K01: i ← a ; obliczamy pierwszą liczbę parzystą
K02: Jeśli a mod 2 ≠ 0, to i ← i + 1
K03: Dopóki i ≤ b, wykonuj kroki K03...K04 ; generujemy liczby parzyste w przedziale <a,b>
K04: Pisz i ; wyprowadzamy liczbę parzystą
K05: i ← i + 2 ; następna liczba parzysta
K06: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program spodziewa się w pierwszym wierszu liczb a i b. W kolejnych wierszach wyświetla liczby parzyste zawarte
w przedziale <a,b>.

Lazarus Code::Blocks Free Basic

// Liczby parzyste
// Data : 11.03.2008
// (C)2012 mgr Jerzy Wałaszek
// Liczby parzyste //----------------------------
// Data : 11.03.2008
// (C)2012 mgr Jerzy Wałaszek ' Liczby parzyste
#include <iostream> ' Data : 11.03.2008
//----------------------------
' (C)2012 mgr Jerzy Wałaszek
using namespace std; '----------------------------
program prg;
int main() Dim As Integer a,b,i
var a,b,i : longint; {
int a,b,i; Input a, b
begin
readln(a,b); i = a
cin >> a >> b; If a Mod 2 Then i += 1
i := a; i = a;
if(a mod 2 <> 0) then inc(i); While i <= b
if(a % 2) i++; Print i; " ";
while(i <= b) do while(i <= b)
begin i += 2
{ Wend
write(i,' '); cout << i << " ";
inc(i,2); i += 2; Print
end; End
}
writeln; cout << endl;
end. return 0;
}

Wynik
-3 15
-2 0 2 4 6 8 10 12 14

Liczby parzyste
(C)2012 mgr Jerzy Wałaszek
a= -3 , b = 15

Wykonaj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0003.php 17 / 519
Algorytmy i Struktury Danych - Liczby parzyste i nieparzyste 2014-10-03

...

Liczby nieparzyste
Liczby nieparzyste generujemy w identyczny sposób: wyznaczamy pierwszą liczbę nieparzystą w przedziale <a,b>, a kolejne są o
2 większe. Jeśli a jest nieparzyste, to pierwsza liczba nieparzysta jest równa a, w przeciwnym razie jest o 1 większa.
Poniższy program czyta krańce przedziału a, b i wyświetla wszystkie kolejne liczby nieparzyste zawarte w tym przedziale.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program spodziewa się w pierwszym wierszu liczb a i b.

Lazarus Code::Blocks Free Basic

// Liczby nieparzyste
// Data : 11.03.2008
// (C)2012 mgr Jerzy Wałaszek
// Liczby nieparzyste //----------------------------
// Data : 11.03.2008 ' Liczby nieparzyste
// (C)2012 mgr Jerzy Wałaszek #include <iostream> ' Data : 11.03.2008
//---------------------------- ' (C)2012 mgr Jerzy Wałaszek
using namespace std; '----------------------------
program prg;
int main() Dim As Integer a,b,i
var a,b,i : longint; {
int a,b,i; Input a, b
begin i = a
readln(a,b); cin >> a >> b; If a Mod 2 = 0 Then i += 1
i := a; i = a; While i <= b
if(a mod 2 = 0) then inc(i); if(a % 2 == 0) i++; Print i; " ";
while(i <= b) do while(i <= b) i += 2
begin { Wend
write(i,' '); cout << i << " "; Print
inc(i,2); i += 2; End
end; }
writeln; cout << endl;
end. return 0;
}

Wynik
-10 10
-9 -7 -5 -3 -1 1 3 5 7 9

Liczby nieparzyste
(C)2012 mgr Jerzy Wałaszek
a= -10 , b = 10

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0003.php 18 / 519
Algorytmy i Struktury Danych - Liczby parzyste i nieparzyste 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0003.php 19 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby podzielne lub niepodzielne przez zadane podzielniki


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Problem 1
Liczby parzyste i nieparzyste Rozwiązanie alternatywne
Liczby podzielne lub niepodzielne przez zadane podzielniki Problem 2
Ciągi arytmetyczne Problem 3
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem nr 1
W przedziale <a,b> liczb całkowitych wyszukać wszystkie liczby podzielne przez jedną z liczb z zadanego zbioru
P.

W przypadku ogólnym stosujemy podejście pierwsze, czyli generujemy wszystkie kolejne liczby z przedziału <a,b> i
sprawdzamy, czy dzielą się bez reszty przez jedną z liczb z zadanego zbioru. Jeśli tak, wyprowadzamy je na wyjście.

Algorytm wyznaczania liczb podzielnych przez zadane czynniki


Wejście
a– początek przedziału, a Z
b– koniec przedziału, b Z, a < b
n– liczba podzielników, n N
P– tablica, której kolejne elementy są podzielnikami. Elementy Z. Numeracja elementów od zera.
Wyjście:
Kolejne liczby z przedziału <a,b> podzielne przez jeden z podzielników w P
Zmienne pomocnicze
i – przebiega przez kolejne liczby w przedziale <a,b>, i Z
j – przebiega przez numery kolejnych podzielników w P, j N
Lista kroków:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 20 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

Dla i = a,a+1,...,b wykonuj


K01: ; przechodzimy przez kolejne liczby z przedziału <a,b>
K02...K03
; sprawdzamy, czy liczba i dzieli się przez jeden z
K02: Dla j = 0,1,...,n-1 wykonuj K03 podzielników
K03: Jeśli i mod P[j] = 0, to: ; z tablicy P[]. Jeśli tak, wyprowadzamy i, przerywamy pętlę
pisz i K02
następny obieg pętli K01
K04: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program spodziewa się w pierwszym wierszu liczb a i b. W drugim wierszu należy podać n = 1...1000, a następnie w
n wierszach kolejne podzielniki (nie muszą być uporządkowane).
Dane przykładowe (przekopiuj do schowka i wklej do okna konsoli):

-100 100
3
5
12
17
Lazarus

// Liczby podzielne
// Data : 11.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
const MAXP = 1000;
var a,b,i,j,n : longint;
P : array[0..MAXP-1] of longint;
begin
readln(a,b);
readln(n);
for i := 0 to n - 1 do
readln(P[i]);
for i := a to b do
for j := 0 to n - 1 do
if i mod P[j] = 0 then
begin
write(i,' ');
break;
end;
writeln;
end.

Code::Blocks

// Liczby podzielne
// Data : 11.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
const int MAXP = 1000;
int main()
{
int a,b,i,j,n,P[MAXP];
cin >> a >> b >> n;
for(i = 0; i < n; i++)
cin >> P[i];
for(i = a; i <= b; i++)
for(j = 0; j < n; j++)
if(i % P[j] == 0)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 21 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

{
cout << i << " ";
break;
}
cout << endl;
return 0;
}

Free Basic

' Liczby podzielne


' Data : 11.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Const MAXP = 1000
Dim As Integer a,b,i,j,n,P(MAXP-1)
Input a,b
Input n
For i = 0 To n - 1
Input P(i)
Next
For i = a To b
For j = 0 To n - 1
If i Mod P(j) = 0 Then
Print i;" ";
Exit For
End If
Next
Next
Print
End

Wynik
-100 100
3
5
12
17
-100 -96 -95 -90 -85 -84 -80 -75 -72 -70 -68 -65 -60 -55 -51 -50 -48 -45 -40 -36
-35 -34 -30 -25 -24 -20 -17 -15 -12 -10 -5 0 5 10 12 15 17 20 24 25 30 34 35 36
40 45 48 50 51 55 60 65 68 70 72 75 80 84 85 90 95 96 100

Liczby podzielne
(C)2012 mgr Jerzy Wałaszek
a= -100 , b = 100 dzielniki = 5 12 17

Wykonaj

...

Rozwiązanie alternatywne
Problem można rozwiązać podejściem drugim, tzn. wyznaczając kolejne liczby podzielne przez jeden z podzielników z tablicy P[].
Wystarczy zauważyć, iż liczby te będą wielokrotnościami swoich podzielników. Zatem dla każdego podzielnika wyznaczamy
najmniejszą wielokrotność zawartą w przedziale <a,b>. Następnie z tych wielokrotności wybieramy najmniejszą i przesyłamy na
wyjście. Jeśli w trakcie szukania najmniejszej liczby trafimy na taką, która została już wysłana na wyjście, to zamieniamy ją na jej
następną wielokrotność. Operację kontynuujemy dotąd, aż najmniejsza liczba przekroczy kraniec b przedziału. Poniżej
przedstawiamy szczegółowy algorytm tego rozwiązania.

Algorytm wyznaczania liczb podzielnych przez zadane czynniki


Wejście
a– początek przedziału, a Z
b– koniec przedziału, b Z
n– liczba podzielników, n N
P– tablica, której kolejne elementy są podzielnikami. Elementy Z. Numeracja elementów od zera.
Wyjście:
Kolejne liczby z przedziału <a,b> podzielne przez jeden z podzielników w P
Zmienne pomocnicze

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 22 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

i – przebiega przez indeksy podzielników w P[]. i Z


s – liczba przesłana na wyjście, s Z
minv – minimalna wartość wielokrotności podzielników z P[]. minv Z
V – tablica wielokrotności podzielników. Elementy Z. Numeracja elementów od zera zgodna z
numeracją P.
MAXZ – największa wartość całkowita
Lista kroków:
Dla i = 0,1,...,n- 1 wykonuj kroki
K01: K02...K03
K02: Jeśli P[i] < 0, to P[i] ← -P[i] ; ujemne podzielniki zamieniamy na dodatnie
K03: Jeśli a < 0, to V[i] ← (a div P[i]) × ; wyznaczamy pierwszą w <a,b> wielokrotność
P[i] ; podzielnika P[i]
inaczej V[i] ← ((a + P[i] - 1)
div P[i]) × P[i]
K04: s ← a - 1 ; s zapamiętuje wysłaną na wyjście liczbę. Nadajemy mu
wartość niemożliwą
K05: minv ← MAXZ ; wśród wielokrotności V szukamy najmniejszej
K06: Dla i = 0,1,...,n- 1 wykonuj kroki
K07...K08
K07: Jeśli V[i] = s, to V[i] ← V[i] + ; jeśli trafimy na wysłaną już wielokrotność, to obliczamy
P[i] kolejną
K08: Jeśli V[i] < minv, to minv ← V[i] ; zapamiętujemy najmniejszą z wielokrotności
K09: s ← minv; ; zapamiętujemy w s znalezioną najmniejszą z
wielokrotności
K10: Jeśli s > b, to zakończ ; jeśli jest ona poza przedziałem <a,b>, to kończymy
K11: Pisz s ; inaczej wyprowadzamy s i kontynuujemy pętlę
K12: Idź do K05

Algorytm wygląda na dłuższy niż w pierwszej wersji. Jednakże działa on efektywniej, ponieważ w pętli głównej nie
wykonujemy dzieleń, a jedynie dodawania oraz pętla ta nie wykonuje pustych przebiegów – przy każdym obiegu
wyprowadzana jest jedna liczba. Korzyści ujawnią się szczególnie przy dużym przedziale <a,b> oraz znacznej
różnicy pomiędzy dzielnikami.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program spodziewa się w pierwszym wierszu liczb a i b. W drugim wierszu należy podać n = 1...1000, a następnie w
n wierszach kolejne podzielniki (nie muszą być uporządkowane).
Dane przykładowe (przekopiuj do schowka i wklej do okna konsoli):
-100 100
3
5
12
17
Lazarus

// Liczby podzielne - wersja 2


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
const MAXZ = 2147483647;
const MAXP = 1000;
var a,b,i,n,s,minv : longint;
P,V : array[0..MAXP-1] of longint;
begin
readln(a,b);
readln(n);
for i := 0 to n-1 do
begin
readln(P[i]);
if P[i] < 0 then P[i] := -P[i];
if a < 0 then

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 23 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

V[i] := (a div P[i]) * P[i]


else
V[i] := ((a + P[i] - 1) div P[i]) * P[i];
end;
s := a - 1;
while true do
begin
minv := MAXZ;
for i := 0 to n-1 do
begin
if V[i] = s then inc(V[i],P[i]);
if V[i] < minv then minv := V[i];
end;
s := minv;
if s > b then break;
write(s,' ');
end;
writeln;
end.

Code::Blocks

// Liczby podzielne - wersja 2


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
const int MAXZ = 2147483647;
const int MAXP = 1000;
int main()
{
int a,b,i,n,s,minv;
int P[MAXP],V[MAXP];
cin >> a >> b >> n;
for(i = 0; i < n; i++)
{
cin >> P[i];
if(P[i] < 0) P[i] = -P[i];
if(a < 0)
V[i] = (a / P[i]) * P[i];
else
V[i] = ((a + P[i] - 1) / P[i]) * P[i];
}
s = a - 1;
while(true)
{
minv = MAXZ;
for(i = 0; i < n; i++)
{
if(V[i] == s) V[i] += P[i];
if(V[i] < minv) minv = V[i];
}
s = minv;
if(s > b) break;
cout << s << " ";
}
cout << endl;
return 0;
}

Free Basic

' Liczby podzielne - wersja 2


' Data : 12.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Const MAXZ = 2147483647
Const MAXP = 1000
Dim As Integer a,b,i,n,s,minv
Dim As Integer P(MAXP),V(MAXP)
Input a,b
Input n
For i = 0 To n-1
Input P(i)
If P(i) < 0 Then P(i) = -P(i)
If a < 0 Then
V(i) = (a \ P(i)) * P(i)
Else

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 24 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

V(i) = ((a + P(i) - 1) \ P(i)) * P(i)


End If
Next
s = a - 1
Do
minv = MAXZ
For i = 0 To n - 1
If V(i) = s Then V(i) += P(i)
If V(i) < minv Then minv = V(i)
Next
s = minv
If s > b Then Exit Do
Print s;" ";
Loop
Print
End

Problem nr 2
W przedziale <a,b> liczb całkowitych wyszukać wszystkie liczby podzielne przez każdą z liczb z zadanego zbioru
P liczb względnie pierwszych

Jeśli liczba ma być podzielna przez każdy z podzielników, to również musi być podzielna przez ich iloczyn. Zatem w podejściu 1
obliczamy iloczyn kolejnych podzielników, a następnie w pętli przebiegającej przez kolejne liczby z przedziału <a,b> sprawdzamy,
czy są one podzielne przez ten iloczyn. Jeśli tak, to wyprowadzamy je na wyjście.
W podejściu drugim obliczamy najmniejszą wielokrotność iloczynu podzielników, która wpada w przedział <a,b> (patrz powyżej –
rozwiązanie alternatywne). Następnie w pętli dopóki wielokrotność jest mniejsza lub równa b, wyprowadzamy ją na wyjście i
obliczamy następną dodając iloczyn podzielników.
W ramach ćwiczeń proponuję czytelnikom samodzielne utworzenie algorytmów oraz na ich podstawie odpowiednich programów.

Problem nr 3
W przedziale <a,b> liczb całkowitych wyszukać wszystkie liczby niepodzielne przez żadną z liczb z zadanego
zbioru P.

Stosując pierwsze podejście przebiegamy przez kolejne liczby w przedziale <a,b>. Każdą liczbę sprawdzamy na podzielność
przez podzielniki z P. Jeśli któryś z nich dzieli liczbę, to przechodzimy do następnej liczby w <a,b>. Jeśli żaden nie dzieli liczby,
liczbę wyprowadzamy na wyjście.

Algorytm wyszukiwania liczb niepodzielnych przez zadane liczby


Wejście
a– początek przedziału, a Z
b– koniec przedziału, b Z
n– liczba podzielników, n N
P– tablica, której kolejne elementy są podzielnikami. Elementy Z. Numeracja elementów od zera.
Wyjście:
Kolejne liczby z przedziału <a,b> niepodzielne przez żaden z podzielników w P
Zmienne pomocnicze
i – przebiega przez kolejne liczby w <a,b>. i Z
j – przebiega przez indeksy podzielników w P. j N
Lista kroków:
K01: Dla i = a,a+1,...,b wykonuj kroki K02...K04 ; pętla przebiegająca przez kolejne liczby z <a,b>
K02: Dla j = 0,1,...,n-1 wykonuj krok K03 ; pętla sprawdzająca podzielność przez dzielniki z
P
K03: Jeśli i mod P[j] = 0, to następny ; jeśli jakiś dzielnik dzieli i, przechodzimy do
obieg pętli K01 następnej liczby
K04: Pisz i ; jeśli żaden dzielnik nie dzieli i, wyprowadzamy je
K05: Zakończ

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 25 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program spodziewa się w pierwszym wierszu liczb a i b. W drugim wierszu należy podać n = 1...1000, a następnie w
n wierszach kolejne podzielniki (nie muszą być uporządkowane).
Dane przykładowe (przekopiuj do schowka i wklej do okna konsoli):
-100 100
4
2
3
5
7

Lazarus

// Liczby niepodzielne
// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
const MAXP = 1000;
var a,b,i,j,n : longint;
P : array[0..MAXP-1] of longint;
t : boolean;
begin
readln(a,b);
readln(n);
for i := 0 to n - 1 do readln(P[i]);
for i := a to b do
begin
t := true;
for j := 0 to n - 1 do
if i mod P[j] = 0 then
begin
t := false;
break;
end;
if t then write(i,' ');
end;
writeln;
end.

Code::Blocks

// Liczby niepodzielne
// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
const int MAXP = 1000;
int main()
{
int a,b,i,j,n,P[MAXP];
bool t;
cin >> a >> b >> n;
for(i = 0; i < n; i++) cin >> P[i];
for(i = a; i <= b; i++)
{
t = true;
for(j = 0; j < n; j++)
if(i % P[j] == 0)
{
t = false;
break;
}
if(t) cout << i << " ";

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 26 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

}
cout << endl;
return 0;
}

Free Basic

' Liczby niepodzielne


' Data : 12.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Const MAXP = 1000
Dim As Integer a,b,i,j,n,P(MAXP),t
Input a,b
Input n
For i = 0 To n - 1: Input P(i): Next
For i = a To b
t = 1
For j = 0 To n - 1
If i Mod P(j) = 0 Then
t = 0
Exit For
End If
Next
If t Then Print i ;" ";
Next
Print
End

Wynik
-100 100
4
2
3
5
7
-97 -89 -83 -79 -73 -71 -67 -61 -59 -53 -47 -43 -41 -37 -31 -29 -23 -19 -17 -13
-11 -1 1 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Liczby niepodzielne
(C)2012 mgr Jerzy Wałaszek
a= -100 , b = 100 dzielniki = 2357

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 27 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03

szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0004.php 28 / 519
Algorytmy i Struktury Danych - Ciągi arytmetyczne i geometryczne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Ciągi arytmetyczne
Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Wygenerować n kolejnych wyrazów ciągu arytmetycznego.

Wzór na i-ty wyraz ciągu arytmetycznego jest następujący:

ai = a1 + (i - 1) × d
a1 – pierwszy wyraz ciągu
d – stały przyrost
i – numer wyrazu do wygenerowania

Algorytm wyznaczania n kolejnych wyrazów ciągu arytmetycznego


Wejście
n – liczba wyrazów ciągu do wygenerowania, n N
a – pierwszy wyraz ciągu, a1 R
d – przyrost, d R

Wyjście:
Kolejne wyrazy ciągu a1 a2 ... an

Zmienne pomocnicze

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 29 / 519
Algorytmy i Struktury Danych - Ciągi arytmetyczne i geometryczne 2014-10-03

i – przebiega przez kolejne indeksy w przedziale <1, n>, i N


Lista kroków:
K01: Dla i = 1,2,...,n wykonu K02
K02: Pisz a + (i - 1) × d
K03: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje kolejno trzy liczby: n, a1 or az d. Następnie wyświetla n kolejnych wyrazów ciągu
arytmetycznego.

Lazarus

// Ciąg arytmetyczny
// Data : 6.02.2011
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var n,i : integer;
a,d : double;
begin
readln(n,a,d);
for i := 1 to n do
write(a + (i - 1) * d:9:3,' ');
writeln;
end.

Code::Blocks

// Ciąg arytmetyczny
// Data : 6.02.2011
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int n,i;
double a,d;
cout << fixed
<< setprecision(3);
cin >> n >> a >> d;
for(i = 1; i <= n; i++)
cout << setw(9)
<< a + (i - 1) * d
<< " ";
cout << endl;
return 0;
}

Free Basic

' Ciąg arytmetyczny


' Data : 6.02.2011
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Integer n,i

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 30 / 519
Algorytmy i Struktury Danych - Ciągi arytmetyczne i geometryczne 2014-10-03

Dim As Double a,d


Input n,a,d
For i = 1 To n
Print Using " #####.###";a + (i - 1) * d;
Next
Print
End

Wynik
32 1 0.57
1.000 1.570 2.140 2.710 3.280 3.850 4.420 4.990
5.560 6.130 6.700 7.270 7.840 8.410 8.980 9.550
10.120 10.690 11.260 11.830 12.400 12.970 13.540 14.110
14.680 15.250 15.820 16.390 16.960 17.530 18.100 18.670

Rozwiązanie dla ciągu arytmetycznego o wyrazach całkowitych.


Jeśli generowany ciąg arytmetyczny ma wyrazy całkowite, to kolejne wyrazy możemy tworzyć rekurencyjnie, tzn. mając wyraz ai,
następny otrzymujemy dodając do poprzedniego przyrost d. Korzyścią jest pozbycie się operacji mnożenia, zatem wyrazy będą
generowane nieco szybciej. Metoda ta nie nadaje się dla ciągów rzeczywistych, ponieważ w wyniku dodawania mogą kumulować
się błędy zaokrągleń.

Algorytm wyznaczania n kolejnych wyrazów całkowitego ciągu arytmetycznego


Wejście
n – liczba wyrazów ciągu do wygenerowania, n N
a – pierwszy wyraz ciągu, a1 Z
d – przyrost, d Z

Wyjście:
Kolejne wyrazy ciągu a1 a2 ... an

Zmienne pomocnicze
i – przebiega przez kolejne indeksy w przedziale <1, n>, i N
Lista kroków:
K01: Dla i = 1,2,...,n wykonu K02,K03
K02: Pisz a ; wypisujemy bieżący wyraz
K03: a ← a + d ; wyliczamy wyraz następny
K04: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje kolejno trzy liczby: n, a1 or az d. Następnie wyświetla n kolejnych wyrazów ciągu
arytmetycznego.

Lazarus

// Całkowity ciąg arytmetyczny


// Data : 6.02.2011
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var n,i,a,d : integer;
begin
readln(n,a,d);
for i := 1 to n do
begin
write(a:7,' ');
inc(a,d);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 31 / 519
Algorytmy i Struktury Danych - Ciągi arytmetyczne i geometryczne 2014-10-03

end;
writeln;
end.

Code::Blocks

// Całkowity ciąg arytmetyczny


// Data : 6.02.2011
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int n,i,a,d;
cin >> n >> a >> d;
for(i = 1; i <= n; i++)
{
cout << setw(7) << a << " ";
a += d;
}
cout << endl;
return 0;
}

Free Basic

' Całkowity ciąg arytmetyczny


' Data : 6.02.2011
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Integer n,i,a,d
Input n,a,d
For i = 1 To n
Print Using " #######";a;
a += d
Next
Print
End

Wynik
20 2000 123
2000 2123 2246 2369 2492 2615 2738 2861 2984 3107
3230 3353 3476 3599 3722 3845 3968 4091 4214 4337

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 32 / 519
Algorytmy i Struktury Danych - Ciągi arytmetyczne i geometryczne 2014-10-03

Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 33 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

NWD - algorytm Euklidesa


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki Rozwiązanie 3
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Dla danych dwóch liczb naturalnych a i b znaleźć największą liczbę naturalną c, która dzieli bez reszty liczbę a i
dzieli bez reszty liczbę b.

Liczba c o powyższej własności nosi nazwę NWD – największego wspólnego dzielnika a i b (ang. GCD – greatest common
divisor). NWD znajdujemy za pomocą znanego algorytmu Euklidesa, będącego jednym z najstarszych algorytmów, ponieważ
pojawił się on w dziele Elementy napisanym przez Euklidesa około 300 p.n.e. Właściwie Euklides nie podał algorytmu dla liczb,
lecz dla dwóch odcinków. Chodziło w nim o znalezienie wspólnej miary (czyli odcinka jednostkowego), która mogłaby posłużyć do
zmierzenia obu danych odcinków – wspólna miara odkłada się w każdym z odcinków całkowitą liczbę razy.

Rozwiązanie 1
Euklides wykorzystał prosty fakt, iż NWD liczb a i b dzieli również ich różnicę. Zatem od większej liczby odejmujemy w pętli
mniejszą dotąd, aż obie liczby się zrównają. Wynik to NWD dwóch wyjściowych liczb.

Algorytm Euklidesa
Wejście
a, b – liczby naturalne, których NWD poszukujemy, a, b N
Wyjście:
NWD liczb a i b

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 34 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

Lista kroków:
K01: Dopóki a ≠ b wykonuj krok K02
K02: Jeśli a < b, to b ← b - a ; od większej liczby odejmujemy mniejszą aż się zrównają
inaczej a←a-b
K03: Pisz a ; wtedy dowolna z nich jest NWD
K04: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza dwie liczby a i b, a następnie wypisuje w następnym wierszu ich NWD.
Żadna z liczb a i b nie może wynosić 0 – wtedy różnica nie zmienia większej z nich i program działa w
nieskończoność. Niedogodność tę da się prosto zlikwidować – proponuję to jako ćwiczenie dla czytelników.

Lazarus

// NWD - wersja z odejmowaniem


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var a,b : longword;
begin
readln(a,b);
while a <> b do
if a < b then b := b - a
else a := a - b;
writeln(a);
end.

Code::Blocks

// NWD - wersja z odejmowaniem


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int a,b;
cin >> a >> b;
while(a != b)
if(a < b) b -= a; else a -= b;
cout << a << endl;
return 0;
}

Free Basic

' NWD - wersja z odejmowaniem


' Data : 12.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger a,b
Input a,b
While a <> b
If a < b Then b -= a: Else: a -= b
Wend
Print a
End

Wynik

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 35 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

18 24
6

Algorytm Euklidesa
(C)2012 mgr Jerzy Wałaszek
a= 18 , b = 24

Wykonaj

...

Rozwiązanie 2
Pierwsze rozwiązanie problemu znajdowania NWD jest złe z punktu widzenia efektywności. Wyobraźmy sobie, iż a jest równe 4
miliardy, a b jest równe 2. Pętla odejmująca będzie wykonywana dotąd, aż zmienna a zrówna się ze zmienną b, czyli w tym
przypadku 2 miliardy razy – trochę dużo. Tymczasem można wykorzystać operację reszty z dzielenia. Mniejszą liczbę można
odjąć od większej liczby tyle razy, ile wynosi iloraz całkowity tych liczb. Po odejmowaniu pozostaje reszta z dzielenia – a Euklides
właśnie zauważył, iż NWD dzieli również różnicę danych liczb, czyli:

NWD(a,b) = NWD(a mod b,b)

Ponieważ reszta zawsze jest mniejsza od dzielnika, wymieniamy a z b, a b z a mod b. Jeśli otrzymamy wynik b = 0, to w a jest
ostatni dzielnik dzielący bez reszty różnicę.

Algorytm Euklidesa
Wejście
a, b – liczby naturalne, których NWD poszukujemy, a, b N
Wyjście:
NWD liczb a i b
Zmienne pomocnicze
t – tymczasowo przechowuje dzielnik, t N
Lista kroków:
Dopóki b ≠ 0 wykonuj kroki
K01:
K02...K04
K02: t ← b ; zapamiętujemy dzielnik
; wyznaczamy resztę z dzielenia, która staje się
K03: b ← a mod b dzielnikiem
K04: a ← t ; poprzedni dzielnik staje teraz się dzielną
K05: Pisz a ; NWD jest ostatnią dzielną
K06: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza dwie liczby a i b, a następnie wypisuje w następnym wierszu ich NWD.

Lazarus

// NWD - wersja z resztą z dzielenia


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var a,b,t : longword;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 36 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

begin
readln(a,b);
while b <> 0 do
begin
t := b;
b := a mod b;
a := t;
end;
writeln(a);
end.

Code::Blocks

// NWD - wersja z resztą z dzielenia


// Data : 12.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int a,b,t;
cin >> a >> b;
while(b)
{
t = b;
b = a % b;
a = t;
}
cout << a << endl;
return 0;
}

Free Basic

' NWD - wersja z resztą z dzielenia


' Data : 12.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger a,b,t
Input a,b
While b
t = b
b = a Mod b
a = t
Wend
Print a
End

Rozwiązanie 3
Istnieje algorytm znajdowania NWD, w którym nie wykonuje się dzieleń – są one kłopotliwe dla małych procesorów, np. dla
kontrolerów jednoukładowych, i zajmują stosunkowo dużo czasu procesora. Algorytm ten wykorzystuje fakt, iż wszystkie liczby są
przechowywane w komputerze w postaci ciągu bitów. Operacje dzielenia z resztą zastępuje się przesunięciami bitów, które są
proste w realizacji i wykonywane szybko, nawet na najprostszym sprzęcie komputerowym. W efekcie otrzymujemy około 60%
przyrost szybkości wyznaczania NWD w stosunku do standardowego algorytmu Euklidesa. Opisywany algorytm nosi nazwę
binarnego algorytmu NWD (ang. binary GCD algorithm).
Algorytm redukuje problem znajdowania NWD przez stosowanie poniższych równoważności:

1. NWD(0, b) = b, ponieważ każda liczba naturalna dzieli zero, a b jest największą liczbą dzielącą b. Podobnie
NWD(a, 0) = a. Natomiast NWD(0, 0) nie jest zdefiniowane.
2. Jeśli liczby a i b są parzyste, to NWD(a, b) = 2NWD(a/2, b/2), ponieważ obie posiadają wspólny podzielnik 2.
3. Jeśli liczba a jest parzysta a b jest nieparzysta, to NWD(a, b) = NWD(a/2, b), ponieważ 2 nie jest wspólnym
podzielnikiem i można go pominąć. Podobnie jest w przypadku odwrotnym, gdy a jest nieparzyste a b jest
parzyste, wtedy NWD(a, b) = NWD(a, b/2).
4. Jeśli obie liczby a i b są nieparzyste, a a ≥ b, to NWD(a, b) = NWD((a−b)/2, b) , inaczej jeśli obie są
nieparzyste i a < b, to NWD(a, b) = NWD((b-a)/2, a). Takie same operacje wykonuje w pętli podstawowy
algorytm Euklidesa – od większej liczby odejmuje mniejszą. Podzielenie różnicy przez 2 daje zawsze liczbę
całkowitą, ponieważ odejmowane są dwie liczby nieparzyste.
5. Kroki 3 i 4 należy powtarzać aż do otrzymania b = 0. Wtedy NWD jest równy 2ka, gdzie k jest liczbą

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 37 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

wspólnych czynników 2 wyeliminowanych w kroku 2. Mnożenie 2k wykonujemy przez przesunięcie bitów


zmiennej a o k pozycji w lewo.

Algorytm Euklidesa
Wejście
a, b – liczby naturalne, których NWD poszukujemy, a, b Z
Wyjście:
NWD liczb a i b
Zmienne pomocnicze
k – przechowuje liczbę wspólnych podzielników 2, k Z
r – wykorzystywane do przechowywania różnicy a i b, r Z
Lista kroków:
K01: Jeśli a = 0, to pisz b i zakończ ; NWD(0,b) = b
K02: Jeśli b = 0, to pisz a i zakończ ; NWD(a,0) = a
K03: k ← 0 ; inicjujemy liczbę wspólnych podzielników 2
Dopóki a i b parzyste wykonuj kroki ; usuwamy z a i b wspólne czynniki 2, zapamiętując ich
K04: K05...K07 liczbę w k
K05: a ← a shr 1 ; przesuwamy bity a o 1 w prawo
K06: b ← b shr 1 ; przesuwamy bity b o 1 w prawo
K07: k ← k + 1
K08: Jeśli a = 0, to a ← b i idź do K18 ; NWD(0,b) = b
Dopóki a parzyste wykonuj a ← a shr
K09: 1 ; eliminujemy podzielniki 2 z a
Dopóki b parzyste wykonuj b ← b shr ; eliminujemy podzielniki 2 z b, teraz a i b są
K10: 1 nieparzyste
K11: Jeśli a ≥ b, to idź do K16
K12: r ← (b - a) shr 1 ; NWD(a,b) = NWD((b-a)/2,a)
K13: b ← a ; zamieniamy b z a
K14: a ← r ; a z różnicą r
K15: Idź do K17
K16: a ← (a - b) shr 1 ; NWD(a,b) = NWD((a-b)/2,b)
K17: Jeśli b ≠ 0, to idź do K08
; przesuwamy bity a o k pozycji w lewo → mnożenie
K18: Jeśli k > 0, to a ← a shl k
przez 2k
K19: Pisz a
K20: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza dwie liczby a i b, a następnie wypisuje w następnym wierszu ich NWD.

Lazarus

// NWD - wersja binarna z przesuwem bitów


// Data : 13.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var a,b,k,r : longword;
begin
readln(a,b);
if (a = 0) or (b = 0) then writeln(a or b)
else
begin
k := 0;
while ((a or b) and 1) = 0 do
begin

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 38 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

a := a shr 1;
b := b shr 1;
inc(k);
end;
repeat
if a = 0 then
begin
a := b;
break;
end;
while (a and 1) = 0 do a := a shr 1;
while (b and 1) = 0 do b := b shr 1;
if a < b then
begin
r := (b - a) shr 1;
b := a;
a := r;
end
else a := (a - b) shr 1;
until b = 0;
if k > 0 then a := a shl k;
writeln(a);
end;
end.

Code::Blocks

// NWD - wersja binarna z przesuwem bitów


// Data : 14.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int a,b,k,r;
cin >> a >> b;
if(!a || !b) cout << (a | b) << endl;
else
{
for(k = 0; !((a | b) & 1); k++)
{
a >>= 1;
b >>= 1;
}
do
{
if(!a)
{
a = b; break;
}
while(!(a & 1)) a >>= 1;
while(!(b & 1)) b >>= 1;
if(a < b)
{
r = (b - a) >> 1;
b = a;
a = r;
}
else a = (a - b) >> 1;
} while(b);
if(k) a <<= k;
cout << a << endl;
}
return 0;
}

Free Basic

' NWD - wersja binarna z przesuwem bitów


' Data : 13.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger a,b,k,r
Input a,b
If (a = 0) Or (b = 0) Then
Print a Or b
Else
k = 0
While ((a Or b) And 1) = 0
a = a Shr 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 39 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03

b = b Shr 1
k += 1
Wend
Do
If a = 0 Then
a = b
Exit Do
End If
While (a And 1) = 0: a = a Shr 1: Wend
While (b And 1) = 0: b = b Shr 1: Wend
If a < b Then
r = (b - a) Shr 1
b = a
a = r
Else
a = (a - b) Shr 1
End If
Loop Until b = 0
If k > 0 Then a = a Shl k
Print a
End If
End

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0006.php 40 / 519
Algorytmy i Struktury Danych - Liczby względnie pierwsze 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby względnie pierwsze


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
W przedziale <a,b> liczb naturalnych wyszukać wszystkie liczby względnie pierwsze (ang. relatively prime integers)
z zadaną liczbą p.

Liczba naturalna m jest względnie pierwsza (ang. coprime) z liczbą naturalną n wtedy i tylko wtedy, gdy NWD(m,n) = 1.
Definicja ta oznacza, iż liczby n i m nie posiadają wspólnych podzielników za wyjątkiem 1.

Rozwiązanie
Przechodzimy przez kolejne liczby w przedziale <a,b>. Dla każdej liczby obliczamy algorytmem Euklidesa jej NWD z liczbą p.
Jeśli wynikiem będzie 1, to obie liczby są względnie pierwsze, zatem wyprowadzamy liczbę z przedziału na wyjście.

Algorytm wyznaczania liczb względnie pierwszych


Wejście
a – początek przedziału, a N
b – koniec przedziału, b N
p – liczba służąca do wyznaczenia w przedziale <a,b> liczb z nią względnie pierwszych, p N

Wyjście:
liczby z przedziału <a,b>, które są względnie pierwsze z liczbą p
Zmienne pomocnicze

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0007.php 41 / 519
Algorytmy i Struktury Danych - Liczby względnie pierwsze 2014-10-03

i – przebiega przez kolejne liczby w przedziale <a,b>. i N


t – tymczasowo przechowuje dzielnik w algorytmie Euklidesa, t N
ax – zmienna dla algorytmu Euklidesa. ax N
bx – zmienna dla algorytmu Euklidesa. bx N
Lista kroków:

K01: Dla i = a,a+1,...,b wykonuj kroki ; przechodzimy przez kolejne liczby z przedziału
K02...K08 <a,b>
K02: ax ← i ; zmienne dla algorytmu Euklidesa
K03: bx ← p
K04: Dopóki bx ≠ 0 wykonuj kroki ; wyznaczamy NWD(i,p)
K05...K07
K05: t ← bx
K06: bx ← ax mod bx
K07: ax ← t
K08: Jeśli ax = 1, to pisz i ; NWD(i,p) = 1, zatem i jest względnie pierwsze z p
K09: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza trzy liczby a, b i p a następnie wypisuje wszystkie liczby w przedziale
<a,b> względnie pierwsze z p.

Lazarus

// Liczby względnie pierwsze


// Data : 14.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var a,b,p,ax,bx,i,t : longword;
begin
readln(a,b,p);
for i := a to b do
begin
ax := i; bx := p;
while bx <> 0 do
begin
t := bx;
bx := ax mod bx;
ax := t;
end;
if ax = 1 then write(i,' ');
end;
writeln;
end.

Code::Blocks

// Liczby względnie pierwsze


// Data : 14.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int a,b,p,ax,bx,i,t;
cin >> a >> b >> p;
for(i = a; i <= b; i++)
{
ax = i; bx = p;
while(bx)
{
t = bx;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0007.php 42 / 519
Algorytmy i Struktury Danych - Liczby względnie pierwsze 2014-10-03

bx = ax % bx;
ax = t;
}
if(ax == 1) cout << i << " ";
}
cout << endl;
return 0;
}

Free Basic

' Liczby względnie pierwsze


' Data : 14.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger a,b,p,ax,bx,i,t
Input a,b,p
For i = a To b
ax = i: bx = p
While bx
t = bx
bx = ax Mod bx
ax = t
Wend
If ax = 1 Then Print i;" ";
Next
Print
End

Wynik
1 100 60
1 7 11 13 17 19 23 29 31 37 41 43 47 49 53 59 61 67 71 73 77 79 83 89 91 97

Liczby względnie pierwsze


(C)2012 mgr Jerzy Wałaszek
a= 1 , b= 100 , p = 60

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0007.php 43 / 519
Algorytmy i Struktury Danych - Liczby względnie pierwsze 2014-10-03

I Liceum Ogólnokształcące
GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0007.php 44 / 519
Algorytmy i Struktury Danych - NWW - najmniejsza wspólna wielokrotność 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Najmniejsza wspólna wielokrotność


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Dla danych liczb naturalnych a i b znaleźć najmniejszą liczbę naturalną c, która jest podzielna bez reszty przez a i
przez b.

Liczba naturalna c o takich własnościach nosi nazwę NWW – najmniejszej wspólnej wielokrotności liczb a i b (ang. the least
common multiple of a and b). Sposób obliczania NWW jest bardzo prosty:

a×b
NWW(a,b) =
NWD(a,b)

Jeśli liczby a i b są względnie pierwsze, to NWD(a,b) = 1. Wtedy NWW(a,b) = a × b.

Algorytm wyznaczania najmniejszej wspólnej wielokrotności


Wejście
a,b – liczby, których NWW poszukujemy, a,b N
Wyjście:
NWW – najmniejsza wspólna wielokrotność liczb a i b.
Zmienne pomocnicze
ab – zapamiętuje iloczyn a i b. ab N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0008.php 45 / 519
Algorytmy i Struktury Danych - NWW - najmniejsza wspólna wielokrotność 2014-10-03

t – tymczasowo przechowuje dzielnik w algorytmie Euklidesa, t N


Lista kroków:
K01: ab ← a × b ; zapamiętujemy iloczyn a i b
K02: Dopóki b ≠ 0 wykonuj kroki K03...K05 ; algorytmem Euklidesa znajdujemy NWD(a,b)
K03: t ← b
K04: b ← a mod b
K05: a ← t
K06: ab ← ab div a ; obliczamy NWW
K07: Pisz ab
K08: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczby a i b. W następnym wierszu wypisuje NWW(a,b). W programie
zastosowano zmienne 64 bitowe.

Lazarus Code::Blocks Free Basic

// NWW // NWW ' NWW


// Data : 2.04.2008 // Data : 2.04.2008 ' Data : 2.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Ulongint a,b,t,ab
var a,b,t,ab : int64; using namespace std; Input a,b
ab = a * b
begin int main() While b
readln(a,b); { t = b
ab := a * b; unsigned long long a,b,t,ab; b = a Mod b
while b <> 0 do a = t
begin cin >> a >> b; Wend
t := b; ab = a * b; ab = ab \ a
b := a mod b; while(b) Print ab
a := t; { Print
end; t = b; End
ab := ab div a; b = a % b;
writeln(ab); a = t;
writeln; }
end. ab /= a;
cout << ab << endl << endl;
return 0;
}

Wynik
9 6
18

Najmniejsza Wspólna Wielokrotność


(C)2012 mgr Jerzy Wałaszek
a= 9 , b= 6

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0008.php 46 / 519
Algorytmy i Struktury Danych - NWW - najmniejsza wspólna wielokrotność 2014-10-03

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0008.php 47 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Odwrotność modulo – rozszerzony algorytm Euklidesa


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Dla danych liczb naturalnych a i b znaleźć taką liczbę naturalną x, aby a × x mod b = 1 lub stwierdzić, iż liczba x
nie istnieje.

Liczbę x nazywamy odwrotnością modulo b (ang. modular multiplicative inverse) liczby a – przez analogię do zwykłej
odwrotności liczby. Wbrew pozorom problem wcale nie należy do łatwych i może się zdarzyć, iż liczba x nie istnieje (b istnieje na
pewno, jeśli liczby a i b są względnie pierwsze). Odwrotność modulo jest szeroko stosowana we współczesnej kryptografii.

Przykład
Znajdźmy odwrotność modulo 7 liczby 5. NWD(5,7) = 1, zatem odwrotność istnieje. Zgodnie z definicją testujemy
kolejne iloczyny:

5 × 1 mod 7 = 5 mod 7 = 5 – NIE


5 × 2 mod 7 = 10 mod 7 = 3 – NIE
5 × 3 mod 7 = 15 mod 7 = 1 – TAK

Zatem odwrotnością modulo 7 liczby 5 jest liczba 3.

Rozszerzony algorytm Euklidesa


Odwrotność modulo znajdujemy przy pomocy rozszerzonego algorytmu Euklidesa (ang. extended Euclidean algorithm), który
oprócz znajdowania NWD(a,b) znajduje również dwie liczby x i y spełniające tzw. tożsamość Bezouta (ang. Bezout's identity):

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0009.php 48 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03

ax + by = NWD(a,b)

Jeśli liczby a i b są względnie pierwsze, to liczba x jest odwrotnością modulo b liczby a.


Zadanie rozwiązujemy pracując z parą równań zapisanych następująco:

(1) a×u+b ×v=w


(2) a×x+b ×y=z

Zażądajmy, aby zawsze spełniony był warunek:

(3) NWD(a,b) = NWD(w,z)

Na początku zapiszmy te dwa równania w poniższej postaci:

a×1+b ×0=a
a×0+b ×1=b

Wynika z tego, iż współczynniki u, v, w, x, y, i z przyjmują odpowiednio wartości:

u = 1, v = 0, w = a
x = 0, y = 1, z = b

Zwróć uwagę, iż dla tak określonych współczynników są spełnione równania (1) i (2) oraz tożsamościowo warunek (3).
Teraz będziemy powtarzać transformacje równań (1) i (2) w pętli, aż otrzymamy wynik w = 0.
Najpierw sprawdzamy, czy w < z. Jeśli tak, to zamieniamy miejscami równania (1) z (2), tzn. wymieniamy ze sobą współczynniki x
z u, v z y i w z z. Po tej operacji w jest równe lub większe od z. Korzystamy z następującej własności NWD:

Jeśli NWD(a,b) = NWD(w,z), to NWD(a,b) = NWD(w mod z, z)

Z tej samej własności korzysta również podstawowy algorytm Euklidesa przy wyznaczaniu NWD(a,b). Musimy zatem w równaniu
(1) zastąpić w przez w mod z. Aby jednak równanie (1) było wciąż spełnione, pozostałe współczynniki u i v również należy
odpowiednio zmienić. Wyznaczamy zatem całkowity iloraz q = w div z. Następnie równanie (1) zastępujemy różnicą: (1) - q(2):

a × (u - q × x) + b × (v - q × y) = w - q × z

Otrzymujemy zatem modyfikację współczynników w równaniu (1):

u ←u-q× x
v ←v-q× y
w ← w - q × z = w mod z

Po wykonaniu powyższych podstawień wracamy do początku pętli i kontynuujemy ją aż do otrzymania w = 0. Ponieważ w i z są


modyfikowane tak samo jak w podstawowym algorytmie Euklidesa, to gdy w przybierze wartość 0 otrzymamy następującą parę
równań:

(1) a× u+ b × v= w= 0
(2) a × x + b × y = z = NWD(a,b)

Jeśli z = NWD(a,b) = 1, to istnieje odwrotność modulo b z liczby a i jest ona równa x. Jednakże x może przyjąć wartość ujemną.
Wtedy sprowadzamy je do wartości dodatniej dodając b.

Przykład
Obliczyć odwrotność modulo 5 z 2. Czyli a = 2, b = 5
Tworzymy parę równań:

(1) 2×1+5×0=2
(2) 2×0+5×1=5

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0009.php 49 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03

Ponieważ 2 < 5, równania zamieniamy miejscami:

(1) 2×0+5×1=5
(2) 2×1+5×0=2

Obliczamy iloraz q = 5 div 2 = 2


Od równania (1) odejmujemy (2) pomnożone przez q:

(1) 2 × (0 - 2 × 1) + 5 × (1 - 2 × 0) = 5 - 2 × 2
(1) 2 × (-2) + 5 × 1 = 1
(2) 2×1+5×0=2

Ponieważ 1 < 2, równania (1) i (2) zamieniamy miejscami:

(1) 2×1+5×0=2
(2) 2 × (-2) + 5 × 1 = 1

Obliczamy iloraz q = 2 div 1 = 2


Od równania (1) odejmujemy (2) pomnożone przez q:

(1) 2 × (1 - 2 × (-2)) + 5 × (0 - 2 × 1) = 2 - 2 × 1
(1) 2 × 5 + 5 × (- 2) = 0
(2) 2 × (-2) + 5 × 1 = 1

Otrzymaliśmy w = 0, kończymy modyfikowanie równań. Wyniki są następujące:

u = 5, v = -2, w = 0
x = -2, y = 1, z = 1

Ponieważ z = NWD(a,b) = NWD(2,5) = 1, to odwrotność istnieje i jest równa x, które sprowadzamy do liczb
dodatnich dodając b:

x ← x + b = -2 + 5 = 3

Sprawdzamy, czy otrzymany wynik spełnia definicję odwrotności modulo:

a × x mod b = 2 × 3 mod 5 = 6 mod 5 = 1 - zgadza się!

Prosta analiza powyższych obliczeń pokazuje, iż możemy z czystym sumieniem pominąć wyznaczanie współczynników v oraz y
– nie są one wykorzystywane do wyliczania u, w, x i z, które jedynie się tutaj liczą. Pozwala to uprościć algorytm wyznaczania
odwrotności modulo.

Algorytm wyznaczania odwrotności modulo


Wejście
a – liczba, której odwrotność modulo obliczamy, a N
b – moduł odwrotności, b N

Wyjście:
odwrotność modulo b z liczby a lub informacja, iż odwrotność taka nie istnieje
Zmienne pomocnicze
u,w,x,z – współczynniki równań. u,v,w,x,y,z Z
q – całkowity iloraz. q Z

Lista kroków:
K01: u ← 1; w ← a; ; ustalamy wartości początkowe współczynników
x ← 0; z ← b
K02: Dopóki w ≠ 0 wykonuj kroki ; w pętli modyfikujemy współczynniki równań

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0009.php 50 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03

K03...K06
K03: Jeśli w < z, to u ↔ x; w ↔ z ; aby algorytm wyliczał nowe współczynniki, w nie może być
mniejsze od z
; jeśli jest, zamieniamy ze sobą współczynniki równań
K04: q ← w div z ; obliczamy iloraz całkowity
K05: u ← u - q × x ; od równania (1) odejmujemy równanie (2) wymnożone przez q
K06: w ← w - q × z
K07: Jeśli z ≠ 1, to idź do K10 ; dla z różnego od 1 nie istnieje odwrotność modulo
K08: Jeśli x < 0, to x ← x + b ; ujemne x sprowadzamy do wartości dodatnich
K09: Pisz x i zakończ ; x jest poszukiwaną odwrotnością modulo
K10: Pisz brak rozwiązania i zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczbę a oraz moduł b. W następnym wierszu wypisuje odwrotność modulo
b liczby a lub pojedyncze słowo "BRAK".

Lazarus Code::Blocks Free Basic

// Odwrotność modulo // Odwrotność modulo ' Odwrotność modulo


// Data : 15.03.2008 // Data : 15.03.2008 ' Data : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Integer a,b,u,w,x,z,q
var a,b,u,w,x,z,q : longint; using namespace std; Input a,b
u = 1: w = a
begin int main() x = 0: z = b
readln(a,b); { While w
u := 1; w := a; int a,b,u,w,x,z,q; If w < z Then
x := 0; z := b; q = u: u = x: x = q
while w <> 0 do cin >> a >> b; q = w: w = z: z = q
begin u = 1; w = a; End If
if w < z then x = 0; z = b; q = w \ z
begin while(w) u = u - q * x
q := u; u := x; x := q; { w = w - q * z
q := w; w := z; z := q; if(w < z) Wend
end; { If z = 1 Then
q := w div z; q = u; u = x; x = q; If x < 0 Then x += b
u := u - q * x; q = w; w = z; z = q; Print x
w := w - q * z; } Else
end; q = w / z; Print "BRAK"
if z = 1 then u -= q * x; End If
begin w -= q * z; End
if x < 0 then inc(x,b); }
writeln(x); if(z == 1)
end {
else writeln('BRAK'); if(x < 0) x += b;
end. cout << x << endl;
}
else cout << "BRAK\n";
return 0;
}

Wynik
12767 256
31
12768 256
BRAK

Odwrotność modulo
(C)2012 mgr Jerzy Wałaszek
a= 12767 , b = 256

Wykonaj

...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0009.php 51 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0009.php 52 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby pierwsze – generacja przez sprawdzanie podzielności


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Znaleźć n kolejnych liczb pierwszych (ang. primes).

Liczba naturalna p jest liczbą pierwszą (ang. prime number) posiadającą dokładnie dwa różne podzielniki: 1 i siebie samą.
W informatyce liczby pierwsze posiadają olbrzymie zastosowanie – np. w kryptografii, czyli przy szyfrowaniu i rozszyfrowywaniu
informacji. Jak jest to ważne dla handlu i bankowości w sieci, chyba nie trzeba nikogo przekonywać. Dlatego znajomość sposobów
generacji liczb pierwszych jest obowiązkowa dla każdego informatyka.

Rozwiązanie 1
Pierwsze, narzucające się podejście do problemu generacji liczb pierwszych jest bardzo prymitywne. Po prostu bierzemy kolejne
liczby naturalne poczynając od 2 (1 nie jest pierwsze ponieważ dzieli się tylko przez 1 i brakuje nam drugiego podzielnika).
Wybraną liczbę naturalną p próbujemy dzielić przez liczby od 2 do p-1. Jeśli żadna z tych liczb nie jest podzielnikiem p, to liczba p
jest pierwsza. Wyprowadzamy ją i w specjalnym liczniku odnotowujemy ten fakt. Gdy licznik osiągnie stan n, kończymy algorytm.

Algorytm wyznaczania liczb pierwszych przez sprawdzanie podzielności


Wejście
n – liczba określająca ile liczb pierwszych należy wygenerować, n N
Wyjście:
n kolejnych liczb pierwszych

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 53 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

Zmienne pomocnicze
lp – zlicza kolejno wygenerowane liczby pierwsze. lp N
p – kolejno testowane liczby naturalne. p N
d – kolejne dzielniki. d N

Lista kroków:
K01: lp ← 0 ; zerujemy licznik liczb pierwszych
K02: p ← 2 ; generację rozpoczynamy od 2
K03: Dopóki lp < n, wykonuj kroki K04...K08 ; pętla generacji liczb pierwszych
Dla d = 2,3,...,p -1, wykonuj krok
K04: K05 ; pętla sprawdzania podzielności p przez d
K05: Jeśli p mod d = 0, idź do K08 ; jeśli p dzieli się przez d, to nie jest pierwsze
K06: Pisz p ; p jest pierwsze
K07: lp ← lp + 1 ; zwiększamy licznik wygenerowanych liczb pierwszych
K08: p ← p + 1 ; przechodzimy do kolejnej liczby, kandydata
K09: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje n kolejnych liczb pierwszych.

Lazarus

// Liczby pierwsze
// Data : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
var n,lp,p,d : longword;
t : boolean;
begin
readln(n);
lp := 0;
p := 2;
while lp < n do
begin
t := true;
for d := 2 to p - 1 do
if p mod d = 0 then
begin
t := false;
break;
end;
if t then
begin
write(p,' ');
inc(lp);
end;
inc(p);
end;
writeln;
end.

Code::Blocks

// Liczby pierwsze
// Data : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int n,lp,p,d;
bool t;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 54 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

cin >> n;
lp = 0;
p = 2;
while(lp < n)
{
t = true;
for(d = 2; d < p; d++)
if(p % d == 0)
{
t = false;
break;
}
if(t)
{
cout << p << " ";
lp++;
}
p++;
}
cout << endl;
return 0;
}

Free Basic

' Liczby pierwsze


' Data : 15.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger n,lp,p,d,t
Input n
lp = 0
p = 2
While lp < n
t = 1
For d = 2 To p - 1
If p Mod d = 0 Then
t = 0
Exit For
End If
Next
If t = 1 Then
Print p;" ";
lp += 1
End If
p += 1
Wend
Print
End

Wynik
25
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Rozwiązanie 2
Pierwszy algorytm generowania liczb pierwszych jest bardzo nieefektywny dla dużych n. Początkowo działa szybko, później
wyraźnie zwalnia, aby na końcu osiągnąć wręcz żółwie tempo pracy. Jest to typowa cecha algorytmów o klasie złożoności
obliczeniowej O(n2) – aby przekonać się, iż liczba p jest liczbą pierwszą, algorytm musi wykonać p - 2 testy. Zastanówmy się nad
tym, czy algorytm faktycznie powinien sprawdzać podzielność liczby p w całym przedziale <2,p-1>.
Jeśli liczba p jest złożona, to rozkłada się na iloczyn czynników pierwszych:

p = d1 × d2 × ... × dk

Czynników tych musi być przynajmniej 2 i muszą one być różne od 1 i p (dlaczego?). Prosta analiza pokazuje nam, iż w
przedziale od pierwiastka z p do p - 1 może leżeć co najwyżej jeden czynnik. Gdyby było ich więcej, to ich iloczyn przekroczyłby
wartość liczby p. Skoro drugi czynnik nie może być większy od pierwiastka z p, to musi być od niego mniejszy lub równy. Z kolei
przy teście na pierwszość liczby p wystarczy nam, iż znajdziemy pierwszą podzielność, aby wyeliminować p. Wynika z tego
prosty wniosek – podzielność liczby p wystarczy sprawdzić dla dzielników z przedziału od 2 do pierwiastka całkowitego z p. Jeśli
żaden podzielnik z tego przedziału nie dzieli p, to p jest pierwsze, ponieważ w pozostałej części przedziału <2, p-1>nie mogą już
wystąpić czynniki p.
Drugie ulepszenie będzie polegało na eliminacji niektórych wartości p. Na przykład jedyną liczbą pierwszą parzystą jest 2.
Wszystkie inne są już nieparzyste. Możemy pójść dalej tym tropem i dokonać dalszych eliminacji. Dwoma początkowymi liczbami
pierwszymi są liczby 2 i 3. Zatem z ciągu dalszych kandydatów na liczby pierwsze możemy wyeliminować wszystkie
wielokrotności liczb 2 i 3, takie jak 6, 8, 9, 10, 12, 14,15... Liczby te mają postać 6k, 6k±2 i 6k±3, dla k = 1,2,.... Pozostają
jedynie do sprawdzenia liczby postaci 6k±1, a tych jest już zdecydowanie mniej.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 55 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

Trzecie ulepszenie polega na tym, iż sprawdzamy podzielność nie przez kolejne liczby z przedziału od 2 do pierwiastka z p lecz
przez liczby pierwsze wpadające w ten przedział. Po prostu algorytm w miarę znajdowania kolejnych liczb pierwszych umieszcza
je w osobnej tablicy i wykorzystuje do testowania podzielności nowych kandydatów na liczbę pierwszą. Wymaga to co prawda
tablicy n elementowej, ale opłaci się szybkością eliminacji liczb złożonych. Zwykle tak jest, iż szybkość pracy algorytmu
zwiększa się kosztem większego zapotrzebowania na pamięć.

Algorytm wyznaczania liczb pierwszych przez sprawdzanie podzielności – wersja ulepszona


Wejście
n – liczba określająca ile liczb pierwszych należy wygenerować, n N

Wyjście:
n kolejnych liczb pierwszych. Wygenerowane liczby pierwsze znajdują się również w kolejnych n
elementach tablicy tlp. Indeksy rozpoczynają się od 0
Zmienne pomocnicze
lp – zlicza kolejno wygenerowane liczby pierwsze. lp N
p – kolejno testowane liczby naturalne. p N
g – zawiera pierwiastek całkowity z p. g N
k – używane do generacji liczb p, k N
d – wraz z k używane do generacji liczb p, d Z
tlp – tablica liczb pierwszych. tlp[i] N, dla i = 0,1,...,n-1
i – wykorzystywane przy numeracji podzielników z tlp. i N

Lista kroków:
K01: lp ← 0 ; ustawiamy licznik liczb pierwszych
K02: k ← 1; d ← 1; p ← 2 ; oraz zmienne do generacji p = 6k±1
K03: Dopóki lp < n, wykonuj kroki K04...K16 ; w pętli znajdujemy kolejne liczby pierwsze
K04: Jeśli lp < 3, to p ← p + lp i idź do K14 ; początkowe trzy liczby pierwsze są zadane
z góry
K05: p←6× k + d ; pozostałe musimy szukać wśród liczb
6k±1
K06: Jeśli d = 1, to idź do K08 ; modyfikujemy d i k dla następnej liczby p
K07: d ← 1 i idź do K09
K08: d ← -1; k ← k + 1
K09: g ← [√p] ; granica sprawdzania podzielności p
K10: i ←2
K11: Dopóki tlp[i] ≤ g, wykonuj kroki K12...K13
K12: Jeśli p mod tlp[i] = 0, to następny obieg ; sprawdzamy podzielność p przez
pętli K03 podzielniki z tlp
K13: i ←i + 1 ; indeks następnego podzielnika
K14 tlp[lp] ← p ; liczbę pierwszą zapamiętujemy w tlp
K15: Pisz p
K16: lp ← lp + 1
K17: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze.

Lazarus Code::Blocks Free Basic

// Liczby pierwsze // Liczby pierwsze ' Liczby pierwsze


// Data : 15.03.2008 // Data : 15.03.2008 ' Data : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Uinteger i,k,g,n,lp,p,t
#include <cmath> Dim As Integer d
type
Tlwarray = array of longword; using namespace std; Input n
var i,k,g,n,lp,p : longword; int main() Dim As Uinteger tlp(n)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 56 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

d : integer; {
t : boolean; unsigned int i,k,g,n,lp,p, * tlp; lp = 0: k = 1: d = 1: p = 2
tlp : Tlwarray; int d; While lp < n
bool t; t = 1
begin If lp < 3 Then
readln(n); cin >> n; p += lp
setlength(tlp,n); tlp = new unsigned int [n]; Else
lp := 0; k := 1; d := 1; p := 2; lp = 0; k = 1; d = 1; p = 2; p = 6 * k + d
while lp < n do while(lp < n) If d = 1 Then
begin { d = -1: k += 1
t := true; t = true; Else
if lp < 3 then inc(p,lp) if(lp < 3) p += lp; d = 1
else else End If
begin { g = Cuint(Sqr(p))
p := 6 * k + d; p = 6 * k + d; i = 2
if d = 1 then if(d == 1) While tlp(i) <= g
begin { If p Mod tlp(i) = 0 Then
d := -1; inc(k) d = -1; k++; t = 0: Exit While
end } End If
else d := 1; else d = 1; i += 1
g := round(sqrt(p)); g = (unsigned int)sqrt(p); Wend
i := 2; for(i = 2; tlp[i] <= g; i++) End If
while tlp[i] <= g do if(!(p % tlp[i])) If t = 1 Then
begin { tlp(lp) = p
if p mod tlp[i] = 0 then t = false; Print p;" ";
begin break; lp += 1
t := false; } End If
break } Wend
end; if(t) Print
inc(i) { End
end tlp[lp++] = p;
end; cout << p << " ";
if t then }
begin }
tlp[lp] := p; cout << endl;
write(p,' '); delete [] tlp;
inc(lp) return 0;
end }
end;
writeln;
SetLength(tlp,0);
end.

Generacja liczb pierwszych


(C)2012 mgr Jerzy Wałaszek
n= 25

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 57 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03

rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 58 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby pierwsze – sito Eratostenesa


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Sito Eratostenesa
Liczby parzyste i nieparzyste Rozwiązanie 1
Liczby podzielne lub niepodzielne przez zadane podzielniki Rozwiązanie 2
Ciągi arytmetyczne Rozwiązanie 3
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
W przedziale <2,n> znaleźć wszystkie liczby pierwsze (ang. primes).

Liczby pierwsze można wyszukiwać poprzez eliminację ze zbioru liczbowego wszystkich wielokrotności wcześniejszych liczb.

Przykład:

Mamy następujący zbiór liczb naturalnych:

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

Ze zbioru wyrzucamy wszystkie wielokrotności pierwszej liczby 2. Otrzymujemy następujący zbiór:

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

W zbiorze pozostały liczby nieparzyste – z wyjątkiem pierwszej liczby 2. Liczby podzielne przez dwa zostały
wyeliminowane. Teraz eliminujemy wielokrotności kolejnej liczby 3. Otrzymamy następujący zbiór:

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

Teraz w zbiorze pozostały liczby niepodzielne przez 2 i 3 – z wyjątkiem pierwszych 2 i 3. Zwróć uwagę, iż kolejna

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 59 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

liczba 4 została już ze zbioru wyeliminowana. Skoro tak, to ze zbioru zniknęły również wszystkie wielokrotności
liczby 4, ponieważ są one również wielokrotnościami liczby 2, a te wyeliminowaliśmy przecież na samym początku.
Przechodzimy zatem do liczby 5 i eliminujemy jej wielokrotności otrzymując zbiór wynikowy:

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

Oprócz 2,3 i 5 pozostałe w zbiorze liczby nie dzielą się już przez 2,3 i 5. Liczba 6 jest wyeliminowana (wielokrotność
2), zatem przechodzimy do 7. Po wyeliminowaniu wielokrotności liczby 7 zbiór przyjmuje postać:

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

W zbiorze pozostały same liczby pierwsze.

{2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50}

Przy eliminacji wystarczy usunąć ze zbioru wielokrotności liczb leżących w przedziale od 2 do pierwiastka z n.
Wyjaśnienie tego faktu jest identyczne jak w algorytmie szukania liczb pierwszych przez testowanie podzielności.
Jeśli liczba p jest złożona, to musi posiadać czynniki pierwsze w przedziale od 2 do pierwiastka z p.

Powyższe operacje wyrzucania wielokrotności prowadzą do przesiania zbioru wejściowego. W zbiorze pozostają tylko liczby
pierwsze, liczby będące wielokrotnościami poprzedzających je liczb zostają ze zbioru odsiane. Algorytm dokonujący tej eliminacji
nosi nazwę sita Eratostenesa (ang. Eratosthenes' sieve) i jest jednym z najszybszych sposobów generowania liczb pierwszych.

Sito Eratostenesa
Sito Eratostenesa jest algorytmem dwuprzebiegowym. Najpierw dokonuje on eliminacji liczb złożonych ze zbioru zaznaczając je w
określony sposób, a w drugim obiegu przegląda zbiór ponownie wyprowadzając na wyjście liczby, które nie zostały zaznaczone.
Podstawowe znaczenie w tym algorytmie ma wybór odpowiedniej struktury danych do reprezentacji zbioru liczb. Na tym polu
można dokonywać różnych optymalizacji. W pierwszym podejściu zastosujemy tablicę wartości logicznych S. Element S[i] będzie
odpowiadał liczbie o wartości i. Zawartość S[i] będzie z kolei informowała o tym, czy liczba i pozostała w zbiorze (S[i] = true) lub
została usunięta (S[i] = false).

Rozwiązanie 1
Najpierw przygotowujemy tablicę reprezentującą zbiór liczbowy wypełniając ją wartościami logicznymi true. Odpowiada to
umieszczeniu w zbiorze wszystkich liczb wchodzących w zakres od 2 do n. Następnie z tablicy będziemy usuwali kolejne
wielokrotności początkowych liczb od 2 do pierwiastka całkowitego z n w pisując do odpowiednich elementów wartość logiczną
false. Na koniec przeglądniemy zbiór i wypiszemy indeksy elementów zawierających wartość logiczną true – odpowiadają one
liczbom, które w zbiorze pozostały.
Za pierwszą wielokrotność do wyrzucenia ze zbioru przyjmiemy kwadrat liczby i. Przyjrzyj się naszemu przykładowi. Gdy
wyrzucamy wielokrotności liczby 2, to pierwszą z nich jest 4 = 22. Następnie dla wielokrotności liczby 3 pierwszą do wyrzucenia
jest 9 = 32, gdyż 6 zostało wyrzucone wcześniej jako wielokrotność 2. Dla 5 będzie to 25 = 52, gdyż 10 i 20 to wielokrotności 2, a
15 jest wielokrotnością 3, itd. Pozwoli to wyeliminować zbędne obiegi pętli usuwającej wielokrotności.

Algorytm sita Eratostenesa


Wejście
n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 1

Wyjście:
Kolejne liczby pierwsze w przedziale od 2 do n.
Zmienne pomocnicze
S – tablica wartości logicznych. S[i] {false,true}, dla i = 2,3,...,n.
g – zawiera granicę wyznaczania wielokrotności. g N
i – przebiega przez kolejne indeksy elementów S[i]. i N
w – wielokrotności wyrzucane ze zbioru S, w N

Lista kroków:
K01: Dla i = 2,3,...,n wykonuj S[i] ← true ; zbiór początkowo zawiera wszystkie liczby
K02: g ← [√n] ; obliczamy granicę eliminowania
wielokrotności
K03: Dla i = 2,3,...,g wykonuj kroki K04...K08 ; w pętli wyrzucamy ze zbioru wielokrotności i
Jeśli S[i] = false, to następny obieg pętli

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 60 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

Jeśli S[i] = false, to następny obieg pętli


K04: ; sprawdzamy, czy liczba i jest w zbiorze
K03
K05: w ← i2 ; jeśli tak, wyrzucamy jej wielokrotności
K06: Dopóki w ≤ n wykonuj kroki K07...K08 ; ze zbioru
K07: S[w] ← false
K08: w← w+ i ; następna wielokrotność
K09: Dla i = 2,3,...,n wykonuj krok K10 ; przeglądamy zbiór wynikowy
K10: Jeśli S[i] = true, to pisz i ; wyprowadzając pozostałe w nim liczby
K11: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze zawarte w
przedziale od 2 do n.

Lazarus

// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
type
Tbarray = array of boolean;
var i,n,w : longword;
S : Tbarray;
begin
readln(n);
setlength(S,n+1);
for i := 2 to n do S[i] := true;
for i := 2 to round(sqrt(n)) do
if S[i] then
begin
w := i * i;
while w <= n do
begin
S[w] := false; inc(w,i);
end;
end;
for i := 2 to n do if S[i] then write(i,' ');
writeln;
end.

Code::Blocks

// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
unsigned int g,i,n,w;
bool * S;
cin >> n;
S = new bool[n + 1];
for(i = 2; i <= n; i++) S[i] = true;
g = (unsigned int)sqrt(n);
for(i = 2; i <= g; i++)
if(S[i])
{
w = i * i;
while(w <= n)
{
S[w] = false; w += i;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 61 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

}
}
for(i = 2; i <= n; i++) if(S[i]) cout << i << " ";
cout << endl;
delete [] S;
return 0;
}

Free Basic

' Sito Eratostenesa


' Data : 17.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger g,i,n,w
Input n
Dim As Byte S(n + 1)
For i = 2 To n: S(i) = 1: Next
g = Cuint(Sqr(n))
For i = 2 To g
If S(i) > 0 Then
w = i * i
While w <= n
S(w) = 0: w += i
Wend
End If
Next
For i = 2 To n
If S(i) > 0 Then Print i;" ";
Next
Print
End

Wynik
100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Sito Eratostenesa
(C)2012 mgr Jerzy Wałaszek
n= 100

Wykonaj

...

Rozwiązanie 2
Jedyną parzystą liczbą pierwszą jest 2. Zatem wszystkie pozostałe liczby parzyste (4, 6, 8, ...) już nie mogą być pierwsze. Utwórzmy
tablicę S, której elementy będą reprezentowały kolejne liczby nieparzyste, począwszy od 3. Poniżej przedstawiamy początkowe
przypisania indeksów liczbom nieparzystym.

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Komórka o indeksie i oznacza liczbę o wartości 2i+1. Np. komórka 7 reprezentuje liczbę 2x7+1=15.
Liczba o wartości k jest reprezentowana przez komórkę (k-1)/2. Np. liczbę 45 reprezentuje komórka (45-1)/2=22.

Z poprzedniego algorytmu pamiętamy, że pierwszą wyrzucaną wielokrotnością jest zawsze kwadrat liczby podstawowej. Nasza tablica
rozpoczyna się od liczby 3, zatem pierwszą wielokrotnością, którą usuniemy, będzie liczba 9 na pozycji 4:

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Zauważ, że kolejne wielokrotności liczby 3, które są reprezentowane w tablicy, znajdują się w niej w odstępach co 3:

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 62 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Oznaczmy przez p odstępy pomiędzy wielokrotnościami, a przez q pozycję kwadratu liczby, której wielokrotności usuwamy z tablicy. Na
początku mamy:

p0 = 3
q0 = 4

Po usunięciu tych wielokrotności tablica S nie będzie zawierała dalszych liczb podzielnych przez 3. Przejdźmy do kolejnej liczby, czyli
do 5. Kwadrat 5 znajduje się na pozycji 12:

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Kolejne wielokrotności 5 znajdują się w naszej tablicy w odstępach co 5 (niektóre z nich są usunięte, ponieważ są również
wielokrotnościami liczby 3) :

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Zapiszmy:

p1 = p0 + 2 = 3 + 2 = 5
q1 = q0 + 8 = 4 + 8 = 12

Następna liczba to 7 i jej pierwsza wielokrotność 49 na pozycji 24. Kolejne wielokrotności są w odstępach co 7:

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Znów zapiszmy:

p2 = p1 + 2 = 5 + 2 = 7
q2 = q1 + 12 = 12 + 12 = 24

Następna liczba to 9 i jej pierwsza wielokrotność 81 na pozycji 40. Kolejne wielokrotności są w odstępach co 9:

indeks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
liczba 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81

Znów zapiszmy:

p3 = p2 + 2 = 7 + 2 = 9
q3 = q2 + 16 = 24 + 16 = 40

Wyrzucanie wielokrotności liczby 9 możemy pominąć, ponieważ sama liczba 9 jest już wyrzucona z tablicy przez 3. Ostatnią rozważaną
tu liczbą jest 11 o kwadracie równym 121. Liczba 121 znajduje się na pozycji (121-1)/2 = 60:

p4 = p3 + 2 = 9 + 2 = 11
q4 = q2 + 20 = 40 + 20 = 60

W tablicy ta pozycja już nie występuje (indeksy kończą się na wartości 40), zatem kończymy. W S pozostały jedynie liczby pierwsze.
Na koniec wyprowadźmy wzory rekurencyjne dla kolejnych wartości p oraz q. W tym celu wystarczy zauważyć prostą zależność (dla
dociekliwych - wynika ona z rozkładu kwadratów liczb nieparzystych na sumę różnic - patrz: całkowity pierwiastek kwadratowy):

Kolejne p powstaje z poprzedniego przez dodanie 2. Kolejne q powstaje z poprzedniego przez dodanie 2(p-1):

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 63 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

i pi 2(pi-1) qi
0 3 4
1 3+2 = 5 2x(5-1) = 8 4+8 = 12
2 5+2 = 7 2x(7-1) = 12 12+12 = 24
3 7+2 = 9 2x(9-1) = 16 24+16 = 40
4 9+2 = 11 2x(11-1) = 20 40+20 = 60

Zatem możemy zapisać wzór rekurencyjny:

p0 = 3, q0 = 4 – wartości startowe
Dla i > 0:
pi = pi-1 + 2
qi = qi-1 + 2(pi - 1)

Po tych rozważaniach przystępujemy do zapisu algorytmu.

Algorytm ulepszonego sita Eratostenesa


Wejście
n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 1
Wyjście:
Kolejne liczby pierwsze w przedziale od 2 do n.
Zmienne pomocnicze
S – tablica wartości logicznych. S[i] {false,true}, dla i = 1,2,...,n/2 - 1.
i, k – przebiega przez indeksy S[i]. i N
p, q – odstęp wielokrotności oraz pierwsza wielokrotność, p,q N
m – połowa z n, m N

Lista kroków:
K01: Jeśli n jest nieparzyste, to n ← n ; n sprowadzamy do parzystego
+1
K02: m ← n shr 1 ; m to połowa z n
K03: Dla i = 1,2,...,m - 1 wykonaj S[i] ; w S są początkowo wszystkie liczby nieparzyste
← true
K04: i ← 1 ; indeks kolejnych liczb liczb pierwszych w S
K05: p ← 3; q ← 4 ; odstęp 3, pierwsza wielokrotność 4 (9)
K06: Jeśli S[i] = false, to idź do K11 ; przeskakujemy liczby wyrzucone z S
K07: k ← q ; w k indeks pierwszej wielokrotności liczby 2i + 1
K08: Dopóki k < m wykonuj kroki ; wyrzucamy z S wielokrotności
K09...K10
K09: S[k] ← false
K10: k ← k + p ; wyznaczamy pozycję kolejnej wielokrotności, przesuniętą o
odstęp p
K11: i ← i + 1 ; indeks następnej liczby w S
K12: p ← p + 2 ; zwiększamy odstęp wielokrotności
K13: q ← q + (p shl 1) - 2 ; wyznaczamy pierwszą wielokrotność
K14: Jeśli q < m, to idź do K06
K15: Pisz 2 ; pierwsza liczba pierwsza wyprowadzana bezwarunkowo
Dla
K16: K17 i = 1,2,...,m- 1 wykonuj krok ; przeglądamy S wypisując pozostałe w niej liczby pierwsze
K17: Jeśli S[i] = true, to pisz 2i + 1
K18: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 64 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze zawarte w
przedziale od 2 do n.

Lazarus

// Program: 0014
// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
type
Tbarray = array of boolean;
var i,k,p,q,n,m : longword;
S : Tbarray;
begin
readln(n);
if n and 1 = 1 then inc(n);
m := n shr 1;
setlength(S,m+1);
for i := 1 to m - 1 do S[i] := true;
i := 1; p := 3; q := 4;
repeat
if S[i] then
begin
k := q;
while k < m do
begin
S[k] := false; inc(k,p);
end;
end;
inc(i); inc(p,2);
inc(q,(p shl 1) - 2);
until q >= m;
write(2,' ');
for i := 1 to m - 1 do if S[i] then write((i shl 1) + 1,' ');
writeln;
end.

Code::Blocks

// Program: 0014
// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int i,k,p,q,n,m;
bool * S;
cin >> n;
if(n & 1) n++;
m = n >> 1;
S = new bool[m + 1];
for(i = 1; i < m; i++) S[i] = true;
i = 1; p = 3; q = 4;
do
{
if(S[i])
{
k = q;
while(k < m)
{
S[k] = false; k += p;
}
}
i++; p += 2;
q += (p << 1) - 2;
} while(q < m);
cout << 2 << " ";
for(i = 1; i < m; i++) if(S[i]) cout << (i << 1)+1 << " ";
cout << endl;
delete [] S;
return 0;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 65 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

Free Basic

' Program: 0014


' Sito Eratostenesa
' Data : 17.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger i,k,p,q,n,m
Input n
If n And 1 = 1 Then n += 1
m = n Shr 1
Dim As Byte S(m + 1)
For i = 1 To m - 1:
S(i) = 1:
Next
i = 1: p = 3: q = 4
Do
If S(i) = 1 Then
k = q
While k < m
S(k) = 0: k += p
Wend
End If
i += 1: p += 2:
q += (p Shl 1) - 2
Loop Until q >= m
Print 2;" ";
For i = 1 To m - 1
If S(i) = 1 Then
Print (i Shl 1) + 1;" ";
End If
Next
Print
End

Rozwiązanie 3
Autorem prezentowanego algorytmu jest chiński informatyk Xuedong Luo. W rozwiązaniu 2 zbiór liczbowy ograniczyliśmy do
liczb nieparzystych. Teraz pójdziemy dalej i wrzucimy z tego zbioru dodatkowo wszystkie liczby podzielne przez 3. W efekcie
nasz zbiór S przybierze postać:

Wartość indeksu i: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ... in ip ... m


S[i] odpowiada liczbie: 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49 53 55 59 61 ... 3in+2 3ip+1 n

gdize: in – indeks nieparzysty, ip – indeks parzysty

Element S[i] odwzorowuje liczbę niepodzielną ani przez 2, ani przez 3. Wartość liczby określamy w zależności od tego, czy
indeks i jest parzysty, czy nieparzysty:

S[in] → 3in + 2
S[ip] → 3ip + 1

Dodatkowe założenie obejmuje n, które powinno spełniać równanie:

n = 3m + 2, gdzie m jest liczbą nieparzystą

Algorytm wyrzuca ze zbioru S wszystkie wielokrotności początkowych liczb. Najpierw wyznaczana jest pozycja kwadratu liczby
na podstawie poprzedniej pozycji. W dalszej kolejności algorytm wyznacza wszystkie pozycje wielokrotności liczby począwszy od
jej kwadratu i pomijając wielokrotności podzielne przez 2 lub przez 3. Elementy zbioru S znajdujące się na tych pozycjach są
zaznaczane jako wyrzucone.
Algorytm jest trochę trudny do zrozumienia, lecz działa znakomicie. Zaletą jest zmniejszone 3 krotnie zapotrzebowanie na pamięć
w stosunku do podstawowego algorytmu Euklidesa. Przy określaniu n pamiętaj, iż musi spełniać zależność n = 3m + 2, gdzie m
jest liczbą nieparzystą. W przeciwnym razie ostatnie liczby pierwsze mogą nie zostać wyliczone.

Algorytm ulepszonego sita Eratostenesa

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 66 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

Wejście

n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 4, n = 3m +


2, gdzie m jest liczbą nieparzystą.
Wyjście:
Kolejne liczby pierwsze w przedziale od 2 do n.
Zmienne pomocnicze
m – ilość elementów w zbiorze S. m N, m = [(n-2) / 3]
S – tablica wartości logicznych. S[i] {false,true}, dla i = 1,2,...,m.
c – pozycja pierwszej wielokrotności, wskazuje kwadrat liczby, c N
q – górna granica pozycji liczb, których wielokrotności są usuwane ze zbioru, q N
k,t,ij – zmienne służą do wyznaczania pozycji następnych wielokrotności. k,t,ij N
i,j – indeksy elementów S, i,j N

Lista kroków:
K01: m ← (n - 2) div 3 ; obliczamy liczbę elementów w S
K02: Dla i = 1,2,...,m wykonuj S[i] ← true ; w zbiorze S są początkowo wszystkie liczby
K03: c ← 0 ; c będzie wskazywało pozycję kwadratu kolejnej
liczby w zbiorze
K04: k ← 1; t ← 2 ; zmienne pomocnicze
K05: q ← [√n/3] ; granica pozycji liczb, których wielokrotności
eliminujemy
K06: Dla i = 1,2,...,q wykonuj kroki K07...K15 ; w pętli wyznaczamy pozycje liczb do usunięcia
K07: k ← 3 - k
K08: c ← c + 4k × i ; pozycja kwadratu liczby o indeksie i
K09: j ← c ; do wyrzucania liczb posłuży indeks j
K10: ij ← 2i × (3-k) + 1
K11: t ← t + 4k
K12: Dopóki j ≤ m wykonuj kroki ; pętla usuwająca liczby
K13...K15
K13: S[j] ← false; ; usuwamy j-tą liczbę
K14: j ← j + ij ; obliczamy pozycję następnej do usunięcia liczby
K15: ij ← t - ij
K16: Pisz 2 3 ; dwie początkowe liczby pierwsze wyprowadzamy
bezwarunkowo
K17: Dla i = 1,2,...,m wykonuj kroki ; przeglądamy zbiór S
K18...K19
K18: Jeśli S[i] = false, to następny obieg ; pomijamy liczby usunięte
pętli K17
K19: Jeśli i nieparzyste, to pisz 2i + 2 ; inaczej wyprowadzamy wartości liczb, które w
inaczej pisz 2i + 1 zbiorze pozostały
K20: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę n = 3m + 2, gdzie m jest liczbą nieparzystą. Jeśli n nie spełnia warunku, to program
odpowiednio dopasuje n i m, co może skutkować powiększeniem przedziału wyszukiwania liczb.

Lazarus

// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
type
Tbarray = array of boolean;
var
c,k,t,q,m,n,i,j,ij : longword;
S : Tbarray;
begin

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 67 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

readln(n);
m := n div 3;
if (m and 1) = 0 then inc(m);
setlength(S,m+1);
c := 0; k := 1; t := 2;
q := round(sqrt(n)) div 3;
for i := 1 to m do S[i] := true;
for i := 1 to q do
begin
k := 3 - k;
inc(c,(k shl 2) * i);
j := c;
ij := (i shl 1) * (3 - k) + 1;
inc(t,k shl 2);
while j <= m do
begin
S[j] := false;
inc(j,ij);
ij := t - ij;
end;
end;
write(2,' ',3,' ');
for i := 1 to m do
if S[i] then
begin
if (i and 1) = 1 then write(3 * i + 2)
else write(3 * i + 1);
write(' ');
end;
writeln;
end.

Code::Blocks

// Sito Eratostenesa
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
unsigned int c,k,t,q,m,n,i,j,ij;
bool * S;
cin >> n;
m = n / 3;
if(!(m & 1)) m++;
S = new bool[m + 1];
c = 0; k = 1; t = 2;
q = ((unsigned int)sqrt(n)) / 3;
for(i = 1; i <= m; i++) S[i] = true;
for(i = 1; i <= q; i++)
{
k = 3 - k;
c += (k << 2) * i;
j = c;
ij = (i << 1) * (3 - k) + 1;
t += k << 2;
while(j <= m)
{
S[j] = false;
j += ij;
ij = t - ij;
}
}
cout << 2 << " " << 3 << " ";
for(i = 1; i <= m; i++)
if(S[i])
{
if(i & 1) cout << 3 * i + 2;
else cout << 3 * i + 1;
cout << " ";
}
cout << endl;
delete [] S;
return 0;
}

Free Basic

' Sito Eratostenesa

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 68 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

' Data : 17.03.2008


' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger c,k,t,q,m,n,i,j,ij
Input n
m = n \ 3
If (m And 1) = 0 Then m += 1
Dim As Byte S(m + 1)
c = 0: k = 1: t = 2
q = Cuint(Sqr(n)) \ 3
For i = 1 To m: S(i) = 1: Next
For i = 1 To q
k = 3 - k
c += (k Shl 2) * i
j = c
ij = (i Shl 1) * (3 - k) + 1
t += k Shl 2
While j <= m
S(j) = 0
j += ij
ij = t - ij
Wend
Next
Print 2;" ";3;" ";
For i = 1 To m
If S(i) = 1 Then
If (i And 1) = 1 Then
Print 3 * i + 2;
Else
Print 3 * i + 1;
End If
Print " ";
End If
Next
Print
End

Wynik
100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 69 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 70 / 519
Algorytmy i Struktury Danych - Sito liniowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby pierwsze – sito liniowe


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
W przedziale <2,n> znaleźć wszystkie liczby pierwsze (ang. primes).

W roku 1978 dwaj informatycy, David Gries z Cornell University oraz Jayadev Misra z University of Texas w Austin, opublikowali
w ramach ACMs algorytm wyszukiwania liczb pierwszych, który działa w czasie liniowym O(n) i z tego powodu został nazwany
sitem liniowym (ang. linear sieve). W porównaniu do algorytmu sita Eratostenesa, sito liniowe wyrzuca ze zbioru liczby złożone
tylko jeden raz – sito Eratostenesa robi to wielokrotnie, gdy wyrzucana liczba rozkłada się na różne czynniki pierwsze. Z drugiej
strony algorytm sita liniowego wykorzystuje intensywnie mnożenie, co może powodować spadek jego efektywności dla małych n.
Algorytm operuje na zbiorze liczbowym S = {2, 3, ..., n}, w którym zdefiniowano dwie operacje:

usuń(S,i) – jest zdefiniowana dla liczby i S: S ← S \ { i }


następne(S, i) – jest funkcją zdefiniowaną dla liczby i S, której wynikiem jest bezpośrednio następna liczba
naturalna w S, większa od i.

W algorytmie wykorzystuje się twierdzenie:

Liczba złożona x może zostać jednoznacznie zapisana jako:

x = pk × q
gdzie:: p jest najmniejszą liczbą pierwszą dzielącą x bez reszty,
k ≥ 1,
p = q lub p jest mniejsze od najmniejszej liczby pierwszej, która dzieli q bez reszty

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0012.php 71 / 519
Algorytmy i Struktury Danych - Sito liniowe 2014-10-03

Ponieważ w powyższy sposób nie można zapisać żadnej liczby pierwszej, zatem w celu usunięcia ze zbioru S liczb złożonych,
algorytm musi jedynie wygenerować wszystkie kombinacje trójek (p, q, k) i usunąć odpowiadające im liczby złożone x. Sztuka
polega na uzyskaniu każdej kombinacji dokładnie jeden raz oraz w takiej kolejności, aby następna kombinacja mogła być
efektywnie wyliczona z kombinacji bieżącej. W tym celu algorytm stosuje mocne uporządkowanie α dla liczb złożonych
wyznaczanych przez trójki (p, q, k), indukowane uporządkowaniem leksykograficznym odpowiednich trójek (p, q, k).

Porządek α zdefiniowany jest następująco:

x = pk × q
x = pk × q
x α x p < p lub
p = p i q < q lub
p=piq=qik <k

Poniższa tabelka przedstawia to uporządkowanie, jednocześnie wyjaśniając sposób pracy algorytmu. Wiersze zawierają kolejne
wartości par (p, q) wraz z zawartością zbioru S przed usunięciem liczb złożonych zawierających te składniki p i q. Wartości liczb
złożonych x = pk × q dla k = 1,2,3,4 zostały zaznaczone na czerwono.

p q S
2 2 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 3 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 5 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 7 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 9 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 11 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 13 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 15 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
3 3 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
3 5 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
3 7 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
5 5 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

Zasada działania algorytmu sita liniowego jest następująca: Za p i q przyjmujemy pierwszą liczbę zbioru. Za k przyjmujemy 1.
Następnie w pętli generujemy liczby x = pk × q dla kolejnych k = 1,2,... do momentu, gdy x przekroczy n. Wygenerowane liczby x
usuwamy ze zbioru. Wtedy za q przyjmujemy następną liczbę w zbiorze i całość powtarzamy. Jeśli pierwsze x, dla k = 1
wykracza poza n, to zmieniamy p i q na następną liczbę, która ze zbioru S nie została jeszcze wyrzucona. Algorytm kończymy,
jeśli iloczyn p × q wykracza poza n. Z przedstawionej tablicy wynika jasno, iż każde x pojawia się tylko jeden raz, nie dochodzi
więc do zbędnych przebiegów.

Algorytm sita liniowego


Wejście
n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 1

Wyjście:
Kolejne liczby pierwsze w przedziale od 2 do n.
Zmienne pomocnicze
S – zbiór liczbowy. S {2, 3, ..., n}
p – mnożnik – liczba pierwsza. p S
q – mnożnik drugi. q S
x – liczba złożona, x S
i – liczba, i S
Lista kroków:
K01: S ← {2, 3, ..., n} ; początkowo zbiór S zawiera wszystkie liczby z przedziału
<2,n>
K02: p ← 2 ; pierwszy mnożnik
K03: Dopóki p × p ≤ n, wykonuj kroki
K04...K11
K04: q ← p ; drugi mnożnik
Dopóki p × q ≤ n, wykonuj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0012.php 72 / 519
Algorytmy i Struktury Danych - Sito liniowe 2014-10-03

Dopóki p × q ≤ n, wykonuj
K05:
kroki K06...K10
K06: x←p× q ; obliczamy liczbę złożoną
Dopóki x ≤ n wykonuj
K07: ; w pętli wyrzucamy ze zbioru liczby złożone
K08...K09
K08: usuń(S,x)
K09: x←p× x ; następna liczba złożona
; za q przyjmujemy następną liczbę w zbiorze, które nie
K10: q ← następne(S,q)
została jeszcze wyrzucona
K11: p ← następne(S,p) ; za p przyjmujemy następną liczbę pierwszą ze zbioru
Dla i = 2,3,...,n, wykonuj krok ; przeglądamy zbiór i wypisujemy pozostałe w nim liczby
K12:
K13 pierwsze
K13: Jeśli i S, to pisz i
K14: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze zawarte w
przedziale od 2 do n.

Lazarus

// Sito Liniowe
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
type
Tbarray = array of boolean;
var i,n,p,q,x : longword;
S : Tbarray;
begin
readln(n);
setlength(S,n+1);
for i := 2 to n do S[i] := true;
p := 2;
while p * p <= n do
begin
q := p;
while p * q <= n do
begin
x := p * q;
while x <= n do
begin
S[x] := false;
x := p * x;
end;
repeat
inc(q);
until S[q];
end;
repeat
inc(p);
until S[p];
end;
for i := 2 to n do if S[i] then write(i,' ');
writeln;
end.

Code::Blocks

// Sito Liniowe
// Data : 17.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0012.php 73 / 519
Algorytmy i Struktury Danych - Sito liniowe 2014-10-03

int main()
{
unsigned int i,n,p,q,x;
bool * S;
cin >> n;
S = new bool[n + 1];
for(i = 2; i <= n; i++) S[i] = true;
p = 2;
while(p * p <= n)
{
q = p;
while(p * q <= n)
{
x = p * q;
while(x <= n)
{
S[x] = false;
x *= p;
}
while(!S[++q]);
}
while(!S[++p]);
}
for(i = 2; i <= n; i++) if(S[i]) cout << i << " ";
cout << endl;
delete [] S;
return 0;
}

Free Basic

' Sito Liniowe


' Data : 17.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger i,n,p,q,x
Input n
Dim As Byte S(n+1)
For i = 2 To n: S(i) = 1: Next
p = 2
While p * p <= n
q = p
While p * q <= n
x = p * q
While x <= n
S(x) = 0
x *= p
Wend
Do
q += 1
Loop Until S(q) = 1
Wend
Do
p += 1
Loop Until S(p) = 1
Wend
For i = 2 To n
If S(i) = 1 Then Print i;" ";
Next
Print
End

Wynik
100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Sito Liniowe
(C)2012 mgr Jerzy Wałaszek
n= 100

Wykonaj

...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0012.php 74 / 519
Algorytmy i Struktury Danych - Sito liniowe 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0012.php 75 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby pierwsze – sito Atkina Bernsteina


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
W przedziale <2,n> znaleźć wszystkie liczby pierwsze (ang. primes).

Sito Atkina-Bernsteina (ang. Atkin-Bernstein Sieve) należy do nowoczesnych algorytmów wyszukujących liczby pierwsze w
przedziale od 2 do zadanej liczby naturalnej n. Jest to zoptymalizowany algorytm w stosunku do starożytnego sita Eratostenesa,
który najpierw wykonuje wstępne przetwarzanie zbioru liczb, a następnie wyrzuca z niego wszystkie wielokrotności kwadratów
początkowych liczb pierwszych zamiast samych liczb pierwszych. Twórcami algorytmu są dwaj profesorowie University of Illinois w
Chicago: Arthur Oliver Lonsdale Atkin – emerytowany profesor matematyki oraz Daniel Julius Bernstein – profesor
matematyki, kryptolog i programista.
Przedstawiony poniżej algorytm i jego realizacja nie są optymalne. Umieściłem je tutaj ze względów dydaktycznych. Prawdziwą
implementację sita Atkina można znaleźć na stronie domowej prof. Bernsteina, czyli u źródeł i tam odsyłam wszystkich
zainteresowanych tym tematem.
W przeciwieństwie do sita Eratostenesa, sito Atkina-Bernsteina rozpoczyna pracę ze zbiorem S, w którym wszystkie liczby są
zaznaczone jako złożone (czyli nie pierwsze). Ma to sens, ponieważ algorytm ignoruje kompletnie liczby podzielne przez 2, 3 lub
5. Zaoszczędzamy czas na wyrzucaniu ich ze zbioru.
Algorytm opiera swoje działanie na następujących faktach matematycznych:

Wszystkie liczby dające resztę z dzielenia przez 60 równą 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34,
36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56 lub 58 są podzielne przez 2, zatem nie są pierwsze – algorytm je ignoruje.
Wszystkie liczby dające resztę z dzielenia przez 60 równą 3, 9, 15, 21, 27, 33, 39, 45, 51 lub 57 są z kolei podzielne przez
3 i również nie są pierwsze – algorytm je ignoruje.
Wszystkie liczby dające resztę z dzielenia przez 60 równą 5, 25, 35 lub 55 są podzielne przez 5 i także nie są pierwsze –
algorytm je ignoruje.
Wszystkie liczby dające resztę z dzielenia przez 60 równą 1, 13, 17, 29, 37, 41, 49 lub 53 posiadają resztę z dzielenia

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0013.php 76 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03

przez 12 równą 1 lub 5. Liczby te są pierwsze wtedy i tylko wtedy, gdy liczba rozwiązań równania 4x2 + y2 = n jest
nieparzysta dla x,y N, a liczba n jest niepodzielna przez kwadraty liczb naturalnych.
Wszystkie liczby dające resztę z dzielenia przez 60 równą 7, 19, 31 lub 43 posiadają resztę z dzielenia przez 12 równą 7.
Są one liczbami pierwszymi wtedy i tylko wtedy, gdy liczba rozwiązań równania 3x2 + y2 = n jest nieparzysta dla x,y N, a
liczba n jest niepodzielna przez kwadraty liczb naturalnych.
Wszystkie liczby dające resztę z dzielenia przez 60 równą 11, 23, 47 lub 59 posiadają resztę z dzielenia przez 12 równą
11. Są one liczbami pierwszymi wtedy i tylko wtedy, gdy liczba rozwiązań równania 3x2 − y2 = n jest nieparzysta dla x,y
N, a liczba n jest niepodzielna przez kwadraty liczb naturalnych.

Algorytm sita Atkina-Bernsteina


Wejście
n – liczba określająca górny kraniec przedziału poszukiwania liczb pierwszych, n N, n > 4

Wyjście:
Kolejne liczby pierwsze w przedziale od 2 do n.
Zmienne pomocnicze
S – zbiór liczbowy. S {5, 6, ..., n}
g – granica przetwarzania liczb w S[]. g N
x,y – używane w równaniach kwadratowych. x,y N
xx,yy – kwadraty x i y, xx,yy N
z – rozwiązanie równania, z N
i – indeks, i N

Lista kroków:
K01: Dla i = 5,6,...,n wykonuj S[i] ← false ; inicjujemy zbiór liczbowy – wszystkie liczby
zaznaczamy jako liczby złożone
K02: g ← [√n] ; granica wyznaczania początkowych liczb pierwszych
K03: Dla każdego (x,y) z <1,g> x <1,g> ; szukamy rozwiązań równań kwadratowych
wykonuj kroki K04...K12
K04: xx = x × x ; xx = x2
K05: yy = y × y ; yy = y2
K06: z ← (xx shl 2) + yy ; z = 4x2 + y2
K07: Jeśli (z ≤ n) (z mod 12 = 1 z ; jeśli spełnione są warunki, to zmieniamy na
mod 12 = 5), przeciwny
to S[z] ← ¬ S[z] ; stan liczby w S
K08: z ← z - xx ; z = 3x2 + y2
K09: Jeśli (z ≤ n) (z mod 12 = 7), to S[z]
← ¬ S[z]
K10: Jeśli x ≤ y, to następny obieg pętli ; ostatnie równanie sprawdzamy tylko dla x > y
K03
K11: z ← z - (yy shl 1) ; z = 3x2 - y2
K12: Jeśli (z ≤ n) ( z mod 12 = 11), to
S[z] ← ¬ S[z]
K13: Dla i = 5,6,...,g wykonuj kroki K14...K18 ; eliminujemy liczby złożone sitem
K14: Jeśli S[i] = false, to następny obieg
pętli K13
K15: xx = i × i; z ← xx ; z S eliminujemy wielokrotności kwadratów liczb
pierwszych
K16: Dopóki z ≤ n wykonuj kroki
K17...K18
K17: S[z] ← false
K18: z ← z + xx
K19: Pisz 2 3 ; dwie pierwsze liczby wyprowadzamy bezwarunkowo
K20: Dla i = 5,6,...,n wykonuj krok K21 ; przeglądamy zbiór wypisując znalezione liczby
pierwsze
K21: Jeśli S[i], to pisz i
K22: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0013.php 77 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03

Program w pierwszym wierszu czyta liczbę n i w następnych wierszach wypisuje kolejne liczby pierwsze zawarte w
przedziale od 2 do n.

Lazarus

// Sito Atkina-Bernsteina
// Data : 21.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
type
Tbarray = array of boolean;
var n,g,x,y,xx,yy,z,i : longword;
S : Tbarray;
begin
readln(n);
setlength(S,n+1);
for i := 5 to n do S[i] := false;
g := round(sqrt(n));
for x := 1 to g do
begin
xx := x * x;
for y := 1 to g do
begin
yy := y * y;
z := (xx shl 2) + yy;
if (z <= n) and ((z mod 12 = 1) or (z mod 12 = 5)) then S[z] := not S[z];
dec(z,xx);
if (z <= n) and (z mod 12 = 7) then S[z] := not S[z];
if (x > y) then
begin
dec(z,yy shl 1);
if (z <= n) and (z mod 12 = 11) then S[z] := not S[z];
end;
end;
end;
for i := 5 to g do
if S[i] then
begin
xx := i * i;
z := xx;
while z <= n do
begin
S[z] := false;
inc(z,xx);
end;
end;
write(2,' ',3,' ');
for i := 5 to n do
if S[i] then write(i,' ');
writeln;
end.

Code::Blocks

// Sito Atkina-Bernsteina
// Data : 21.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
unsigned int n,g,x,y,xx,yy,z,i;
bool * S;
cin >> n;
S = new bool[n+1];
for(i = 5; i <= n; i++) S[i] = false;
g = (unsigned int)(sqrt(n));
for(x = 1; x <= g; x++)
{
xx = x * x;
for(y = 1; y <= g; y++)
{
yy = y * y;
z = (xx << 2) + yy;
if((z <= n) && ((z % 12 == 1) || (z % 12 == 5))) S[z] = !S[z];

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0013.php 78 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03

z -= xx;
if((z <= n) && (z % 12 == 7)) S[z] = !S[z];
if(x > y)
{
z -= yy << 1;
if((z <= n) && (z % 12 == 11)) S[z] = !S[z];
}
}
}
for(i = 5; i <= g; i++)
if(S[i])
{
xx = i * i;
z = xx;
while(z <= n)
{
S[z] = false;
z += xx;
}
}
cout << 2 << " " << 3 << " ";
for(i = 5; i <= n; i++)
if(S[i]) cout << i << " ";
cout << endl;
delete [] S;
return 0;
}

Free Basic

' Sito Atkina-Bernsteina


' Data : 21.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Dim As Uinteger n,g,x,y,xx,yy,z,i
Input n
Dim As Byte S(n+1)
For i = 5 To n: S(i) = 0: Next
g = Cuint(Sqr(n))
For x = 1 To g
xx = x * x
For y = 1 To g
yy = y * y
z = (xx Shl 2) + yy
If (z <= n) And ((z Mod 12 = 1) Or (z Mod 12 = 5)) Then S(z) = 1-S(z)
z -= xx
If (z <= n) And (z Mod 12 = 7) Then S(z) = 1-S(z)
If (x > y) Then
z -= yy Shl 1
If (z <= n) And (z Mod 12 = 11) Then S(z) = 1-S(z)
End If
Next
Next
For i = 5 To g
If S(i) = 1 Then
xx = i * i
z = xx
While z <= n
S(z) = 0
z += xx
Wend
End If
Next
Print 2;" ";3;" ";
For i = 5 To n
If S(i) Then Print i;" ";
Next
Print
End

Wynik
100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Sito Atkina-Bernsteina
(C)2012 mgr Jerzy Wałaszek
n= 100

Wykonaj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0013.php 79 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0013.php 80 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Czynniki pierwsze – metoda próbnych dzieleń


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Znaleźć rozkład liczby p > 1. na iloczyn czynników pierwszych.

Postawiony problem posiada bardzo duże znaczenie w wielu dziedzinach informatyki – szczególnie w kryptografii. Na dzień
dzisiejszy nie istnieje powszechnie znany żaden szybki algorytm rozkładu dużej liczby naturalnej na czynniki pierwsze (ang.
prime factorization). Na tym fakcie opierają swoje bezpieczeństwo współczesne systemy szyfrowania informacji – np. RSA. Dla
przykładu rozkład 200 cyfrowej liczby zajął 18 miesięcy wielu komputerom pracującym w sieci – w sumie czas obliczeń dla
pojedynczej maszyny wyniósł ponad pół wieku!
Zasadnicze twierdzenie arytmetyki (ang. fundamental theorem of arithmetic) mówi, iż każda liczba naturalna większa od 1
może być jednoznacznie zapisana jako iloczyn liczb pierwszych. Na przykład:

1200 = 24 × 3 × 52 i nie istnieje żaden inny rozkład dla liczby 1200

Znając rozkład liczby na czynniki pierwsze można dla niej określić wszystkie możliwe podzielniki. Na przykład każdy podzielnik
liczby 1200 da się zapisać jako:

p1200 = 2a × 3b × 5c, gdzie a {0,1,2,3,4}, b {0,1}, c {0,1,2}

Zatem wszystkich możliwych podzielników jest tyle ile wynosi iloczyn liczebności możliwych wartości a, b i c:

5 x 2 x 3 = 30

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 81 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03

Podstawowe twierdzenie arytmetyki mówi nam, iż rozkład na czynniki pierwsze jest zawsze możliwy i jednoznaczny, lecz nie
mówi, jak tego mamy dokonać.

Rozwiązanie 1
Pierwsze podejście do znalezienia rozkładu liczby p na jej czynniki pierwsze jest bardzo prymitywne, chociaż daje oczywiście
poprawny wynik. Nazywa się ono bezpośrednim poszukiwaniem rozkładu na czynniki pierwsze (ang. direct search
factorization lub trial division) Będziemy sprawdzać podzielność liczby p przez kolejne liczby naturalne od 2 do pierwiastka z p.
Jeśli liczba p będzie podzielna przez daną liczbę, to liczbę wyprowadzimy na wyjście, a za nowe p przyjmiemy wynik dzielenia i
próbę dzielenia będziemy powtarzać dotąd, aż nie będzie to już możliwe. Wtedy przejdziemy do następnego dzielnika.
Przykład:
Rozłożyć liczbę 44100 na czynniki pierwsze.

Podział Reszta Czynnik Znalezione czynniki Uwagi


44100 : 2 = 22050 0 2 2 dzieli się
22050 : 2 = 11025 0 2 22 dzieli się
11025 : 2 = 5512 1 X 22 nie dzieli się
11025 : 3 = 3675 0 3 223 dzieli się
3675 : 3 = 1225 0 3 2233 dzieli się
1225 : 3 = 408 1 X 2233 nie dzieli się
1225 : 4 = 306 1 X 2233 nie dzieli się
1225 : 5 = 245 0 5 22335 dzieli się
245 : 5 = 49 0 5 223355 dzieli się
49 : 5 = 9 4 X 223355 nie dzieli się
49 : 6 = 8 1 X 223355 nie dzieli się
49 : 7 = 7 0 7 2233557 dzieli się
7: 7=1 0 7 22335577 dzieli się
Kończymy, ponieważ wynik ostatniego dzielenia jest równy 1

44100 = 2 × 2 × 3 × 3 × 5 × 5 × 7 × 7

Algorytm rozkładu liczby naturalnej na czynniki pierwsze


Wejście
p – liczba rozkładana na czynniki pierwsze, p N, p > 1
Wyjście:
Czynniki pierwsze liczby p.
Elementy pomocnicze:
g – granica sprawdzania podzielności liczby p. g N
i – kolejne podzielniki, i N

Lista kroków:
K01: g ← [√p] ; granica sprawdzania czynników pierwszych
K02: Dla i = 2,3,...,g wykonuj kroki ; w pętli sprawdzamy podzielność liczby p przez kolejne
K03...K06 liczby
K03: Dopóki p mod i = 0 ; dopóki dzielnik dzieli p
K04: Pisz i ; wyprowadzamy go i
K05: p ← p div i ; dzielimy przez niego p
K06: Jeśli p = 1, to zakończ ; pętlę przerywamy, gdy stwierdzimy brak dalszych
dzielników
K07: Jeśli p > 1, to pisz p ; p może posiadać ostatni czynnik większy od pierwiastka
zp
K08: Zakończ

Program
Ważne:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 82 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03

Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę p i w następnym wierszu wypisuje kolejne czynniki pierwsze tej liczby.

Lazarus Code::Blocks Free Basic

// Rozkład na czynniki pierwsze // Rozkład na czynniki pierwsze ' Rozkład na czynniki pierwsze
// Data : 21.03.2008 // Data : 21.03.2008 ' Data : 21.03.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Uinteger p,i,g
#include <cmath>
var p,i,g : longword; Input p
using namespace std; g = Cuint(Sqr(p))
begin For i = 2 To g
readln(p); int main() While p Mod i = 0
g := round(sqrt(p)); { Print i;" ";
for i := 2 to g do unsigned int p,i,g; p \= i
begin Wend
while p mod i = 0 do cin >> p; If p = 1 Then Exit For
begin g = (unsigned int)sqrt(p); Next
write(i,' '); for(i = 2; i <= g; i++) If p > 1 Then Print p;
p := p div i; { Print
end; while(!(p % i)) End
if p = 1 then break; {
end; cout << i << " ";
if p > 1 then write(p); p /= i;
writeln; }
end. if(p == 1) break;
}
if(p > 1) cout << p;
cout << endl;
return 0;
}

Wynik
3971235724
2 2 431 2303501

Rozwiązanie 2
Poprzedni algorytm sprawdza podzielność liczby p przez wszystkie kolejne liczby naturalne, zawarte w przedziale <2,√p>.
Tymczasem poszukiwane podzielniki muszą być liczbami pierwszymi. Jeśli nie mamy liczb pierwszych pod ręką, to przynajmniej
możemy ograniczyć dzielenia do liczb 2, 3 oraz 6k±1, dla k = 1,2... wpadających w przedział <2,√p> . Prezentowany poniżej
algorytm dokonuje takiej właśnie optymalizacji, redukując do 1/3 liczbę sprawdzanych podzielników.

Algorytm rozkładu liczby naturalnej na czynniki pierwsze


Wejście
p – liczba rozkładana na czynniki pierwsze, p N, p > 1
Wyjście:
Czynniki pierwsze liczby p.
Elementy pomocnicze:
g – granica sprawdzania podzielności liczby p. g N
i – kolejne podzielniki, i N
k,d – zmienne do generacji liczb postaci 6k±1, k N, d {-1, 1}

Lista kroków:
K01: g ← [√p] ; wyznaczamy granicę sprawdzania podzielności
K02: k ← 1; d ← -1 ; współczynniki do generacji liczb postaci 6k±1
K03: i ← 2 ; początek sprawdzania podzielności
K04: Dopóki i ≤ g wykonuj kroki K05...K12
K05: Dopóki p mod i = 0 wykonuj kroki ; wyznaczamy dzielnik p
K06...K07
K06: Pisz i
K07: p ← p div i ; modyfikujemy p
K08: Jeśli p = 1, to idź do K14 ; p nie jest już podzielne

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 83 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03

K09: Jeśli i ≥ 3, to idź do K11 ; wyznaczamy następny podzielnik


K10: i ← i + 1 i następny obieg pętli K04 ; podzielniki 2 i 3
K11: i ← 6k + d ; pozostałe, postaci 6k±1
K12: Jeśli d = 1, to d ← -1; k ← k + 1 ; modyfikujemy współczynniki dla następnego
inaczej d←1 podzielnika
K13: Jeśli p ≠ 1, to pisz p ; ewentualny, ostatni podzielnik
K14: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę p i w następnym wierszu wypisuje kolejne czynniki pierwsze tej liczby.

Lazarus Code::Blocks Free Basic

// Rozkład na czynniki pierwsze // Rozkład na czynniki pierwsze ' Rozkład na czynniki pierwsze
// Data : 24.03.2008 // Data : 24.03.2008 ' Data : 24.03.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Uinteger p,i,g,k
#include <cmath> Dim As Integer d
var p,i,g,k : longword;
d : integer; using namespace std; Input p
begin g = Cuint(Sqr(p))
readln(p); int main() i = 2: k = 1: d = -1
g := round(sqrt(p)); { While i <= g
i := 2; k := 1; d := -1; unsigned int p,i,g,k; While p Mod i = 0
int d; Print i;" ";
while i <= g do p = p \ i
begin cin >> p; Wend
while p mod i = 0 do g = (unsigned int)sqrt(p); If p = 1 Then Exit While
begin i = 2; k = 1; d = -1; If i < 3 Then
write(i,' '); i += 1
p := p div i; while(i <= g) Else
end; { i = 6 * k + d
if p = 1 then break; while(!(p % i)) If d = 1 Then
if i < 3 then inc(i) { d = -1: k += 1
else cout << i << " "; Else
begin p /= i; d = 1
i := 6 * k + d; } End If
if d = 1 then if(p == 1) break; End If
begin if(i < 3) i++; Wend
d := -1; inc(k); else If p > 1 Then Print p;
end { Print
else d := 1; i = 6 * k + d; End
end; if(d == 1)
end; {
if p > 1 then write(p); d = -1; k++;
writeln; }
end. else d = 1;
}
}
if(p > 1) cout << p;
cout << endl;
return 0;
}

Wynik
3971235724
2 2 431 2303501

Rozkład na czynniki pierwsze


(C)2012 mgr Jerzy Wałaszek
p= 3971235724

Wykonaj

...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 84 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 85 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Czynniki pierwsze – metoda Fermata


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki Rozwiązanie 3
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Znaleźć rozkład liczby p > 1. na iloczyn czynników pierwszych.

Pierre de Fermat podał prosty sposób znajdowania czynników (liczb dzielących p bez reszty) liczby nieparzystej p. Opiera się on
na spostrzeżeniu, iż jeśli potrafimy znaleźć dwie liczby naturalne x i y, takie że:

p = x2 - y2,

to
p = (x + y) × (x - y),

zatem czynnikami liczby p są:

m=x+y
n=x-y

Znalezione czynniki m i n nie muszą być liczbami pierwszymi, zatem metodę Fermata stosujemy również do ich rozkładu. Jest to
możliwe, ponieważ czynniki liczby nieparzystej są również nieparzyste. Czynniki 2 można wyeliminować z p przed zastosowaniem
metody Fermata, zatem nie jest to żadne ograniczenie.
Metoda Fermata jest szczególnie efektywna w przypadku czynników leżących w pobliżu pierwiastka z p. W przeciwnym razie jej
efektywność jest taka sama lub nawet gorsza jak dla metody próbnych dzieleń. W praktyce metoda Fermata nie jest używana –

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 86 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

podajemy ją ze względów dydaktycznych oraz z powodu jej wagi dla innych, bardziej zaawansowanych metod poszukiwań
rozkładu liczby na czynniki pierwsze.

Rozwiązanie 1
Algorytm wykorzystuje bezpośrednio własność p = x2 - y2 do znalezienia liczb x i y. Poszukiwania rozpoczynamy od x równego
pierwiastkowi z p zaokrąglonemu w górę do najbliższej liczby całkowitej. Obliczamy:

y2 = x2 - p

Następnie sprawdzamy, czy y jest liczbą całkowitą. Jeśli tak, to znaleźliśmy odpowiednie liczby x i y. Ponieważ liczba p z
założenia jest nieparzysta, to wszystkie jej dzielniki są nieparzyste. Obliczamy dzielniki m i n i jeśli są różne od 1, to wywołujemy
rekurencyjnie procedurę Fermata do znalezienia ich rozkładu. Jeśli jeden z czynników jest równy 1, to drugi musi być równy p i p
jest liczbą pierwszą – wypisujemy p i kończymy.
Ponieważ czynnik musi być mniejszy lub równy p, to:

m=x+y≤p

Przy wzroście x rośnie również y lub pozostaje takie samo. Zatem gdy suma x + y przekroczy p, to nie znajdziemy już żadnego
dzielnika liczby p i algorytm można zakończyć przyjmując, iż p jest pierwsze.

Algorytm rozkładu liczby naturalnej na czynniki pierwsze metodą Fermata


Wejście
p – liczba rozkładana na czynniki pierwsze, p N, p jest nieparzyste

Wyjście:
Czynniki pierwsze liczby p.
Elementy pomocnicze:
x,y,z – zmienne do rozkładu p. x,y,z N
m,n – czynniki p, m,n N
Lista kroków dla procedury rekurencyjnej Fermat(p)
K01: x ← √p ; obliczamy wartość początkową dla x
K02: z ← x2 - p ; z = y2
K03: y ← √z ; sprawdzamy, czy z jest kwadratem liczby naturalnej
K04: Jeśli z ≠ y2, to idź do K11
K05: m ← x + y ; obliczamy czynniki
K06: n ← x - y
K07: Jeśli n = 1, to idź do K13 ; przerywamy przy n = 1, gdyż p jest pierwsze
K08: Fermat(m) ; rozkładamy rekurencyjnie czynniki m i n
K09: Fermat(n)
K10: Zakończ
K11: x ← x + 1 ; następne x
K12: Jeśli x + y < p, to idź do K02 ; kontynuujemy pętlę
K13: Pisz p ; p jest pierwsze
K14: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta dowolną liczbę naturalną p > 1 i w następnym wierszu wypisuje kolejne
czynniki pierwsze tej liczby. Czynniki mogą nie być wypisywane w kolejności rosnącej. Zwróć uwagę, iż do
poprawnego działania zmienna x musi być zadeklarowana jako 64 bitowa – w przeciwnym razie dla dużych liczb jej
kwadrat wyjdzie poza zakres liczb 32-bitowych. W efekcie program może dokonywać rozkładu na czynniki pierwsze
liczb 32 bitowych.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 87 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

Lazarus

// Metoda Fermata
// Data : 27.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
uses math;
procedure Fermat(p : longword);
var x,y,z,m,n : qword;
begin
x := ceil(sqrt(p));
repeat
z := x * x - p;
y := floor(sqrt(z));
if z = y * y then
begin
m := x + y;
n := x - y;
if n = 1 then break;
Fermat(m);
Fermat(n);
exit;
end;
inc(x);
until x + y >= p;
write(p,' ');
end;
var p : longword;
begin
readln(p);
while p mod 2 = 0 do
begin
p := p div 2;
write(2,' ');
end;
if p > 1 then Fermat(p);
writeln;
end.

Code::Blocks

// Metoda Fermata
// Data : 27.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cmath>
using namespace std;
void Fermat(unsigned int p)
{
unsigned long long x,y,z,m,n;
x = (unsigned long long)ceil(sqrt(p));
do
{
z = x * x - p;
y = (unsigned long long)floor(sqrt(z));
if(z == y * y)
{
m = x + y;
n = x - y;
if(n == 1) break;
Fermat(m);
Fermat(n);
return;
}
x++;
} while(x + y < p);
cout << p << " ";
}
int main()
{
unsigned int p;
cin >> p;
while(p % 2 == 0)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 88 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

{
p /= 2;
cout << 2 << " ";
}
if(p > 1) Fermat(p);
cout << endl;
return 0;
}

Free Basic

' Metoda Fermata


' Data : 27.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Sub Fermat(Byval p As Uinteger)
Dim As Ulongint x,y,z,m,n
x = Ceil(Sqr(p))
Do
z = x * x - p
y = Floor(Sqr(z))
If z = y * y Then
m = x + y
n = x - y
If n = 1 Then Exit Do
Fermat(m)
Fermat(n)
Exit Sub
End If
x += 1
Loop Until x + y >= p
Print p;" ";
End Sub
Function Ceil(Byval x As Double) As Ulongint
Dim result As Ulongint
result = Culngint(x)
If result < x Then result += 1
Ceil = result
End Function
Function Floor(Byval x As Double) As Ulongint
Dim result As Ulongint
result = Culngint(x)
If result > x Then result -= 1
Floor = result
End Function
Dim p As Uinteger
Input p
While p Mod 2 = 0
p = p Shr 1
Print 2;" ";
Wend
If p > 1 Then Fermat(p)
Print
End

Wynik
855855
11 7 13 3 3 5 19

Rozwiązanie 2
Podstawową wadą pierwszego rozwiązania jest konieczność wyznaczania wartości pierwiastka kwadratowego. Niestety, jest to
operacja dosyć czasochłonna. Zastanówmy się zatem, czy algorytmu Fermata nie można zapisać w inny sposób, tak aby nie było
konieczności wyznaczania pierwiastków kwadratowych w pętli poszukującej dzielników. Dokonajmy najpierw kilku prostych
spostrzeżeń.
Jeśli mamy daną wartość kwadratu liczby naturalnej, np. x2, to kwadrat następnej liczby x + 1 można wyznaczyć następująco:

(x + 1)2 = x2 + 2x + 1 = x2 + dx, gdzie dx = 2x + 1

Ponieważ x wzrosło o 1, to nowy przyrost dx' wyniesie:

dx' = 2(x + 1) + 1 = 2x + 2 + 1 = (2x + 1) + 2 = dx + 2

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 89 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

Wynika stąd, iż mając wartość początkową kwadratu x2 oraz dx, kolejne kwadraty obliczamy w pętli wg reguły:

x'2 = x2 + dx, x = (dx - 1) / 2, dx' = dx + 2

Poniższy program demonstruje tę zasadę wyznaczania kwadratów kolejnych liczb naturalnych od 0 do 15.

kx – kwadrat x, początkowo 0
dx – przyrost kwadratu, początkowo dx = 1, gdyż x = 0
x – kolejna liczba od 0 do 15. Liczbę tę wyznaczamy bezpośrednio z przyrostu dx.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Lazarus Code::Blocks Free Basic

// Wyznaczanie kwadratów // Wyznaczanie kwadratów ' Wyznaczanie kwadratów


// kolejnych liczb naturalnych // kolejnych liczb naturalnych ' kolejnych liczb naturalnych
// Data : 2.04.2008 // Data : 2.04.2008 ' Data : 2.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Integer kx,dx,x
#include <iomanip>
var kx,dx,x : integer; kx = 0: dx = 1
using namespace std; Do
begin x = (dx - 1) Shr 1
kx := 0; dx := 1; int main() Print Using "#### ####";x;kx
repeat { kx += dx: dx += 2
x := (dx - 1) shr 1; int kx,dx,x; Loop Until x = 15
writeln(x:5,kx:5); Print
inc(kx,dx); inc(dx,2); kx = 0; dx = 1; End
until x = 15; do
writeln; {
end. x = (dx - 1) >> 1;
cout << setw(5) << x
<< setw(5) << kx
<< endl;
kx += dx; dx += 2;
} while(x < 15);
cout << endl;
return 0;
}

Wynik
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
11 121
12 144
13 169
14 196
15 225

Zastosujmy następującą strategię. Mamy daną liczbę naturalną p, p > 1 i p jest nieparzyste. Obliczamy:

x = √p , y = 0
e = x2 - y2 - p

Liczba e jest wartością błędu pomiędzy x2 - y2 a liczbą p. Wyznaczamy przyrosty dla x2 oraz dla y2:

dx = 2x + 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 90 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

dy = 2y + 1 = 1, gdyż początkowo y = 0

Teraz w pętli badamy błąd e. Jeśli jest równy 0, to

p = x2 - y2
x = (dx - 1) / 2
y = (dy - 1) / 2
m = x + y = (dx - 1) / 2 + (dy - 1) / 2 = (dx + dy - 2) / 2 = (dx + dy) / 2 - 1
n = x - y = (dx - 1) / 2 - (dy - 1) / 2 = (dx - dy) / 2

Jeśli n jest różne od 1, to m i n rozkładamy dalej na czynniki tym samym algorytmem. W przeciwnym
razie liczba p jest pierwsza, zwracamy ją i kończymy.

Jeśli e jest różne od zera, to nie znaleźliśmy jeszcze x i y spełniających równanie Fermata. Badamy znak e.

Jeśli e > 0, to przeważa x2, zatem zwiększamy y2 i odejmujemy od e przyrost dy. Po tej operacji dy
jest zwiększane o 2, aby w kolejnym obiegu otrzymać kwadrat następnego y - a właściwie jego
przyrost w stosunku do poprzedniego kwadratu. Zwiększanie y kontynuujemy do momentu, aż e ≤ 0,
czyli aż osiągniemy równowagę lub przeważy y2.
Jeśli e < 0, to przeważa y2, zatem zwiększamy x2 i dodajemy do e przyrost dx, który następnie
zwiększamy o 2. Operację kontynuujemy do momentu, aż e ≥ 0.

Wracamy na początek pętli, aż do uzyskania e = 0. W tym miejscu algorytm można zoptymalizować, sprawdzając,
czy x + y ≥ p, czyli (dx + dy) / 2 > p. Jeśli tak, to liczba p jest pierwsza i nie znajdziemy rozkładu na inne czynniki
poza p i 1. Zatem przerywamy pętlę zwracając p.
Zwróć uwagę, iż w tej wersji algorytmu Fermata nie wykonujemy w pętli operacji pierwiastkowania, a jedynie proste
dodawania. Dzięki temu algorytm staje się dużo szybszy od algorytmu podanego w rozwiązaniu 1.

Algorytm rozkładu liczby naturalnej na czynniki pierwsze metodą Fermata


Wejście
p – liczba rozkładana na czynniki pierwsze, p N, p jest nieparzyste

Wyjście:
Czynniki pierwsze liczby p.
Elementy pomocnicze:
e – wartość błędu. e C
m,n – czynniki p, m,n N
dx,dy – przyrosty kwadratów x i y. dx,dy N

Lista kroków dla procedury rekurencyjnej Fermat(p)


K01: m ← √p ; wyznaczamy wartość błędu e oraz przyrosty
dx i dy
K02: e ← m2 - p ; e = x2 - p, ponieważ początkowo y = 0
K03: dx ← 2m + 1 ; dx – przyrost x2
K04: dy ← 1 ; dy – przyrost y2
K05: Dopóki e ≠ 0, wykonuj kroki K06...K16 ; w pętli staramy się zrównać x2 - y2 z p - wtedy
e=0
K06: Dopóki e > 0, wykonuj kroki K07...K08 ; przeważa x2, zwiększamy y2
K07: e ← e - dy ; modyfikujemy błąd o przyrost y2
K08 dy ← dy + 2 ; przyrost y2 dla zwiększonego o 1 y
K09: Dopóki e < 0, wykonuj kroki K10...K11 ; przeważa y2, zwiększamy x2
K10: e ← e + dx ; modyfikujemy błąd o przyrost x2
K11: dx ← dx + 2 ; przyrost x2 dla zwiększonego x
K12: Jeśli (dx + dy) / 2 ≤ p, to następny obieg ; sprawdzamy drugi warunek zakończenia pętli
pętli K05
K13: dx ← 2 ; jeśli jest spełniony, to p jest liczbą pierwszą
K14: dy ← 0 ; dx i dy ustawiamy tak, aby otrzymać n = 1
K15: Idź do K16 ; przerywamy pętlę K05
K16: m ← (dx + dy) / 2 - 1 ; czynnik większy
K17: n ← (dx - dy) / 2 ; czynnik mniejszy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 91 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

K18: Jeśli n = 1, to idź do K21 ; sprawdzamy, czy p jest pierwsze


K19: Fermat(m); Fermat(n) ; jeśli nie, to czynniki m i n rozkładamy dalej
K20: Zakończ
K21: Pisz p ; p jest pierwsze. Wyprowadzamy je i kończymy
K22: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych
programów oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę p i w następnym wierszu wypisuje kolejne czynniki pierwsze tej liczby.
Liczba p nie musi być nieparzysta. Przed przekazaniem jej do procedury Fermat program usuwa z p wszystkie
czynniki równe 2.

Lazarus Code::Blocks Free Basic

// Metoda Fermata // Metoda Fermata ' Metoda Fermata


// Data : 2.04.2008 // Data : 2.04.2008 ' Data : 2.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
uses math; #include <cstdlib> Sub Fermat(Byval p As Longint)
#include <iostream>
procedure Fermat(p : int64); #include <cmath> Dim As Longint e,dx,dy,m,n
var e,dx,dy,m,n : int64; using namespace std; m = Clngint(Sqr(p))
dx = (m Shl 1) + 1
begin void Fermat(long long p) e = m * m - p
m := ceil(sqrt(p)); { dy = 1
dx := (m shl 1) + 1; long long e,dx,dy,m,n; While e
e := m * m - p; m = (long long)sqrt(p); While e > 0
dy := 1; dx = (m << 1) + 1; e -= dy: dy += 2
while e <> 0 do e = m * m - p; Wend
begin dy = 1; While e < 0
while e > 0 do while(e != 0) e += dx: dx += 2
begin { Wend
dec(e,dy); inc(dy,2); while(e > 0) If (dx + dy) Shr 1 > p Then
end; { dx = 2: dy = 0: Exit While
while e < 0 do e -= dy; dy += 2; End If
begin } Wend
inc(e,dx); inc(dx,2); while(e < 0) m = ((dx + dy) Shr 1) - 1
end; { n = (dx - dy) Shr 1
if (dx + dy) shr 1 > p then e += dx; dx += 2; If n > 1 Then
begin } Fermat(m): Fermat(n)
dx := 2; dy := 0; break; if(((dx + dy) >> 1) > p) Else
end; { Print p;" ";
end; dx = 2; dy = 0; break; End If
m := ((dx + dy) shr 1) - 1; } End Sub
n := (dx - dy) shr 1; }
if n > 1 then m = ((dx + dy) >> 1) - 1; Dim p As Longint
begin n = (dx - dy) >> 1;
Fermat(m); Fermat(n); if(n != 1) Input p
end { While p Mod 2 = 0
else write(p,' '); Fermat(m); Fermat(n); Print 2;" ";
end; } p = p Shr 1
else cout << p << " "; Wend
var p : int64; } If p > 1 Then Fermat(p)
Print
begin int main() End
readln(p); {
while p mod 2 = 0 do long long p;
begin cin >> p;
write(2,' '); while(p % 2 == 0)
p := p shr 1; {
end; cout << 2 << " ";
if p > 1 then Fermat(p); p >>= 1;
writeln; }
end. if(p > 1) Fermat(p);
cout << endl;
return 0;
}

Wynik
254821743888
2 2 2 2 230816797 23 3

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 92 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

Rozkład na czynniki pierwsze metodą Fermata


(C)2012 mgr Jerzy Wałaszek
p= 33768

Wykonaj

...

Rozwiązanie 3
Większość liczb złożonych posiada czynniki pierwsze o małych wartościach. Algorytm Fermata jest szczególnie wydajny dla
czynników leżących w pobliżu pierwiastka rozkładanej liczby. Natomiast czynniki małe są znajdowane wolno. Proponowane tutaj
ulepszenie polega na połączeniu algorytmu próbnych dzieleń z algorytmem Fermata. W tablicy przechowujemy kolejne liczby
pierwsze od 2 do 1009 – jest ich 169. Przed przekazaniem liczby p do procedury Fermat, z liczby p usuwamy wszystkie czynniki
pierwsze przechowywane w tablicy. Jeśli liczba posiada takie czynniki, to po usunięciu ich staje się dużo mniejsza – zatem
algorytm Fermata szybciej znajdzie pozostałe, duże czynniki pierwsze. Czas wykonania 169 próbnych dzieleń jest bardzo mały
dla współczesnych procesorów, zatem warto zastosować to ulepszenie.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Lazarus

// Metoda Fermata
// Data : 2.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
uses math;
procedure Fermat(p : int64);
var e,dx,dy,m,n : int64;
begin
m := ceil(sqrt(p));
dx := (m shl 1) + 1;
e := m * m - p;
dy := 1;
while e <> 0 do
begin
while e > 0 do
begin
dec(e,dy); inc(dy,2);
end;
while e < 0 do
begin
inc(e,dx); inc(dx,2);
end;
if (dx + dy) shr 1 > p then
begin
dx := 2; dy := 0; break;
end;
end;
m := ((dx + dy) shr 1) - 1;
n := (dx - dy) shr 1;
if n > 1 then
begin
Fermat(m); Fermat(n);
end
else write(p,' ');
end;
const lp : array[0..168] of integer = (
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 93 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

var
p : int64;
i : integer;
begin
readln(p);
for i := 0 to 168 do
begin
if p >= lp[i] then
while p mod lp[i] = 0 do
begin
write(lp[i],' ');
p := p div lp[i];
end
else break;
end;
if p > 1 then Fermat(p);
writeln;
end.

Code::Blocks

// Metoda Fermata
// Data : 2.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <cstdlib>
#include <iostream>
#include <cmath>
using namespace std;
void Fermat(long long p)
{
long long e,dx,dy,m,n;
m = (long long)sqrt(p);
dx = (m << 1) + 1;
e = m * m - p;
dy = 1;
while(e != 0)
{
while(e > 0)
{
e -= dy; dy += 2;
}
while(e < 0)
{
e += dx; dx += 2;
}
if(((dx + dy) >> 1) > p)
{
dx = 2; dy = 0; break;
}
}
m = ((dx + dy) >> 1) - 1;
n = (dx - dy) >> 1;
if(n != 1)
{
Fermat(m); Fermat(n);
}
else cout << p << " ";
}
int main()
{
const int lp[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009};
long long p;
int i;
cin >> p;
for(i = 0; i < 169; i++)
{
if(p >= lp[i])
while(p % lp[i] == 0)
{
cout << lp[i] << " ";
p /= lp[i];
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 94 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

else break;
}
if(p > 1) Fermat(p);
cout << endl;
return 0;
}

Free Basic

' Metoda Fermata


' Data : 2.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Sub Fermat(Byval p As Longint)
Dim As Longint e,dx,dy,m,n
m = Clngint(Sqr(p))
dx = (m Shl 1) + 1
e = m * m - p
dy = 1
While e
While e > 0
e -= dy: dy += 2
Wend
While e < 0
e += dx: dx += 2
Wend
If (dx + dy) Shr 1 > p Then
dx = 2: dy = 0: Exit While
End If
Wend
m = ((dx + dy) Shr 1) - 1
n = (dx - dy) Shr 1
If n > 1 Then
Fermat(m): Fermat(n)
Else
Print p;" ";
End If
End Sub
Dim lp(168) As Integer => {_
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,_
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,_
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,_
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,_
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,_
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,_
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,_
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,_
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009}
Dim p As Longint, i As Integer
Input p
For i = 0 To 168
If p >= lp(i) Then
While p Mod lp(i) = 0
Print lp(i);" ";
p \= lp(i)
Wend
Else
Exit For
End If
Next
If p > 1 Then Fermat(p)
Print
End

Wynik
1234567891011
3 7 13 67 107 630803
1111111111111111
11 17 73 101 137 5882353
222222222222222222
2 3 3 7 11 13 19 37 333667 52579

Rozkład na czynniki pierwsze metodą Fermata


(C)2012 mgr Jerzy Wałaszek
p= 2542

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 95 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 96 / 519
Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Pierwszość liczby naturalnej – algorytmy naiwne


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki Rozwiązanie 3
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Sprawdzić, czy liczba naturalna p jest pierwsza.

Rozwiązanie 1
Liczba jest pierwsza (ang. prime), jeśli nie posiada dzielników (ang. divisors) innych poza 1 i sobą samą. Pierwsze rozwiązanie
testu na pierwszość (ang. primality test) polega na próbnym dzieleniu liczby p przez liczby z przedziału od 2 do [√p] i badaniu
reszty z dzielenia. Powód takiego postępowania jest prosty – jeśli liczba p posiada czynnik większy od pierwiastka z p, to drugi
czynnik musi być mniejszy od pierwiastka, aby ich iloczyn był równy p. W przeciwnym razie iloczyn dwóch liczb większych od √p
dawałby liczbę większą od p. Zatem wystarczy przebadać podzielność p przez liczby z przedziału <2,[√p]>, aby wykluczyć liczby
złożone.

Algorytm sprawdzania pierwszości liczby naturalnej


Wejście
p – liczba badana na pierwszość, p N, p > 1

Wyjście:
TAK, jeśli p jest pierwsze lub NIE w przypadku przeciwnym.
Elementy pomocnicze:
g – granica sprawdzania podzielności p. g N
i – kolejne podzielniki liczby p, i N

Lista kroków:
K01: g ← [√p] ; wyznaczamy granicę sprawdzania podzielności p

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 97 / 519
Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

K02: Dla i = 2,3,...,g wykonuj krok K03 ; przebiegamy przez przedział <2,[√p]>
K03: Jeśli p mod i = 0, to pisz NIE i ; jeśli liczba z przedziału <2,[√p]> dzieli p, to p nie jest
zakończ pierwsze
K04: Pisz TAK ; jeśli żadna liczba z <2,[√p]> nie dzieli p, p jest
pierwsze
K05: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje w pierwszym wierszu liczbę p, a w drugim wierszu wypisuje słowo TAK, jeśli liczba p jest
pierwsza lub NIE w przypadku przeciwnym. W programie zastosowano zmienne 64-bitowe, zatem zakres p jest
dosyć duży. Jednakże dla wielkich liczb naturalnych test na pierwszość może zająć wiele czasu.

Lazarus Code::Blocks Free Basic

// Badanie pierwszości // Badanie pierwszości ' Badanie pierwszości


// Data : 3.04.2008 // Data : 3.04.2008 ' Data : 3.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Ulongint g,i,p
#include <cmath> Dim t As Byte
var g,i,p : qword;
t : boolean; using namespace std; Input p
g = Culngint(Sqr(p))
begin int main() t = 1
readln(p); { For i = 2 To g
g := round(sqrt(p)); unsigned long long g,i,p; If p Mod i = 0 Then
t := true; bool t; t = 0: Exit For
i := 2; End If
while i <= g do cin >> p; Next
begin g = (unsigned long long)sqrt(p); If t = 1 Then
if p mod i = 0 then t = true; Print "TAK"
begin for(i = 2; i <= g; i++) Else
t := false; break; { Print "NIE"
end; if(p % i == 0) End If
inc(i); { End
end; t = false; break;
if t then writeln('TAK') }
else writeln('NIE'); }
end. if(t) cout << "TAK";
else cout << "NIE";
cout << endl;
return 0;
}

Wynik
10000000000037
TAK
100000000000031
TAK
1000000000000037
TAK
10000000000000061
TAK
100000000000000003
TAK
1000000000000000003
TAK
9223372036854775783
TAK

Rozwiązanie 2
Liczba p jest liczbą pierwszą, jeśli nie dzieli się przez żadną liczbę pierwszą z przedziału <2,[√p]>. Wszystkie liczby pierwsze z
wyjątkiem 2 są liczbami nieparzystymi. Możemy zatem w poprzednim algorytmie zmniejszyć dwukrotnie liczbę potrzebnych
dzieleń, jeśli liczbę p będziemy dzielić przez kolejne liczby nieparzyste z przedziału <2,[√p]>. Oczywiście najpierw należy
wykonać test podzielności przez 2.

Algorytm sprawdzania pierwszości liczby naturalnej


Wejście

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 98 / 519
Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

Wejście
p – liczba badana na pierwszość, p N, p > 1

Wyjście:
TAK, jeśli p jest pierwsze lub NIE w przypadku przeciwnym.
Elementy pomocnicze:
g – granica sprawdzania podzielności p. g N
i – kolejne podzielniki liczby p, i N
Lista kroków:
K01 Jeśli p = 2, to idź do K08 ; liczba 2 jest pierwsza
K02: Jeśli p mod 2 = 0, to idź do K10 ; sprawdzamy podzielność przez 2
K03: g ← [√p] ; granica sprawdzania podzielności
K04: i ← 3 ; pierwszy podzielnik nieparzysty
K05: Dopóki i ≤ g, wykonuj kroki ; w pętli sprawdzamy podzielność przez podzielniki
K06...K07 nieparzyste
K06: Jeśli p mod i = 0, to idź do K10
K07: i ← i + 2 ; następny podzielnik nieparzysty
K08: Pisz "TAK" ; p nie dzieli się, zatem jest pierwsze
K09: Zakończ
K10: Pisz "NIE" ; p jest podzielne, zatem nie jest pierwsze
K11: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje w pierwszym wierszu liczbę p, a w drugim wierszu wypisuje słowo TAK, jeśli liczba p jest pierwsza lub NIE w
przypadku przeciwnym.

Lazarus Code::Blocks Free Basic

// Badanie pierwszości // Badanie pierwszości ' Badanie pierwszości


// Data : 3.04.2008 // Data : 3.04.2008 ' Data : 3.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Ulongint g,i,p
#include <cmath> Dim t As Byte
var g,i,p : qword;
t : boolean; using namespace std; Input p
t = 1
begin typedef unsigned long long ulong; If p > 2 Then
readln(p); If p Mod 2 = 0 Then
t := true; int main() t = 0
if p > 2 then { Else
begin ulong g,i,p; g = Culngint(Sqr(p))
if p mod 2 = 0 then t := false bool t = true; For i = 3 To g Step 2
else If p Mod i = 0 Then
begin cin >> p; t = 0
g := round(sqrt(p)); if(p > 2) Exit For
i := 3; { End If
while i <= g do if(p % 2 == 0) t = false; Next
begin else End If
if p mod i = 0 then { End If
begin g = (ulong)sqrt(p); If t = 1 Then
t := false; for(i = 3; i <= g; i += 2) Print "TAK"
break; if(p % i == 0) Else
end; { Print "NIE"
inc(i,2); t = false; End If
end; break; End
end; }
end }
if t then writeln('TAK') }
else writeln('NIE'); cout << (t ? "TAK" : "NIE") << endl;
end. return 0;
}

Rozwiązanie 3
Liczbę niezbędnych dzieleń można dalej ograniczyć, jeśli liczbę p będziemy dzielić przez dzielniki:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 99 / 519
Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

2, 3 oraz 6k ± 1, dla k = 1,2,..., 6k ± 1 <2,[√p]>.

Dwa pierwsze dzielniki są początkowymi liczbami pierwszymi. Gdy wyeliminujemy czynniki 2 i 3, pozostałe liczby pierwsze
muszą przybrać postać 6k ± 1. Wyjaśnienie jest bardzo proste:

6k = 2 × 3 × k – liczby podzielne przez 2 i 3 nie są pierwsze


6k ± 2 = 2 × (3k ± 1) – liczby podzielne przez 2 nie są pierwsze
6k ± 3 = 3 × (2k ± 1) – liczby podzielne przez 3 nie są pierwsze
6k ± 4 = 2 × (3k ± 2) – liczby podzielne przez 2 nie są pierwsze

Pozostają liczby postaci:

6k ± 1, które mogą być pierwsze (ale nie muszą!). Jednakże liczb tych jest 1/3 w stosunku do pierwotnego
algorytmu, co zaowocuje zmniejszeniem liczby wykonywanych dzieleń.

Algorytm sprawdzania pierwszości liczby naturalnej


Wejście
p – liczba badana na pierwszość, p N, p > 1

Wyjście:
TAK, jeśli p jest pierwsze lub NIE w przypadku przeciwnym.
Elementy pomocnicze:
g – granica sprawdzania podzielności p. g N
i – podzielniki liczby p, i N
k – mnożnik do wyznaczania podzielników postaci 6k ± 1, k N
d – zmienna pomocnicza do wyznaczania podzielników, d {false,true}

Lista kroków:
K01: g ← [√p] ; wyznaczamy granicę sprawdzania podzielności
K02: i ← 2 ; pierwszy dzielnik
K03: k ← 1; d ← false; ; ustawiamy zmienne pomocnicze
K04: Dopóki i ≤ g, wykonuj kroki K05...K14
K05: Jeśli p mod i = 0, to idź do K17 ; sprawdzamy podzielność p przez i
K06: Jeśli i > 2, to idź do K09 ; podzielniki > 3 generujemy wg wzoru 6k ± 1
K07: i ← i + 1 ; podzielniki 2 i 3
K08: Następny obieg pętli K04
K09: d ← ¬ d ; generujemy podzielnik i = 6k ± 1
K10: i ← 6k
K11: Jeśli d = true, to idź do K14
K12: k ← k + 1
K13 i ← i + 1 i następny obieg pętli K04
K14 i ← i - 1
K15: Pisz "TAK" ; p nie dzieli się przez żaden z dzielników
K16: Zakończ
K17: Pisz "NIE" ; p nie jest pierwsze
K18: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje w pierwszym wierszu liczbę p, a w drugim wierszu wypisuje słowo TAK, jeśli liczba p jest
pierwsza lub NIE w przypadku przeciwnym.

Lazarus Code::Blocks Free Basic

// Badanie pierwszości // Badanie pierwszości ' Badanie pierwszości


// Data : 3.04.2008 // Data : 3.04.2008 ' Data : 3.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
program prg; #include <iostream> Dim As Ulongint i,g,k,p

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 100 / 519


Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

#include <cmath> Dim As Byte d,t


var i,g,k,p : qword;
d,t : boolean; using namespace std; Input p
begin g = Culngint(Sqr(p))
readln(p); typedef unsigned long long ulong; i = 2
g := round(sqrt(p)); k = 1: d = 0
i := 2; int main() t = 1
k := 1; d := false; { While i <= g
t := true; ulong i,g,k,p; If p Mod i = 0 Then
while i <= g do bool d,t; t = 0: Exit While
begin End If
if p mod i = 0 then cin >> p; If i > 2 Then
begin g = (ulong)sqrt(p); d = 1 - d
t := false; break; i = 2; i = (k Shl 1) + (k Shl 2)
end; k = 1; d = false; If d = 1 Then
if i > 2 then t = true; i -= 1
begin while(i <= g) Else
d := not d; { k += 1: i += 1
i := (k shl 1) + (k shl 2); if(p % i == 0) End If
if d then dec(i) { Else
else t = false; break; i += 1
begin } End If
inc(k); inc(i); if(i > 2) Wend
end; { If t = 1 Then
end d = !d; Print "TAK"
else inc(i); i = (k << 1) + (k << 2); Else
end; if(d) i--; Print "NIE"
if t then writeln('TAK') else End If
else writeln('NIE'); { End
end. k++; i++;
}
}
else i++;
}
cout << (t ? "TAK" : "NIE") << endl;
return 0;
}

Badanie pierwszości
(C)2012 mgr Jerzy Wałaszek
p= 32767

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 101 / 519


Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03

(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0016.php 102 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Pierwszość liczby naturalnej – Chiński Test Pierwszości


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Wyznaczanie dużych potęg liczby 2 modulo n
Liczby parzyste i nieparzyste Chiński test pierwszości
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Sprawdzić, czy liczba naturalna p jest pierwsza.

Zainteresowanie testami pierwszości (ang. primality testing) wzrosło w ostatnich latach z uwagi na powszechne wprowadzenie do
użytku różnych, publicznych systemów kryptograficznych. Systemy te wymagają szybkich algorytmów weryfikacji, czy dana
liczba naturalna jest liczbą pierwszą – liczby pierwsze są potrzebne do tworzenia tzw. kluczy szyfrujących. Z uwagi na charakter
procesu szyfrowania informacji testowane liczby są bardzo duże. Zatem zwykłe metody testowania pierwszości, np. przez próbne
dzielenia liczby przez czynniki, nie zdają egzaminu z powodu swojej powolności. Dla przykładu rozważmy test pierwszości liczby
200 cyfrowej. Możemy ją przybliżyć następująco:

p ≈ 10200

Aby sprawdzić, czy liczba p jest pierwsza, dzielimy ją próbnie przez liczby z przedziału <2,√p>. Liczb tych jest:

d ≈ 10100

Jeśli wyeliminujemy z tego przedziału liczby podzielne przez 2 lub przez 3, to pozostanie ich 1/3 w stosunku do pierwotnej liczby,
zatem:

100
d ≈ 10
3

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 103 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

Tyle musimy wykonać dzieleń próbnych w algorytmie naiwnym. Wyobraźmy sobie, iż dysponujemy superkomputerem, który w
ciągu 1 sekundy jest w stanie wykonać sto miliardów (1011) takich testów podzielności (zwykłe Pentium IV tego nie potrafi, być
może jakiś super Cray przyszłości lub komputer kwantowy). Zatem wykonanie operacji zajmie:

d 10100 1089
t≈ = = [s] ≈ 3,33 × 1088 [s] ≈ 1081 [lat]
1011 3 × 1011 3

Widzimy wyraźnie, iż czas obliczeń jest niewyobrażalnie duży – cały nasz Wszechświat istnieje "zaledwie" 1,3 × 1010 lat.
Musimy zatem sięgnąć do innych metod testowania pierwszości.
Ideą takich metod jest wyszukiwanie własności, które spełniają tylko liczby pierwsze lub własności spełnianych jedynie przez
liczby złożone. Następnie sprawdzamy, czy badana liczba spełnia określoną własność i na tej podstawie wnioskujemy o jej
pierwszości. Pierwsza wskazówka pojawiła się około 500 lat p.n.e. w Chinach, gdzie odkryto fakt, iż jeśli liczba p jest pierwsza, to
wyrażenie 2p - 2 jest podzielne przez p. Sprawdźmy dla kilku początkowych liczb pierwszych:

p = 2, 2p - 2 = 22 - 2 = 4 - 2 = 2 = 1 × p
p = 3, 2p - 2 = 23 - 2 = 8 - 2 = 6 = 2 × p
p = 5, 2p - 2 = 25 - 2 = 32 - 2 = 30 = 6 × p
p = 7, 2p - 2 = 27 - 2 = 128 - 2 = 126 = 18 × p
p = 11, 2p - 2 = 211 - 2 = 2048 - 2 = 2046 = 186 × p
...

Powyższą własność można zapisać w postaci twierdzenia:

Niech p będzie liczbą naturalną większą od 1. Jeśli liczba 2p nie jest przystająca do liczby 2 modulo p, to p jest
liczbą złożoną.
Innymi słowy, jeśli 2p mod p ≠ 2, to p jest złożone.

Jeśli twierdzenie jest spełnione, to mamy pewność, iż p jest liczbą złożoną. Niestety, jeśli jednak twierdzenie nie jest spełnione,
czyli 2p mod p = 2, to liczba p jest albo pierwszą (wszystkie liczby pierwsze p dzielą 2p - 2) , albo pseudopierwszą przy
podstawie 2 (ang. base 2 pseudoprime), czyli złożoną dzielącą 2p - 2. Na szczęście liczby pseudopierwsze przy podstawie 2
występują bardzo rzadko. Jeśli zatem liczba p nie spełnia testu, to istnieje tylko prawdopodobieństwo około 0,002%, iż mimo
wszystko p jest liczbą złożoną.

Wyznaczanie dużych potęg liczby 2 modulo n


Podstawowym problemem chińskiego testu pierwszości jest obliczanie dużych potęg liczby 2. Na pierwszy rzut oka możemy
odnieść wrażenie, iż zadanie nie jest wykonalne w zakresie liczb oferowanym przez podstawowe typy danych. Na przykład liczby
64-bitowe posiadają jedynie zakres od 0 do 264 - 1. Jednakże nas nie interesuje konkretna wartość potęgi liczby 2, a jedynie reszta
z dzielenia tej potęgi przez p. Możemy zatem wykorzystać zasady arytmetyki modularnej (ang. modular arithmetic), w której
wynik każdej operacji jest resztą z dzielenia przez moduł.
Na przykład dla modułu n = 5 mamy:

3 + 4 mod 5 = 7 mod 5 = 2
4 × 3 mod 5 = 12 mod 5 = 2
24 mod 5 = 16 mod 5 = 1

Z przytoczonych przykładów wynika, iż wynik operacji arytmetycznej modulo n wpada w przedział od 0, do n-1. Zatem będzie tego
samego rzędu, co n. Dzięki tej własności możemy obliczać reszty potęg dowolnej wielkości – obliczanie potęgi rozbijamy na
operacje mnożenia modulo n. W efekcie potęgowanie sprowadza się do wymnażania odpowiednich reszt. Idea opiera się na prostej
zależności:

ax + y mod n = (ax mod n) × (ay mod n) mod n

Oznacza to, iż zamiast wyliczać resztę potęgi ax+y mod n, możemy wyznaczyć reszty modulo n potęg ax oraz ay i wymnożyć je
modulo n. Dla potęg ax i ay można dokonać rekurencyjnie takiego samego rozkładu.

Przykład:
Obliczyć 2367 mod 13.
Rozbijamy liczbę 387 na sumę potęg 2:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 104 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

387 = 256 + 128 + 2 + 1


2387 mod 13 = (2256 mod 13) × (2128 mod 13) × (22 mod 13) × (21 mod 13) mod 13

Teraz obliczamy reszty potęg 2:

21 mod 13 = 2 mod 13 = 2
22 mod 13 = (21 mod 13) × (21 mod 13) mod 13 = (2 × 2) mod 13 = 4 mod 13 = 4
24 mod 13 = (22 mod 13) × (22 mod 13) mod 13 = (4 × 4) mod 13 = 16 mod 13 = 3
28 mod 13 = (24 mod 13) × (24 mod 13) mod 13 = (3 × 3) mod 13 = 9 mod 13 = 9
216 mod 13 = (28 mod 13) × (28 mod 13) mod 13 = (9 × 9) mod 13 = 81 mod 13 = 3
232 mod 13 = (216 mod 13) × (216 mod 13) mod 13 = (3 × 3) mod 13 = 9 mod 13 = 9
264 mod 13 = (232 mod 13) × (232 mod 13) mod 13 = (9 × 9) mod 13 = 81 mod 13 = 3
2128 mod 13 = (264 mod 13) × (264 mod 13) mod 13 = (3 × 3) mod 13 = 9 mod 13 = 9
2256 mod 13 = (2128 mod 13) × (2128 mod 13) mod 13 = (9 × 9) mod 13 = 81 mod 13 = 3

Mając reszty potęg (zaznaczone na czerwono) wymnażamy je modulo 13:

2387 mod 13 = (2256 mod 13) × (2128 mod 13) × (22 mod 13) × (21 mod 13) mod 13
2387 mod 13 = 3 × 9 × 4 × 2 mod 13 = 216 mod 13 = 8

Zwróć uwagę, iż w obliczeniu końcowym biorą udział potęgi liczby 2, które odpowiadają wagom bitów ustawionych na
1 w reprezentacji dwójkowej wykładnika potęgowego:

387(10) = 110000011(2)

Spostrzeżenie to wykorzystuje poniższy algorytm szybkiego wyznaczania reszty modulo n potęgi liczby 2. Bada on
stan bitów wykładnika i na tej podstawie tworzy wynik. Mnożenie modulo n można rozbić na dodawanie modulo n
wielokrotności mnożnej. Dzięki temu wynik częściowy nie wykroczy wiele poza moduł i operację będzie można
przeprowadzać dla dużych liczb.

Algorytm obliczania a × b mod n


Wejście
a, b – mnożone liczby, a,b N
n – moduł operacji mnożenia, n N
Wyjście:
w = a × b mod n
Elementy pomocnicze:
m – maska bitowa służąca do testowania ustawionych w b bitów
Lista kroków:
K01: w ← 0 ; wynik mnożenia
K02: m ← 1 ; maska bitowa
K03: Dopóki m ≠ 0, wykonuj K04...K06
K04: Jeśli b and m ≠ 0, ; sprawdzamy ustawione bity w mnożniku
to w ← w + a mod n
K05: a ← (a shl 1) mod n ; przesuwamy mnożną o 1 bit w lewo
K06: m ← m shl 1 ; przesuwamy bit w masce
K07: Pisz w
K08: Zakończ

Algorytm obliczania ae mod n


Wejście
a – podstawa potęgi, a N
e – wykładnik dla potęgi a, e N
n – moduł, n N, n > 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 105 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

Wyjście:

w = 2e mod n
Elementy pomocnicze:
m – maska bitowa służąca do testowania ustawionych w e bitów
p – reszta z kolejnych potęg a modulo n, p N
Lista kroków:
K01: p ← a ; wartość początkowa potęgi a1
K02: w ← 1 ; wynik
K03: m ← 1 ; maska bitowa – ustawiony bit b0
K04: Dopóki m ≠0, wykonuj K05...K07 ; obliczamy 2e mod n
K05: Jeśli e and m ≠0, ; sprawdzamy, czy e posiada ustawiony bit na pozycji z m
to w ← w × p mod n ; jeśli tak, to wynik wymnażamy przez potęgę
K06: p ← p × p mod n ; wyliczamy następną potęgę
K07: m ← m shl 1 ; przesuwamy w lewo bit maski
K08: Pisz w
K09: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza dwie liczby: e – wykładnik potęgi liczby 2 oraz n – moduł. W drugim
wierszu wypisuje wynik 2e mod n.

Lazarus

// Reszty potęg 2 modulo n


// Data : 4.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
// Funkcja mnoży a i b mod n
//--------------------------
function MnozModulo(a,b,n : qword) : qword;
var m,w : qword;
begin
w := 0; m := 1;
while m <> 0 do
begin
if b and m <> 0 then w := (w + a) mod n;
a := (a shl 1) mod n;
m := m shl 1;
end;
MnozModulo := w;
end;
var e,n,m,p,w : qword;
begin
readln(e,n);
p := 2; w := 1; m := 1;
while m <> 0 do
begin
if e and m <> 0 then w := MnozModulo(w,p,n);
p := MnozModulo(p,p,n);
m := m shl 1;
end;
writeln(w);
end.

Code::Blocks

// Reszty potęg 2 modulo n


// Data : 4.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 106 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

#include <iostream>
using namespace std;
typedef unsigned long long ulong;
// Funkcja mnoży a i b mod n
//--------------------------
ulong MnozModulo(ulong a, ulong b, ulong n)
{
ulong m,w;
w = 0; m = 1;
while(m)
{
if(b & m) w = (w + a) % n;
a = (a << 1) % n;
m <<= 1;
}
return w;
}
int main()
{
ulong e,n,m,p,w;
cin >> e >> n;
p = 2; w = m = 1;
while(m)
{
if(e & m) w = MnozModulo(w,p,n);
p = MnozModulo(p,p,n);
m <<= 1;
}
cout << w << endl;
return 0;
}

Free Basic

' Reszty potęg 2 modulo n


' Data : 4.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
' Funkcja mnoży a i b mod n
'--------------------------
Function MnozModulo(Byval a As Ulongint, Byval b As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,w
w = 0: m = 1
While m
If (b And m) <> 0 Then w = (w + a) Mod n
a = (a Shl 1) Mod n
m = m Shl 1
Wend
MnozModulo = w
End Function
Dim As Ulongint e,n,m,p,w
Input e,n
p = 2: w = 1: m = 1
While m
If (e And m) <> 0 Then w = MnozModulo(w,p,n)
p = MnozModulo(p,p,n)
m = m Shl 1
Wend
Print w
End

Wynik
283125637241 551842729812
4916904288980

Chiński test pierwszości


Po podaniu algorytmów mnożenia modulo oraz potęgowania modulo przy podstawie 2 chiński test pierwszości (ang. Chinese
Primality Test) staje się bardzo prosty. Odczytujemy liczbę p. Obliczamy 2p mod p. Jeśli reszta jest różna od 2, to liczba p na
pewno jest liczbą złożoną. Jeśli reszta jest równa 2, to z dużym prawdopodobieństwem p jest liczbą pierwszą.

Program

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 107 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program w pierwszym wierszu czyta liczbę p i w następnym wierszu wypisuje słowo NIE, jeśli p jest liczbą złożoną lub TAK, jeśli
liczba p jest pierwsza lub pseudopierwsza przy podstawie 2. Zwróć uwagę, iż testowanie jest błyskawiczne.

Lazarus

// Chiński test pierwszości


// Data : 4.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
// Funkcja mnoży a i b mod n
//--------------------------
function MnozModulo(a,b,n : qword) : qword;
var m,w : qword;
begin
w := 0; m := 1;
while m <> 0 do
begin
if b and m <> 0 then w := (w + a) mod n;
a := (a shl 1) mod n;
m := m shl 1;
end;
MnozModulo := w;
end;
// Funkcja oblicza 2^e mod n
//--------------------------
function PotegujModulo(e,n : qword) : qword;
var m,p,w : qword;
begin
p := 2; w := 1; m := 1;
while m <> 0 do
begin
if e and m <> 0 then w := MnozModulo(w,p,n);
p := MnozModulo(p,p,n);
m := m shl 1;
end;
PotegujModulo := w;
end;
var p : qword;
begin
readln(p);
if PotegujModulo(p,p) = 2 then writeln('TAK') else writeln('NIE');
end.

Code::Blocks

// Chiński test pierwszości


// Data : 4.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
using namespace std;
typedef unsigned long long ulong;
// Funkcja mnoży a i b mod n
//--------------------------
ulong MnozModulo(ulong a, ulong b, ulong n)
{
ulong m,w;
w = 0; m = 1;
while(m)
{
if(b & m) w = (w + a) % n;
a = (a << 1) % n;
m <<= 1;
}
return w;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 108 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

}
// Funkcja oblicza 2^e mod n
//--------------------------
ulong PotegujModulo(ulong e, ulong n)
{
ulong m,p,w;
p = 2; w = m = 1;
while(m)
{
if(e & m) w = MnozModulo(w,p,n);
p = MnozModulo(p,p,n);
m <<= 1;
}
return w;
}
int main()
{
ulong p;
cin >> p;
cout << (PotegujModulo(p,p) == 2 ? "TAK" : "NIE") << endl;
return 0;
}

Free Basic

' Chiński test pierwszości


' Data : 4.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
' Funkcja mnoży a i b mod n
'--------------------------
Function MnozModulo(Byval a As Ulongint, Byval b As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,w
w = 0: m = 1
While m
If b And m Then w = (w + a) Mod n
a = (a Shl 1) Mod n
m = m Shl 1
Wend
MnozModulo = w
End Function
' Funkcja oblicza 2^e mod n
'--------------------------
Function PotegujModulo(Byval e As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,p,w
p = 2: w = 1: m = 1
While m
If e And m Then w = MnozModulo(w,p,n)
p = MnozModulo(p,p,n)
m = m Shl 1
Wend
PotegujModulo = w
End Function
Dim p As Ulongint
Input p
If PotegujModulo(p,p) = 2 Then Print "TAK": Else Print "NIE"
End

Wynik
9223372036854775783
TAK
9223372036854775787
NIE

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 109 / 519


Algorytmy i Struktury Danych - Chiński test pierwszości 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0017.php 110 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Pierwszość liczby naturalnej – Małe Twierdzenie Fermata


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Sprawdzić, czy liczba naturalna p jest pierwsza.

W roku 1640 Pierre de Fermat odkrył ponownie chińskie twierdzenie o pierwszości:

Jeśli p jest liczbą pierwszą, to 2p mod p = 2.

Równanie jest spełniane przez wszystkie bez wyjątku liczby pierwsze. Jeśli liczba p nie spełnia równania 2p mod p = 2, to nie jest
liczbą pierwszą – to jest 100% pewne. Pozwala to eliminować w prosty sposób liczby złożone. Jednakże spełnianie tego równania
przez p nie gwarantuje, iż p jest pierwsze, ponieważ istnieją liczby złożone, które spełniają równanie. Nazywamy je liczbami
pseudopierwszymi przy podstawie 2 (ang. base-2 pseudoprimes).

Przykład:
p = 341 = 11 × 31
2341 mod 341 = 2

Pomyślisz w tym miejscu, iż w takim razie twierdzenie jest niepoprawne. Nic z tych rzeczy. W matematyce relacja wynikania nie
jest zwrotna:

Z A wynika B – nie jest tym samym, co z B wynika A – wtedy mielibyśmy relację równoważności.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 111 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

Twierdzenie mówi jedynie, iż jeśli liczba p jest pierwsza, to 2p jest przystające modulo p do 2. Czyli jest spełnione dla każdej
liczby pierwszej (z A wynika B). Jeśli jednak weźmiemy drugą część twierdzenia, czyli równanie 2p mod p = 2, i znajdziemy liczbę
p, która je spełnia, to wcale nie mamy gwarancji, iż p będzie liczbą pierwszą (z B nie wynika A):

Wszyscy Polacy mówią po polsku (z A wynika B) , ale jeśli ktoś mówi po polsku, to wcale nie musi być Polakiem (z
B nie wynika A).

Fermat dokładnie przestudiował chińskie twierdzenie o pierwszości oraz zbadał je dla innych podstaw niż 2. W efekcie znacznie
poprawił dokładność chińskiego testu. Wyniki pracy Fermata są dzisiaj znane pod nazwą Małego Twierdzenia Fermata (ang.
Fermat's Little Theorem – FLT). Można je przedstawić następująco:

Małe Twierdzenie Fermata


Niech p będzie dowolną liczbą pierwszą i a dowolną liczbą naturalną.
Jeśli NWD(p,a) = 1, to a p - 1 mod p = 1

Małe Twierdzenie Fermata jest bardziej ogólne od twierdzenia chińskiego. Jeśli w FLT podstawę a zastąpimy przez 2 i obie strony
równania pomnożymy przez 2, to otrzymamy chińskie twierdzenie o pierwszości:

a=2
a p - 1 mod p = 1 / x 2
(2p-1) × 2 mod p = 1 × 2
2p - 1 + 1 mod p = 2
2p mod p = 2

Wzór Fermata, ap-1 mod p = 1, pozwala przetestować liczbę p dla kilku różnych podstaw, co w olbrzymim stopniu poprawia
rzetelność testu na pierwszość. Oczywiście Małe Twierdzenie Fermata również jest nawiedzane przez liczby pseudopierwsze przy
podstawie a. Jednakże dla różnych a są to zwykle różne liczby. Zatem testy dla kilku podstaw a pozwalają je zwykle znakomicie
wyeliminować. Jednakże należy tutaj wspomnieć o istnieniu pewnych liczb złożonych, zwanych liczbami Carmichaela, które są
pseudopierwsze dla każdej podstawy a – test Fermata ich nie wyeliminuje. Na szczęście liczby Carmichaela są bardzo rzadkie i
odległe od siebie – mniej więcej jedna co miliard liczb pierwszych – przykładowo poniżej 1016 są tylko 246'683 liczby Carmichaela,
natomiast liczb pierwszych jest 279'238'341'033'925.
Algorytm Fermata testujący pierwszość liczby p wybiera liczbę losową a w zakresie od 2 do p-1. Następnie sprawdza, czy jest
ona względnie pierwsza z p, czyli czy NWD(p,a) = 1. Jeśli tak, to testowany jest warunek: ap - 1 mod p = 1. Jeśli liczba p nie
spełnia tego warunku, to na pewno nie jest liczbą pierwszą. Jeśli spełnia go, to jest albo liczbą pierwszą, albo pseudopierwszą.
Aby wykluczyć większość liczb pseudopierwszych można test Fermata wykonać kilkakrotnie, np. 10 razy. Jeśli liczba p nie
zostanie odrzucona, to z bardzo dużym prawdopodobieństwem jest liczbą pierwszą lub liczbą pseudopierwszą Carmichaela. Te
ostatnie można wyeliminować w ponad 95% sprawdzając podzielność liczby p przez liczby pierwsze z przedziału od 2 do 1000.
Jeśli liczba p przejdzie pomyślnie te testy, to prawie na pewno jest liczbą pierwszą.
Do wyznaczania ap-1 zastosujemy algorytmy mnożenia i potęgowania modulo n z poprzedniego rozdziału. Sposób obliczania NWD
podaliśmy w rozdziale opisującym algorytm Euklidesa. Generację liczb pseudolosowych opisujemy w następnych rozdziałach.

Algorytm testu pierwszości Fermata


Wejście
p – testowana liczba, p N, p > 2
Wyjście:
TAK – liczba p jest pierwsza lub z bardzo małym prawdopodobieństwem pseudopierwsza Carmichaela
NIE – liczba p nie jest pierwsza
Elementy pomocnicze:
a – podstawa potęgi, a N
i – zlicza ilość wykonanych testów Fermata, i N
NWD(a,b) – oblicza NWD liczb a i b
Losowa(a,b) – zwraca liczbę pseudolosową z zakresu od a do b - 1
Lista kroków:
K01: Sprawdź, czy p jest podzielne przez ; wstępna eliminacja liczb złożonych oraz
liczbę pierwszą z przedziału od 2 do ewentualnych
1000. ; liczb pseudopierwszych Carmichaela
Jeśli tak, to idź do K07 ; Dla liczb złożonych zwracamy NIE i kończymy
K02: Dla i = 1,2,...,10 wykonuj K03...K05 ; w pętli sprawdzamy warunek Fermata

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 112 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

K03: a ← Losowa(2,p - 1) ; wybieramy losową podstawę a


K04: Jeśli NWD(p,a) ≠1, to idź do K07 ; podstawa musi być względnie pierwsza z p
K05: Jeśli a p-1 mod p ≠1, to idź do K07 ; test na liczbę złożoną
K06: Pisz "TAK" i zakończ ; liczba p przeszła pozytywnie wszystkie testy
K07: Pisz "NIE" i zakończ ; liczba p nie przeszła testów, jest zatem liczbą
złożoną

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczbę p, a w drugim wierszu wypisuje:


TAK – jeśli liczba p jest pierwsza lub z bardzo małym prawdopodobieństwem pseudopierwsza Carmichaela
NIE – jeśli liczba p jest liczbą złożoną

Lazarus

// Test pierwszości Fermata


// Data : 5.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
// 64 bitowy generator pseudolosowy
//---------------------------------
function Losuj(a,b : qword) : qword;
var w : qword;
i : integer;
begin
for i := 1 to 8 do
begin
w := w shl 8;
w := w and random(256);
end;
Losuj := a + (w mod (b - a));
end;
// Funkcja oblicza NWD
//--------------------
function NWD(a,b : qword) : qword;
var t : qword;
begin
while b <> 0 do
begin
t := b;
b := a mod b;
a := t;
end;
NWD := a;
end;
// Funkcja mnoży a i b mod n
//--------------------------
function MnozModulo(a,b,n : qword) : qword;
var m,w : qword;
begin
w := 0; m := 1;
while m <> 0 do
begin
if b and m <> 0 then w := (w + a) mod n;
a := (a shl 1) mod n;
m := m shl 1;
end;
MnozModulo := w;
end;
// Funkcja oblicza a^e mod n
//--------------------------
function PotegujModulo(a,e,n : qword) : qword;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 113 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

var m,p,w : qword;


begin
p := a; w := 1; m := 1;
while m <> 0 do
begin
if e and m <> 0 then w := MnozModulo(w,p,n);
p := MnozModulo(p,p,n);
m := m shl 1;
end;
PotegujModulo := w;
end;
// Tablica początkowych 169 liczb pierwszych
//------------------------------------------
const lp : array[0..168] of integer = (
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009);
var p,a : qword;
i : integer;
t : boolean;
begin
randomize;
readln(p);
t := true;
for i := 0 to 168 do
if (p <> lp[i]) and (p mod lp[i] = 0) then
begin
t := false; break;
end;
if t and (p > 1009) then
begin
for i := 1 to 10 do
begin
a := Losuj(2,p-1);
if (NWD(p,a) <> 1) or (PotegujModulo(a,p-1,p) <> 1) then
begin
t := false; break;
end;
end;
end;
if t then writeln('TAK')
else writeln('NIE');
end.

Code::Blocks

// Test pierwszości Fermata


// Data : 5.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
typedef unsigned long long ulong;
// 64 bitowy generator pseudolosowy
//---------------------------------
ulong Losuj(ulong a,ulong b)
{
ulong w;
int i;
for(i = 1; i <= 8; i++)
{
w <<= 8;
w &= rand() % 256;
}
return a + (w % (b - a));
}
// Funkcja oblicza NWD
//--------------------
ulong NWD(ulong a, ulong b)
{
ulong t;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 114 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

while(b)
{
t = b; b = a % b; a = t;
}
return a;
}
// Funkcja mnoży a i b mod n
//--------------------------
ulong MnozModulo(ulong a, ulong b, ulong n)
{
ulong m,w;
w = 0;
for(m = 1; m; m <<= 1)
{
if(b & m) w = (w + a) % n;
a = (a << 1) % n;
}
return w;
}
// Funkcja oblicza a^e mod n
//--------------------------
ulong PotegujModulo(ulong a, ulong e, ulong n)
{
ulong m,p,w;
p = a; w = 1;
for(m = 1; m; m <<= 1)
{
if(e & m) w = MnozModulo(w,p,n);
p = MnozModulo(p,p,n);
}
return w;
}
// Tablica początkowych 169 liczb pierwszych
//------------------------------------------
const ulong lp[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009};
int main()
{
ulong p,a;
int i;
bool t;
srand((unsigned)time(NULL));
cin >> p;
t = true;
for(i = 0; i < 169; i++)
if((p != lp[i]) && (p % lp[i] == 0))
{
t = false; break;
}
if(t && (p > 1009))
{
for(i = 1; i <= 10; i++)
{
a = Losuj(2,p-1);
if((NWD(p,a) != 1) || (PotegujModulo(a,p-1,p) != 1))
{
t = false; break;
}
}
}
cout << (t ? "TAK" : "NIE") << endl;
return 0;
}

Free Basic

' Test pierwszości Fermata


' Data : 5.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
' 64 bitowy generator pseudolosowy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 115 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

'---------------------------------
Function Losuj(Byval a As Ulongint, Byval b As Ulongint) As Ulongint
Dim w As Ulongint, i As Integer
For i = 1 To 8
w = w Shl 8
w = w And Culngint(Rnd(1)* 256)
Next
Losuj = a + (w Mod (b - a))
End Function
' Funkcja oblicza NWD
'--------------------
Function NWD(Byval a As Ulongint, Byval b As Ulongint) As Ulongint
Dim t As Ulongint
While b
t = b: b = a Mod b: a = t
Wend
NWD = a
End Function
' Funkcja mnoży a i b mod n
'--------------------------
Function MnozModulo(Byval a As Ulongint, Byval b As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,w
w = 0: m = 1
While m
If b And m Then w = (w + a) Mod n
a = (a Shl 1) Mod n
m = m Shl 1
Wend
MnozModulo = w
End Function
' Funkcja oblicza a^e mod n
'--------------------------
Function PotegujModulo(Byval a As Ulongint, Byval e As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,p,w
p = a: w = 1: m = 1
While m
If e And m Then w = MnozModulo(w,p,n)
p = MnozModulo(p,p,n)
m = m Shl 1
Wend
PotegujModulo = w
End Function
' Tablica początkowych 169 liczb pierwszych
'------------------------------------------
Dim lp(168) As Integer => {_
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,_
71, 73, 79, 83, 89, 97,101,103,107,109,113,127,131,137,139,149,151,157,163,_
167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,_
271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,_
389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,_
503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,_
631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,_
757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,_
883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997,1009}
Dim As Ulongint p,a
Dim i As Integer, t As Byte
Randomize
Open Cons For Input As #1
Input #1,p
Close #1
t = 1
For i = 0 To 168
If (p <> lp(i)) And (p Mod lp(i) = 0) Then
t = 0: Exit For
End If
Next
If (t = 1) And (p > 1009) Then
For i = 1 To 10
a = Losuj(2,p-1)
If (NWD(p,a) <> 1) Orelse (PotegujModulo(a,p-1,p) <> 1) Then

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 116 / 519


Algorytmy i Struktury Danych - Małe Twierdzenie Fermata 2014-10-03

t = 0: Exit For
End If
Next
End If
If t = 1 Then Print "TAK": Else Print "NIE"
End

Wynik
9223372036854775783
TAK
9223372036854775787
NIE

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0018.php 117 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Pierwszość liczby naturalnej – test Millera-Rabina


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Sprawdzić, czy liczba naturalna p jest pierwsza.

Opisany w tym rozdziale algorytm testowania pierwszości (ang. primality testing algorithm) liczby naturalnej p został
opracowany w roku 1975 przez Michaela O. Rabina na podstawie prac Gary'ego L. Millera. Udostępnia on szybką metodę
sprawdzania pierwszości liczby z możliwością kontrolowania poziomu prawdopodobieństwa popełnienia błędu – jest to zatem
metoda probabilistyczna, zwana testem Millera-Rabina (ang. the Rabin-Miller Probabilistic Primalty Algorithm).
Test Millera-Rabina oparty jest na następującym twierdzeniu:

Niech p będzie nieparzystą liczbą pierwszą zapisaną jako p = 1 + 2sd, gdzie d jest nieparzyste. Wtedy dla dowolnej
liczby naturalnej a <2, p - 2> ciąg Millera-Rabina:

d 2d 4d s-1 s
a , a , a , ..., a 2 d , a 2 d (mod p)

kończy się liczbą 1. Co więcej, jeśli ad nie przystaje modulo p do 1, to wyraz ciągu Millera-Rabina bezpośrednio
poprzedzający 1 jest równy p - 1.

Jeśli liczba p przejdzie test, to jest albo pierwsza, albo silnie pseudopierwsza przy podstawie a. Test Millera-Rabina daje złe wyniki
(p złożona) dla co najwyżej 1/4 baz a < p. Zatem dla jednego przebiegu prawdopodobieństwo błędu wynosi 1/4. Dla n przebiegów
przy różnych podstawach a prawdopodobieństwo błędu spada do (1/4)n. Już dwadzieścia testów daje szansę 1 do 1099511627776,

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 118 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

iż liczba złożona p zostanie potraktowana jako pierwsza. Widzimy zatem wyraźnie, iż liczbę p możemy sprawdzić na pierwszość
z bardzo dużym prawdopodobieństwem wykonując odpowiednią liczbę testów Millera-Rabina.
W algorytmie testu Millera-Rabina wykorzystujemy procedury mnożenia i potęgowania modulo, które opisaliśmy w rozdziale o
chińskim teście pierwszości. Test ten wykorzystują obecnie prawie wszystkie systemy kryptografii publicznej do testowania
pierwszości dużych liczb potrzebnych przy generacji kluczy szyfrujących/deszyfrujących.

Algorytm sprawdzania pierwszości nieparzystej liczby naturalnej testem Millera-Rabina


Wejście
p – liczba badana na pierwszość, p N, p > 2, p jest nieparzyste
n – ilość powtórzeń testu Millera-Rabina, n N

Wyjście:

TAK, jeśli p jest pierwsze lub silnie pseudopierwsze z prawdopodobieństwem (1/4)n.


NIE , jeśli p jest liczbą złożoną
Elementy pomocnicze:
s – wykładnik potęgi 2 w dzielniku p - 1. s N
d – mnożnik potęgi 2 w dzielniku p - 1. d N
i – zlicza wykonane testy Millera-Rabina, i N
a – baza. a N, a <2,p-2>
x – wyraz ciągu Millera-Rabina, x N
j – zlicza wyrazy ciągu Millera-Rabina, j N
Lista kroków:
K01: d ← p - 1 ; obliczamy s i d
K02: s ← 0
K03: Dopóki d mod 2 = 0, wykonuj K04...K05 ; usuwamy z p - 1 dzielniki 2 zliczając je w s
K04: s ← s + 1
K05: d ← d div 2
K06: Dla i = 1,2,...,n, wykonuj K07...K15 ; wykonujemy n testów Millera-Rabina
K07: a ← Losuj(2,p-2) ; losujemy bazę a
K08: x ← ad mod p ; wyliczamy pierwszy wyraz ciągu Millera-Rabina
K09: Jeśli (x = 1) (x = p - 1), to następny ; jeśli x nie spełnia warunku, wybieramy inne a
obieg K06
K10: j ← 1
K11: Dopóki (j < s) (x ≠ p - 1), wykonuj ; rozpoczynamy generację kolejnych wyrazów ciągu
K12...K14 Millera-Rabina
K12: 2
x ← x mod p ; obliczamy kolejny wyraz
K13: Jeśli x = 1, to idź do K17 ; tylko ostatni wyraz ciągu Millera-Rabina może mieć
wartość 1!
K14: j ←j + 1
K15: Jeśli x ≠p - 1, to idź do K17 ; przedostatni wyraz ciągu Millera-Rabina musi być
równy p - 1
K16: Pisz "TAK" i zakończ ; pętla wykonała n testów i zakończyła się naturalnie.
K17: Pisz "NIE" i zakończ ; liczba p nie przeszła testów Millera-Rabina

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczbę p oraz ilość testów Millera-Rabina n, a w drugim wierszu wypisuje:
TAK – jeśli liczba p jest pierwsza z prawdopodobieństwem 1 - (1/4)n
NIE – jeśli liczba p jest liczbą złożoną

Lazarus

// Test Millera-Rabina
// Data : 6.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 119 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

program prg;
// 64 bitowy generator pseudolosowy
//---------------------------------
function Losuj(a,b : qword) : qword;
var w : qword;
i : integer;
begin
for i := 1 to 8 do
begin
w := w shl 8;
w := w or random(256);
end;
Losuj := a + (w mod (b - a));
end;
// Funkcja mnoży a i b mod n
//--------------------------
function MnozModulo(a,b,n : qword) : qword;
var m,w : qword;
begin
w := 0; m := 1;
while m <> 0 do
begin
if b and m <> 0 then w := (w + a) mod n;
a := (a shl 1) mod n;
m := m shl 1;
end;
MnozModulo := w;
end;
// Funkcja oblicza a^e mod n
//--------------------------
function PotegujModulo(a,e,n : qword) : qword;
var m,p,w : qword;
begin
p := a; w := 1; m := 1;
while m <> 0 do
begin
if e and m <> 0 then w := MnozModulo(w,p,n);
p := MnozModulo(p,p,n);
m := m shl 1;
end;
PotegujModulo := w;
end;
var a,d,p,x : qword;
i,j,s,n : integer;
t : boolean;
begin
randomize;
readln(p,n);
d := p - 1;
s := 0;
while d mod 2 = 0 do
begin
inc(s); d := d div 2;
end;
t := true;
for i := 1 to n do
begin
a := Losuj(2,p-2);
x := PotegujModulo(a,d,p);
if (x = 1) or (x = p - 1) then continue;
j := 1;
while (j < s) and (x <> p - 1) do
begin
x := MnozModulo(x,x,p);
if x = 1 then
begin
t := false; break;
end;
inc(j);
end;
if not t then break;
if x <> p - 1 then
begin
t := false; break;
end;
end;
if t then writeln('TAK')
else writeln('NIE');
end.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 120 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

Code::Blocks

// Test Millera-Rabina
// Data : 6.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
typedef unsigned long long ulong;
// 64 bitowy generator pseudolosowy
//---------------------------------
ulong Losuj(ulong a,ulong b)
{
ulong w;
int i;
for(i = 1; i <= 8; i++)
{
w <<= 8;
w |= rand() % 256;
}
return a + (w % (b - a));
}
// Funkcja mnoży a i b mod n
//--------------------------
ulong MnozModulo(ulong a, ulong b, ulong n)
{
ulong m,w;
w = 0;
for(m = 1; m; m <<= 1)
{
if(b & m) w = (w + a) % n;
a = (a << 1) % n;
}
return w;
}
// Funkcja oblicza a^e mod n
//--------------------------
ulong PotegujModulo(ulong a, ulong e, ulong n)
{
ulong m,p,w;
p = a; w = 1;
for(m = 1; m; m <<= 1)
{
if(e & m) w = MnozModulo(w,p,n);
p = MnozModulo(p,p,n);
}
return w;
}
int main()
{
ulong a,d,p,x;
int i,j,s,n;
bool t;
srand((unsigned)time(NULL));
cin >> p >> n;
s = 0;
for(d = p - 1; d % 2 == 0; s++) d /= 2;
t = true;
for(i = 1; i <= n; i++)
{
a = Losuj(2,p-2);
x = PotegujModulo(a,d,p);
if((x == 1) || (x == p - 1)) continue;
for(j = 1; (j < s) && (x != p - 1); j++)
{
x = MnozModulo(x,x,p);
if(x == 1)
{
t = false; break;
}
}
if(!t) break;
if(x != p - 1)
{
t = false; break;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 121 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

}
cout << (t ? "TAK" : "NIE") << endl;
return 0;
}

Free Basic

' Test Millera-Rabina


' Data : 6.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
' 64 bitowy generator pseudolosowy
'---------------------------------
Function Losuj(Byval a As Ulongint, Byval b As Ulongint) As Ulongint
Dim w As Ulongint, i As Integer
For i = 1 To 8
w = w Shl 8
w = w Or Culngint(Rnd(1)* 256)
Next
Losuj = a + (w Mod (b - a))
End Function
' Funkcja mnoży a i b mod n
'--------------------------
Function MnozModulo(Byval a As Ulongint, Byval b As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,w
w = 0: m = 1
While m
If b And m Then w = (w + a) Mod n
a = (a Shl 1) Mod n
m = m Shl 1
Wend
MnozModulo = w
End Function
' Funkcja oblicza a^e mod n
'--------------------------
Function PotegujModulo(Byval a As Ulongint, Byval e As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint m,p,w
p = a: w = 1: m = 1
While m
If e And m Then w = MnozModulo(w,p,n)
p = MnozModulo(p,p,n)
m = m Shl 1
Wend
PotegujModulo = w
End Function
Dim As Ulongint a,d,p,x
Dim As Integer i,j,s,n
Dim t As Byte
Randomize
Input p,n
d = p - 1
s = 0
While d Mod 2 = 0
s += 1: d \= 2
Wend
t = 1
For i = 1 To n
a = Losuj(2,p-2)
x = PotegujModulo(a,d,p)
If (x = 1) Or (x = p - 1) Then Continue For
j = 1
While (j < s) And (x <> p - 1)
x = MnozModulo(x,x,p)
If x = 1 Then
t = 0: Exit While
End If
j += 1
Wend
If t = 0 Then Exit For
If x <> p - 1 Then
t = 0: Exit For
End If
Next
If t = 1 Then Print "TAK": Else Print "NIE"
End

Wynik

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 122 / 519


Algorytmy i Struktury Danych - Test Millera-Rabina 2014-10-03

9223372036854775783 20
TAK
9223372036854775787 1
NIE

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0019.php 123 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liniowe generatory liczb pseudolosowych


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Liczby pseudolosowe
Liczby parzyste i nieparzyste Generatory LCG
Liczby podzielne lub niepodzielne przez zadane podzielniki Własności generatorów LCG
Ciągi arytmetyczne Liczby pseudolosowe w przedziale
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Liczby pseudolosowe
Liczba losowa (ang. random number) jest liczbą r należącą do pewnego zbioru wartości {r1, ..., rn} wybieranych z pewnym
prawdopodobieństwem. Jeśli jako r może pojawić się każda z liczb zbioru z tym samym prawdopodobieństwem p(r) = 1/n, to
mówimy o równomiernym rozkładzie prawdopodobieństwa liczb losowych z tego zbioru. Na przykład rozważmy rzut kostką. Każdy
rzut daje liczbę losową r ze zbioru {1,2,3,4,5,6}. Jeśli kostka nie jest oszukana, to każda z możliwych wartości r pojawia się w
rzucie kostką z prawdopodobieństwem p(r) = 1/6. Liczby losowe r posiadają zatem równomierny rozkład prawdopodobieństwa.
Problem z otrzymaniem liczb losowych wynika z deterministycznego charakteru komputera i wykonywanych przez niego operacji.
Gdy człowiek dokonuje rzutu kością, nie wie co wypadnie. Taka sama operacja na komputerze wymaga działania, którego wynik
jest nieprzewidywalny – żadna z operacji wykonywanych przez procesor nie posiada takiej cechy (o ile procesor jest sprawny).
Problem starano się rozwiązać wykorzystując zewnętrzne źródła sygnałów losowych (np. generatory białego szumu), jednakże w
tego typu urządzenia nie są standardowo wyposażano komputery osobiste – należałoby wspomnieć o próbach wykorzystania
szumów kart dźwiękowych, jednakże system ten nie rozpowszechnił się z prostej przyczyny – różne karty dźwiękowe szumią
różnie, a te z górnej półki nie szumią prawie wcale.
Z drugiej strony liczby losowe używane są powszechnie przy programowaniu komputerów – gry losowe, symulacje różnych
procesów losowych, statystycznych, testowanie algorytmów dla losowych zestawów danych itp. Ponieważ nie możemy w prosty
sposób mieć prawdziwych liczb losowych, musimy się zadowolić ich sztucznym odpowiednikiem – liczbami pseudolosowymi
(ang. pseudorandom numbers). Liczby pseudolosowe wyglądają jak losowe, lecz tworzy się je algorytmicznie. Oznacza to, iż
znając wzór generacyjny oraz kilka kolejnych liczb pseudolosowych możemy bez problemu wygenerować wszystkie dalsze – tej
cechy nie posiadają liczby losowe, w przeciwnym razie totolotek straciłby sens.

Generatory LCG
Do rozwiązania problemu generacji liczb pseudolosowych opracowano specjalne funkcje modularne zwane liniowymi
generatorami kongruencyjnymi liczb pseudolosowych (ang. pseudorandom number linear congruential generator – w skrócie
LCG) o następującej postaci:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 124 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Xn = (a × Xn-1 + c) mod m
Xn – n-ta liczba pseudolosowa
Xn-1 – poprzednia liczba pseudolosowa
a – mnożnik
c – przyrost
m – moduł

Ze wzoru wynika, iż kolejna liczba pseudolosowa Xn powstaje z poprzedniej Xn-1. Liczby te tworzą zatem ściśle określony ciąg
kolejno następujących po sobie wartości.
Drugą cechą charakterystyczną jest to, iż liczba pseudolosowa Xn jest resztą z dzielenia przez moduł m. Skoro tak, to może
przyjmować wartości od 0 do m - 1. Z pierwszej i drugiej własności wynika, iż po m cyklach obliczeniowych, liczby pseudolosowe
zaczynają się powtarzać:

X0 → X1 → X2 → ... → Xm-2 → Xm-1 → X0 → X1 → ...

Jeśli współczynniki a, c i m są źle dobrane, to cykl powtarzania może być krótszy niż m.

Rozróżniamy dwa podstawowe rodzaje generatorów LCG:

Addytywny LCG: Xn = (aXn-1 + c) mod m


Multiplikatywny LCG: Xn = aXn-1 mod m

Podstawowa różnica pomiędzy nimi jest taka, iż generator addytywny LCG może generować liczby pseudolosowe z zakresu od 0
d o m - 1, a generator multiplikatywne generuje je z zakresu od 1 do m - 1. Poniżej podajemy prosty algorytm doboru
współczynników dla generatora LCG.

Określamy zakres liczb pseudolosowych 0...Xmax (dla LCG multiplikatywnego jest to 1...Xmax). Moduł m jest
zawsze o 1 większy od maksymalnej liczby w zakresie, czyli:

m = Xmax + 1

Przyrost c musi być względnie pierwszy z modułem m. Możemy m rozłożyć na czynniki pierwsze i dla c wybieramy
czynniki nie występujące w m. Możemy również generować pseudolosowe c i sprawdzać, czy spełnia warunek:

NWD(c,m) = 1

Mnożnik dobieramy wykorzystując regułę, iż wyrażenie a - 1 jest podzielne przez każdy czynnik pierwszy modułu m.
Jeśli moduł m dzieli się przez 4, to a - 1 również powinno być podzielne przez 4.

Przykład:
Zaprojektować addytywny generator LCG generujący liczby pseudolosowe w przedziale od 0 do 11.
Z warunków zadania mamy:

Xmax = 11
m = Xmax + 1 = 11 + 1 = 12

Przyrost c musi być względnie pierwszy z m. Moduł m rozkładamy na iloczyn czynników pierwszych:

m=2×2×3

Na przyrost c możemy wybrać dowolną liczbę nie posiadającą czynników 2 i 3. Na przykład może to być:

c=7

Wyrażenie a - 1 musi być podzielne przez 4 i 3.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 125 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

a - 1 = 4 × 3 = 12
a = 12 + 1 = 13

Otrzymujemy następujący wzór generatora LCG: Xn = (13 × Xn-1 + 7) mod 12 → LCG(12,13,7)

Ponieważ wzór ten pozwala obliczyć kolejną liczbę pseudolosową Xn z liczby poprzedniej Xn-1, musimy określić wartość startową
X0, od której rozpocznie się generacja liczb pseudolosowych. Wartość tę nazywamy ziarnem pseudolosowym (ang.
pseudorandom seed). Ziarno wpływa na miejsce w pierścieniu liczb pseudolosowych, od którego rozpocznie się generacja
następnych liczb.
Przyjmijmy X0 = 0 i policzmy wszystkie kolejne liczby pseudolosowe, które tworzy nasz generator LCG:

X1 = 13 × 0 + 7 mod 12 = 7 mod 12 = 7
X2 = 13 × 7 + 7 mod 12 = 98 mod 12 = 2
X3 = 13 × 2 + 7 mod 12 = 33 mod 12 = 9
X4 = 13 × 9 + 7 mod 12 = 124 mod 12 = 4
X5 = 13 × 4 + 7 mod 12 = 59 mod 12 = 11
X6 = 13 × 11 + 7 mod 12 = 150 mod 12 = 6
X7 = 13 × 6 + 7 mod 12 = 85 mod 12 = 1
X8 = 13 × 1 + 7 mod 12 = 20 mod 12 = 8
X9 = 13 × 8 + 7 mod 12 = 111 mod 12 = 3
X10 = 13 × 3 + 7 mod 12 = 46 mod 12 = 10
X11 = 13 × 10 + 7 mod 12 = 137 mod 12 = 5
X12 = 13 × 5 + 7 mod 12 = 72 mod 12 = 0
X13 = 13 × 0 + 7 mod 12 = 7 mod 12 = 7 = X1 – ciąg zaczyna się powtarzać
X14 = 13 × 7 + 7 mod 12 = 98 mod 12 = 2 = X2
...

Dla X0 = 0 otrzymaliśmy ciąg liczb pseudolosowych: 7 2 9 4 11 6 1 8 3 10 5 0 7 2 9 4 ...


Jeśli przyjmiemy inną wartość za X0, to otrzymamy ten sam ciąg, lecz startujący od innego punktu:

Dla X0 = 1 mamy : 8 3 10 5 0 7 2 9 4 11 6 1 ...


Dla X0 = 2 mamy : 9 4 11 6 1 8 3 10 5 0 7 2 ...
Dla X0 = 3 mamy : 10 5 0 7 2 9 4 11 6 1 8 3 ...

Następstwo kolejnych liczb pseudolosowych jest zawsze takie samo – np. po liczbie 3 zawsze wystąpi liczba 10.
Z powyższych rozważań można wyciągnąć wniosek, iż każdy generator LCG da się jednoznacznie scharakteryzować czwórką
parametrów:

LCG(m,a,c,X0)
m – moduł
a – mnożnik
c – przyrost
X0 – ziarno

W praktycznych realizacjach dąży się do dużych okresów generatora LCG – wtedy liczby pseudolosowe powtarzają się dopiero po
wielu miliardach przebiegów. Jako przykład niech posłuży poniższy generator LCG zaproponowany przez prof. D. Knutha:

LCG(34359738368, 3141592653, 2718281829, Xo)

m = 34359738368 = 235
a = [π × 109]
c = [e × 109], e – podstawa logarytmów naturalnych, e = 2,7182818284590452353602874713527...

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 126 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Poniższy program zawiera generator LCG. W pierwszym wierszu na wejściu należy umieścić cztery liczby m, a, c
oraz X0. W następnych wierszach program wyświetla kolejne liczby pseudolosowe generowane przez zadany
współczynnikami generator LCG aż do zamknięcia cyklu. Ambitnym czytelnikom proponujemy drobną rozbudowę
tego programu o licznik wygenerowanych liczb pseudolosowych. Po zakończeniu działania głównej zawartość
licznika powinna zostać wypisana, aby użytkownik mógł sprawdzić, czy generator LCG miał cykl maksymalny równy
m – przy źle dobranych współczynnikach m, a i c cykl generatora może się zamykać szybciej niż po wygenerowaniu
m liczb pseudolosowych.

Lazarus Code::Blocks Free Basic

// Generator LCG // Generator LCG ' Generator LCG


// Data : 8.04.2008 // Data : 8.04.2008 ' Data : 8.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//---------------------------- //---------------------------- '----------------------------
var m,a,c,x,x0 : qword; #include <iostream> Dim As Ulongint m,a,c,x,x0
begin using namespace std; Input m,a,c,x0
readln(m,a,c,x0); x = x0
x := x0; int main() Do
repeat { x = (a * x + c) Mod m
x := (a * x + c) mod m; unsigned long long m,a,c,x,x0; Print x;" ";
write(x,' '); Loop Until x = x0
until x = x0; cin >> m >> a >> c >> x0; Print
writeln; x = x0; End
end. do
{
x = (a * x + c) % m;
cout << x << " ";
} while(x != x0);
cout << endl;
return 0;
}

Wynik
12 13 7 0
7 2 9 4 11 6 1 8 3 10 5 0

Generator LCG
(C)2012 mgr Jerzy Wałaszek
m= 12 , a = 13 , c = 7 , X0 =
0

Wykonaj

...

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Poniżej mamy prosty program wyliczający współczynniki addytywnego generatora LCG na podstawie maksymalnej
liczby pseudolosowe Xmax, którą należy podać na wejściu w pierwszym wierszu. W następnych wierszach program
wypisuje wyliczone współczynniki m, a i c. Program korzysta z wbudowanego generatora liczb pseudolosowych.

Lazarus

// Współczynniki generatora LCG


// Data : 10.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
function NWD(a,b : qword) : qword;
var t : qword;
begin
while b <> 0 do
begin

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 127 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

t := b; b := a mod b; a := t;
end;
NWD := a;
end;
var m,a,c,x,d,g : qword;
begin
randomize;
readln(x);
m := x + 1;
repeat
c := random(m);
until NWD(m,c) = 1;
a := 1; x := m; d := 2; g := round(sqrt(m));
while d <= g do
begin
if x mod d = 0 then
begin
a := a * d;
repeat
x := x div d;
until x mod d <> 0;
end;
if d = 2 then inc(d) else inc(d,2);
if x < d then break;
end;
a := a * x;
if m mod 4 = 0 then a := a * 2;
inc(a);
writeln('m = ',m:12);
writeln('a = ',a:12);
writeln('c = ',c:12);
writeln;
end.

Code::Blocks

// Współczynniki generatora LCG


// Data : 10.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <iomanip>
#include <cmath>
#include <cstdlib>
#include <time.h>
using namespace std;
typedef unsigned long long ulong;
ulong NWD(ulong a, ulong b)
{
ulong t;
while(b)
{
t = b; b = a % b; a = t;
}
return a;
}
int main()
{
ulong m,a,c,x,d,g;
srand((unsigned)time(NULL));
cin >> x;
m = x + 1;
do c = rand() % m; while(NWD(m,c) != 1);
a = 1; x = m; d = 2; g = (ulong)sqrt(m);
while(d <= g)
{
if(x % d == 0)
{
a *= d;
do x /= d; while(x % d == 0);
}
if(d == 2) d++; else d += 2;
if(x < d) break;
}
a *= x;
if(m % 4 == 0) a <<= 1;
a++;
cout << "m = " << setw(12) << m << endl
<< "a = " << setw(12) << a << endl
<< "c = " << setw(12) << c << endl

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 128 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

<< endl;
return 0;
}

Free Basic

' Współczynniki generatora LCG


' Data : 10.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
Function NWD(Byval a As Ulongint, Byval b As Ulongint) As Ulongint
Dim t As Ulongint
While b
t = b: b = a Mod b: a = t
Wend
NWD = a
End Function
Dim As Ulongint m,a,c,x,d,g
Randomize Timer
Input x
m = x + 1
Do
c = Culngint(Rnd(1) * m)
Loop Until NWD(m,c) = 1
a = 1: x = m: d = 2: g = Culngint(Sqr(m))
While d <= g
If x Mod d = 0 Then
a = a * d
Do
x \= d
Loop Until x Mod d <> 0
End If
If d = 2 Then d += 1: Else d += 2
If x < d Then Exit While
Wend
a *= x
If m Mod 4 = 0 Then a *= 2
a += 1
Print Using "m = ############";m
Print Using "a = ############";a
Print Using "c = ############";c
Print
End

Wynik
9987
m = 9988
a = 9989
c = 1785

Współczynniki dla generator LCG


(C)2012 mgr Jerzy Wałaszek
Xmax = 9987

Wykonaj

...

Poniższy program sprawdza z kolei, czy generator LCG o zadanych współczynnikach m, a i c generuje m liczb w przedziale od 0
do m - 1. Na wejściu program pyta o kolejne współczynniki, po czym sprawdza generator i wypisuje ilość wygenerowanych
wartości oraz wyraz OK, jeśli ta ilość jest równa m. Program kończy się po podaniu za m wartości 0. W programie
wykorzystujemy tablicę dynamiczną.

Lazarus

// Test generatora LCG


// Data : 13.04.2014
// (C)2014 mgr Jerzy Wałaszek
//---------------------------
program LCGTest;
var

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 129 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

m,a,c,x,i,count : cardinal;
T : array of boolean;
begin
while true do
begin
write('m = '); readln(m); // Czytamy moduł
if m = 0 then break; // Jeśli zero, to kończymy
write('a = '); readln(a); // Czytamy mnożnik
write('c = '); readln(c); // Czytamy przyrost
SetLength(T,m); // Tworzymy tablicę dynamiczną
for i := 0 to m - 1 do // Wypełniamy ją zerami
T[i] := false;
x := 0; // Określamy ziarno generatora
for i := 0 to m - 1 do // Generujemy m liczb pseudolosowych
begin
x := (a * x + c) Mod m; // Wyznaczamy kolejną liczbę pseudolosową
T[x] := true; // i umieszczamy ją na swoim miejscu w tablicy
end;
count := 0;
for i := 0 to m - 1 do
if T[i] then inc(count); // Zliczamy wygenerowane liczby
writeln(count); // Wyświetlamy ilość wygenerowanych liczb
if count = m then writeln('OK') // Oceniamy generator
else writeln('---');
writeln;
SetLength(T,0); // Usuwamy tablicę dynamiczną
end;
end.

Code::Blocks

// Test generatora LCG


// Data : 13.04.2014
// (C)2014 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
int main()
{
unsigned int m,a,c,x,i,count;
bool * T;
while(true)
{
cout << "m = "; cin >> m; // Czytamy moduł
if(!m) break; // Jeśli zero, to kończymy
cout << "a = "; cin >> a; // Czytamy mnożnik
cout << "c = "; cin >> c; // Czytamy przyrost
T = new bool [m]; // Tworzymy tablicę dynamiczną
for(i = 0; i < m; i++) // Wypełniamy ją zerami
T[i] = false;
x = 0; // Określamy ziarno generatora
for(i = 0; i < m; i++) // Generujemy m liczb pseudolosowych
{
x = (a * x + c) % m; // Wyznaczamy kolejną liczbę pseudolosową
T[x] = true; // i umieszczamy ją na swoim miejscu w tablicy
}
count = 0;
for(i = 0; i < m; i++)
if(T[i]) count++; // Zliczamy wygenerowane liczby
cout << count << endl; // Wyświetlamy ilość wygenerowanych liczb
if(count == m) cout << "OK\n\n"; // Oceniamy generator

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 130 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

else cout << "---\n\n";


delete [] T; // Usuwamy tablicę dynamiczną
}
return 0;
}

Free Basic

' Test generatora LCG


' Data : 13.04.2014
' (C)2014 mgr Jerzy Wałaszek
'---------------------------
Dim As Uinteger m,a,c,x,i,count
Dim As Byte Ptr T
While 1
Input "m = "; m ' Czytamy moduł
If m = 0 Then Exit While ' Jeśli zero, to kończymy
Input "a = "; a ' Czytamy mnożnik
Input "c = "; c ' Czytamy przyrost
T = New Byte [m] ' Tworzymy tablicę dynamiczną
For i = 0 To m - 1 ' Wypełniamy ją zerami
T[i] = 0
Next
x = 0 ' Określamy ziarno generatora
For i = 0 To m - 1 ' Generujemy m liczb pseudolosowych
x = (a * x + c) Mod m ' Wyznaczamy kolejną liczbę pseudolosową
T[x] = 1 ' i umieszczamy ją na swoim miejscu w tablicy
Next
count = 0
For i = 0 To m - 1
If T[i] = 1 Then count += 1 ' Zliczamy wygenerowane liczby
Next
Print Using "&";count ' Wyświetlamy ilość wygenerowanych liczb
If count = m Then ' Oceniamy generator
Print "OK"
Else
Print"---"
End If
Print
Delete [] T ' Usuwamy tablicę dynamiczną
Wend
End

Wynik
m = 71
a = 72
c = 8
71
OK
m = 111
a = 112
c = 9
37
---

Własności generatorów LCG


Generatory LCG zostały opracowane dosyć dawno i dzisiaj są już nieco przestarzałe. Posiadają szereg istotnych wad, dlatego nie
powinno się ich stosować w aplikacjach, gdzie wymagana jest wysoka przypadkowość danych – np. losowania w grach
liczbowych, symulacje ekonomiczne i finansowe, profesjonalne systemy kryptograficzne itp. Natomiast dla potrzeb amatorskich są
zupełnie wystarczające ze względu na swoją prostotę – dlatego opisujemy je w naszym serwisie.
Jednym z poważnych problemów generatorów LCG jest to, iż młodsze bity generowanych liczb pseudolosowych posiadają krótszy
okres powtarzania niż cała liczba pseudolosowa, jeśli moduł jest potęgą liczby 2 - a tak zwykle jest we wbudowanych
generatorach LCG z uwagi na prostotę wyliczania reszty z dzielenia przez moduł, gdzie dzielenie zastępuje się obcinaniem bitów
wykraczających poza wartość modułu:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 131 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Jeśli m = 2k, to

Xn = (a × Xn-1 + c) mod 2k = (a × Xn-1 + c) obcięte do k bitów.

Najczęściej k = 16 lub 32, co daje wynik bezpośrednio mieszczący się w komórkach pamięci. Procesor wylicza wyrażenie
(a × Xn-1 + c) i wynik umieszcza w 32-bitowym obszarze pamięci, co powoduje automatyczne obcięcie bitów wykraczających
poza 31-szą pozycję.
Poniżej podajemy przykładowe parametry generatorów LCG stosowanych w różnych językach programowania (źródło pochodzi z
Wikipedii):

Źródło m a c Wyjściowe bity ziarna w rand()


lub w Random(L)
Numerical Recipes 232 1664525 1013904223
Borland C/C++ 232 22695477 1 bity 30..16 w rand(), 30..0 w lrand()
GNU Compiler Collection 232 69069 5 bity 30..16
ANSI C: Watcom, Digital Mars, CodeWarrior, IBM VisualAge C/C++ 232 1103515245 12345 bity 30..16
Borland Delphi, Virtual Pascal 232 134775813 1 bity 63..32 w (seed * L)
Microsoft Visual/Quick C/C++ 232 214013 2531011 bity 30..16
ANSIC 231 1103515245 12345
MINSTD 231-1 16807 0
RANDU 231 65539 0
SIMSCRIPT 231-1 630360016 0
BCSLIB 235 515 7261067085
BCPL 232 2147001325 715136305
URN12 231 452807053 0
APPLE 235 1220703125 0
Super-Duper 232 69069 0

Liczby pseudolosowe w przedziale


Problem
Mając dany generator LCG należy wygenerować serie liczb pseudolosowych w przedziale całkowitym <x,y>.

Jest to typowe zadanie losowania wartości pseudolosowej. Załóżmy, iż tworzymy program gry w kości. W grze będziemy losować
wyniki rzutów kostką będące liczbami pseudolosowymi z przedziału całkowitego <1,6>. Na pierwszy rzut oka wygląda na to, iż
problem rozwiążemy tworząc generator LCG generujący liczby od 1 do 6 (może to być generator multiplikatywny). Odradzam to
rozwiązanie – okres powtarzania takiego generatora jest bardzo krótki i ze względu na własności generatorów LCG po każdych 6
rzutach kostką otrzymywalibyśmy wciąż te same wyniki – przyznasz, że gra straciłaby wiele na atrakcyjności.
Problem rozwiązujemy inaczej – tworzony jest generator LCG o bardzo dużym okresie m - np. m = 264. Następnie wygenerowaną
liczbę pseudolosową dzielimy przez 6 i bierzemy resztę z tego dzielenia. Otrzymamy liczby od 0 do 5. Sekwencje tych liczb będą
się powtarzały z okresem 264! (przynajmniej teoretycznie jeśli 6 nie dzieli modułu generatora!) Aby sprowadzić wynik do przedziału
<1,6> musimy do otrzymanej reszty dodać 1.
Zapiszmy to tak:

X ← (a × X + c) mod m
W ← (X mod 6) + 1
X – liczba pseudolosowa
m,a,c – współczynniki generatora LCG
W – wynik losowania rzutu kostką

Powyższy wzór możemy w prosty sposób uogólnić na dowolny przedział całkowity <x,y>. W tym celu obliczamy długość
przedziału plus jeden:

Lxy ← y - x + 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 132 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Liczba Lxy stanie się dzielnikiem wygenerowanej przez generator liczby pseudolosowej X. Otrzymaną z dzielenia resztę należy
zwiększyć o wartość x, aby wpadała w przedział <x,y>:

Wxy ← (X mod Lxy) + x

Otrzymane w powyższy sposób liczby pseudolosowe mogą cierpieć na zmniejszoną pseudolosowość młodszych bitów w liczbach
generowanych przez generator LCG. Istnieje proste i w miarę skuteczne rozwiązanie tego problemu. Liczbę X dzielimy przez m
zmiennoprzecinkowo i zapamiętujemy wynik, który jest rzeczywistą liczbą pseudolosową z przedziału <0,1):

XR ← X : m

Następnie liczbę XR wymnażamy przez Lxy i jako wynik bierzemy część całkowitą tego iloczynu powiększoną o x:

Wxy ← [XR × Lxy] + x

Ponieważ teraz przy tworzeniu Wxy są brane pod uwagę wszystkie bity X (a nie tylko te młodsze z reszty z dzielenia, które mają
niską przypadkowość), wynik jest "bardziej" pseudolosowy niż w pierwszym rozwiązaniu.
Pozostaje jeszcze jeden, bardzo istotny problem. Generator LCG startując od zadanego ziarna X0 zawsze tworzy ten sam ciąg
liczb pseudolosowych. Wynika z tego, iż nasz program powinien przy każdym uruchomieniu wybierać inne ziarno, w przeciwnym
razie wylosowane liczby będą się powtarzały – np. zawsze gracze będą wyrzucali te same sekwencje oczek lub będą otrzymywali
te same kolejne układy kart w grach losowych przy każdym uruchomieniu programu – w końcu nauczą się ich na pamięć!. W
komputerach IBM-PC mamy do dyspozycji tzw. zegar czasu rzeczywistego (ang. RTC – Real Time Clock), który zlicza czas –
dzięki niemu komputer IBM-PC utrzymuje poprawną datę i czas systemowy. Czas zliczany jest z dokładnością do milisekund.
Przy każdym uruchomieniu programu odczytujemy stan zegara i wykorzystujemy go jako wartość dla ziarna pseudolosowego
generatora LCG. W ten sposób ziarno jest każdorazowo inne (nie wiadomo przecież z góry, w jakim czasie użytkownik będzie
uruchamiał swój program), zatem sekwencja liczb pseudolosowych wystartuje od innego punktu. W efekcie otrzymamy
nieprzewidywalne z góry ciągi pseudolosowe – i o to właśnie chodzi.

Algorytm generacji liczb pseudolosowych


Wejście
x, y – początek i koniec przedziału generacji liczb pseudolosowych, x,y C
LCG(m,a,c) – generator LCG
Wyjście:
Wxy liczba pseudolosowa z przedziału <x,y>.

Elementy pomocnicze:
Lxy – długość przedziału <x,y> + 1
X – ziarno pseudolosowe oraz liczba pseudolosowa
XR – rzeczywista liczba pseudolosowa z przedziału <0,1)

Lista kroków:
K01: X ← czas z zegara RTC ; tworzymy przypadkowe ziarno generatora LCG
K02: Powtarzaj wymaganą liczbę razy kroki ; w pętli generujemy pożądaną ilość liczb
K03...K07 pseudolosowych
K03: X ← (a × X + c) mod m ; generujemy liczbę pseudolosową
K04: XR ← X : m ; tworzymy rzeczywistą liczbę pseudolosową
K05: Lxy ← y - x + 1 ; obliczamy długość przedziału <x,y> plus 1
K06: Wxy ← XR × Lxy + x ; obliczamy liczbę pseudolosową w <x,y>
K07: Przetwarzaj Wxy ; wykorzystujemy wygenerowaną liczbę
pseudolosową
K08: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 133 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Program odczytuje z pierwszego wiersza trzy liczby x,y oraz n. Liczby x i y określają przedział całkowity, w którym mają być
wygenerowane liczby pseudolosowe. Liczba n określa ile liczb pseudolosowych należy wygenerować. Jako generator LCG
stosujemy generator zaproponowany przez prof. Donalda Knutha o następujących parametrach:

LCG(34359738368, 3141592653, 2718281829, Xo)

Do wykonywania mnożenia i dodawania stosujemy procedury mnożenia i dodawania modulo, które opisaliśmy w rozdziale o
chińskim teście pierwszości. Dzięki nim rachunki nie przekroczą zakresu liczb 64-bitowych.

Lazarus

// Liczby pseudolosowe w <x,y>


// Data : 12.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
program prg;
uses SysUtils,DateUtils;
// Zmienne globalne dla LCG
//-------------------------
const a = 3141592653;
c = 2718281829;
m = 34359738368;
var X0 : qword;
// Procedura inicjuje ziarno X
//----------------------------
procedure Uprzypadkowij;
var
t : TDateTime;
begin
t := Now;
X0 := HourOf(t);
X0 := X0 * 60 + MinuteOf(t);
X0 := X0 * 60 + SecondOf(t);
X0 := X0 * 1000 + MillisecondOf(t);
end;
// Funkcja oblicza a * b mod n
//----------------------------
function Mnoz(a,b,n : qword) : qword;
var
m,w : qword;
begin
w := 0; m := 1;
while m <> 0 do
begin
if b and m <> 0 then w := (w + a) mod n;
a := (a shl 1) mod n;
m := m shl 1;
end;
Mnoz := w;
end;
// Funkcja generatora LCG
//-----------------------
function LCG() : qword;
begin
X0 := Mnoz(X0,a,m);
X0 := (X0 + c) mod m;
LCG := X0;
end;
var
Wxy,Lxy,x,y : qword;
i,n : integer;
XR : double;
begin
Uprzypadkowij;
readln(x,y,n);
Lxy := y - x + 1;
for i := 1 to n do
begin
XR := LCG / m;
Wxy := trunc(XR * Lxy) + x;
write(Wxy,' ');
end;
writeln;
end.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 134 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Code::Blocks

// Liczby pseudolosowe w <x,y>


// Data : 12.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------
#include <iostream>
#include <cmath>
using namespace std;
typedef unsigned long long ulong;
// Zmienne globalne dla LCG
//-------------------------
const ulong a = 3141592653ull;
const ulong c = 2718281829ull;
const ulong m = 34359738368ull;
ulong X0;
// Procedura inicjuje ziarno X
//----------------------------
void Uprzypadkowij()
{
X0 = (ulong)time(NULL);
}
// Funkcja oblicza a * b mod n
//----------------------------
ulong Mnoz(ulong a, ulong b, ulong n)
{
ulong m,w;
w = 0; m = 1;
while(m)
{
if(b & m) w = (w + a) % n;
a = (a << 1) % n;
m <<= 1;
}
return w;
}
// Funkcja generatora LCG
//-----------------------
ulong LCG()
{
X0 = Mnoz(X0,a,m);
X0 = (X0 + c) % m;
return X0;
}
int main()
{
ulong Wxy,Lxy,x,y;
int n;
double XR;
Uprzypadkowij();
cin >> x >> y >> n;
Lxy = y - x + 1;
for(int i = 1; i <= n; i++)
{
XR = LCG() / (double)m;
Wxy = (ulong)floor(XR * Lxy) + x;
cout << Wxy << " ";
}
cout << endl << endl;
return 0;
}

Free Basic

' Liczby pseudolosowe w <x,y>


' Data : 12.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------
' Zmienne globalne dla LCG
'-------------------------
Dim Shared a As Ulongint = 3141592653
Dim Shared c As Ulongint = 2718281829
Dim Shared m As Ulongint = 34359738368

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 135 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Dim Shared X0 As Ulongint


' Procedura inicjuje ziarno X
'----------------------------
Sub Uprzypadkowij()
X0 = Culngint(Timer * 1000) Mod m
End Sub
' Funkcja oblicza a * b mod n
'----------------------------
Function Mnoz(Byval ax As Ulongint, Byval bx As Ulongint, Byval n As Ulongint) As Ulongint
Dim As Ulongint mx,w
w = 0: mx = 1
While mx
If bx And mx Then w = (w + ax) Mod n
ax = (ax Shl 1) Mod n
mx = mx Shl 1
Wend
Mnoz = w
End Function
' Funkcja generatora LCG
'-----------------------
Function LCG() As Ulongint
X0 = Mnoz(X0,a,m)
X0 = (X0 + c) Mod m
LCG = X0
End Function
' Zaokrągla w dół
'----------------
Function Floor(Byval x As Double) As Ulongint
Dim result As Ulongint
result = Culngint(x)
If result > x Then result -= 1
Floor = result
End Function
Dim As Ulongint Wxy,Lxy,x,y
Dim As Integer i,n
Dim As Double XR
Uprzypadkowij()
Input x,y,n
Lxy = y - x + 1
For i = 1 To n
LCG()
XR = X0 / m
Wxy = Floor(XR * Lxy) + x
Print Wxy;" ";
Next
Print
End

Wynik
1 6 40
4 5 4 2 4 6 2 4 4 3 6 5 1 5 2 3 6 5 6 1 5 5 5 3 4 2 2 6 3 6 5 6 2 5 3 5 4 1 4 3
1 6 40
3 1 5 1 3 4 5 1 5 2 4 5 6 3 5 2 5 2 3 6 2 1 3 1 2 3 4 5 4 2 3 6 2 2 3 4 3 5 4 2
1 6 40
3 3 2 1 5 2 1 2 5 5 4 3 1 5 5 3 3 6 5 1 3 5 1 6 6 6 4 2 4 2 4 5 2 4 6 3 1 4 2 2

Liczby pseudolosowe w przedziale <x,y>


(C)2012 mgr Jerzy Wałaszek
x= 1,y= 6 , n= 40

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 136 / 519


Algorytmy i Struktury Danych - Liniowe generatory liczb pseudolosowych 2014-10-03

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0020.php 137 / 519


Algorytmy i Struktury Danych - Generator LCG Parka-Millera 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Generator pseudolosowy Parka-Millera


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Parka-Millera
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Generator pseudolosowy Park-Miller jest odmianą multiplikatywnego liniowego generatora kongruencyjnego. Ogólny wzór generacyjny ma
poniższą postać:

Xi = Xi-1 × g mod n
Gdzie:
Xi – generowana, nowa liczba pseudolosowa
Xi-1 – poprzednio wygenerowana liczba pseudolosowa
g – pierwiastek pierwotny modulo n
n – liczba pierwsza lub jej potęga
X0 – ziarno pseudolosowe musi być względnie pierwsze z n

Pierwiastek pierwotny modulo n (ang. primitive root modulo n) jest dowolną liczbą g o własności takiej, iż każda liczba względnie pierwsza z
n przystaje do do pewnej potęgi g modulo n. Innymi słowy, jeśli g jest pierwiastkiem pierwotnym modulo n i NWD(a,n) = 1, to istnieje taka liczba
całkowita k, iż gk mod n = a.
Generator P-M posiada okres powtarzania równy n-1. Można go traktować jako szczególny przypadek multiplikatywnego generatora LCG,
jednakże współczynniki g i n oraz X0 muszą spełniać powyżej podane warunki. Oto kilka par wartości n i g, które są stosowane w praktyce:

Lp. n g
1 216+1=65537 (liczba pierwsza Fermata F4) 75 (pierwiastek pierwotny modulo F4)

2 231-1 = 2147483647 (liczba pierwsza Mersenne'a M31) 16807 (pierwiastek pierwotny modulo M31)
3 248 = 281474976710656 44485709377909
4 4294967291 279470273

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0021.php 138 / 519


Algorytmy i Struktury Danych - Generator LCG Parka-Millera 2014-10-03

Wykorzystując pozycję nr 2, tj. n = 231-1 i g = 16807, można skonstruować generator LCG, w którym operację mnożenia modulo n zastąpimy
prostymi działaniami na bitach. Ma to sens dla małych mikroprocesorów, np. kontrolerów jednoukładowych, w których albo brak jest operacji
dzielenia z resztą, albo jest ona kosztowna czasowo.

Algorytm generatora pseudolosowego Park-Miller


Wejście
X0 – ziarno pseudolosowe

Wyjście:

X0 – liczba pseudolosowa z zakresu od 1 do 231 - 1.

Zmienne pomocnicze
XLO – dolna połówka iloczynu
XHI – górna połówka iloczynu

Lista kroków:

K01: XLO ← 16807 × (X0 and $FFFF) ; obliczamy dolną połówkę iloczynu
K02: XHI ← 16807 × (X0 shr 16) ; obliczamy górną połówkę iloczynu
X
K03: LO ← X LO + ((XHI and $7FFF) shl 16) ; łączymy dolną połówkę z górną
K04: XLO ← XLO + (XHI shr 15)
K05: Jeśli XLO > $7FFFFFFF, to XLO ← XLO - $7FFFFFFF ; korekcja wyniku
K07: X0 ← XLO ; wynik zapamiętujemy w ziarnie
K08: Zakończ z wynikiem X0

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób
korzystania z nich.

Program odczytuje z pierwszego wiersza trzy liczby a,b oraz n. Liczby a i b określają przedział całkowity, w którym mają być
wygenerowane liczby pseudolosowe. Liczba n określa ile liczb pseudolosowych należy wygenerować. Liczby pseudolosowe są
generowane w kolejnych wierszach. Ponieważ okres n jest liczbą pierwszą, to każda liczba X0 mniejsza od n i większa od 0 jest
względnie pierwsza z n i może być użyta na ziarno pseudolosowe. X0 inicjujemy wartością odczytaną z zegara systemowego.

Lazarus

// Generator pseudolosowy Park-Miller


// Data: 18.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------------
program prg;
uses SysUtils,DateUtils;
var x0 : longword;
// Generuje liczby pseudolosowe
//-----------------------------
function PM_RNG : longword;
var
xlo,xhi : longword;
begin
xlo := 16807 * (x0 and $ffff);
xhi := 16807 * (x0 shr 16);
inc(xlo,(xhi and $7fff) shl 16);
inc(xlo,xhi shr 15);
if xlo > $7fffffff then dec(xlo,$7fffffff);
x0 := xlo;
PM_RNG := xlo;
end;
// Ustawia losowe x0
//------------------
procedure Uprzypadkowij;
var

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0021.php 139 / 519


Algorytmy i Struktury Danych - Generator LCG Parka-Millera 2014-10-03

t : TDateTime;
begin
repeat
t := Now;
x0 := HourOf(t);
x0 := x0 * 60 + MinuteOf(t);
x0 := x0 * 60 + SecondOf(t);
x0 := x0 * 1000 + MillisecondOf(t);
until x0 <> 0;
end;
var
a,b,n,i : longint;
begin
readln(a,b,n);
Uprzypadkowij;
for i := 1 to n do write(a + PM_RNG mod (b - a + 1),' ');
writeln;
end.

Code::Blocks

// Generator pseudolosowy Park-Miller


// Data: 18.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------------
#include <iostream>
using namespace std;
unsigned int x0;
// Generuje liczby pseudolosowe
//-----------------------------
unsigned int PM_RNG()
{
unsigned int xlo,xhi;
xlo = 16807 * (x0 & 0xffff);
xhi = 16807 * (x0 >> 16);
xlo += (xhi & 0x7fff) << 16;
xlo += xhi >> 15;
if(xlo > 0x7fffffff) xlo -= 0x7fffffff;
return (x0 = xlo);
}
// Ustawia losowe x0
//------------------
void Uprzypadkowij()
{
do x0 = (unsigned int) time(NULL); while(!x0);
}
int main()
{
unsigned int a,b,n,i;
cin >> a >> b >> n;
Uprzypadkowij();
for(i = 1; i <= n; i++) cout << (a + PM_RNG() % (b - a + 1)) << " ";
cout << endl;
return 0;
}

Free Basic

' Generator pseudolosowy Park-Miller


' Data: 18.04.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------------
Dim Shared x0 As Uinteger
' Generuje liczby pseudolosowe
'-----------------------------
Function PM_RNG As Uinteger
Dim As Uinteger xlo,xhi
xlo = 16807 * (x0 And &HFFFF)
xhi = 16807 * (x0 Shr 16)
xlo += (xhi And &H7FFF) Shl 16
xlo += xhi Shr 15
If xlo > &H7FFFFFFF Then xlo -= &H7FFFFFFF
x0 = xlo
PM_RNG = xlo

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0021.php 140 / 519


Algorytmy i Struktury Danych - Generator LCG Parka-Millera 2014-10-03

End Function
' Ustawia losowe x0
'------------------
Sub Uprzypadkowij
Do
x0 = Timer * 1000
Loop Until x0
End Sub
Dim As Integer a,b,n,i
Open Cons For Input As #1
Input #1,a,b,n
Close #1
Uprzypadkowij
For i = 1 To n: Print a + PM_RNG Mod (b - a + 1);: Next
Print
End

Wynik
1 6 200
3 2 1 5 2 6 2 2 2 3 2 5 4 5 2 2 6 4 3 6 6 5 3 5 5 5 3 6 5 1 6 5 3 4 1 1 1 1 3 6
2 4 2 6 3 6 3 5 1 2 6 4 4 5 1 1 3 2 4 4 6 4 6 5 1 1 6 2 4 5 5 4 3 4 2 4 6 3 6 5
1 4 3 5 5 4 6 3 5 3 2 6 5 2 4 3 6 6 6 5 4 2 1 3 1 3 5 3 1 5 4 1 2 2 3 3 6 6 5 2
4 1 3 3 4 2 6 2 6 6 2 1 5 5 2 1 4 6 4 1 4 3 2 1 4 1 4 3 3 5 3 6 2 5 5 1 4 2 3 3
1 3 4 4 1 2 3 5 5 3 1 5 2 6 6 2 1 5 3 2 6 4 6 6 2 3 4 3 4 3 3 3 6 4 4 4 5 5 1 5

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0021.php 141 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Generator pseudolosowy Mersenne Twister


Tematy pokrewne
Przedziały liczbowe i liczby
Liczby parzyste i nieparzyste
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Opisane w poprzednim rozdziale liniowe generatory kongruencyjne liczb pseudolosowych posiadają wiele różnych wad eliminujących je z
niektórych zastosowań. W celu usunięcia tych niedogodności, w roku 1997 dwaj japończycy, Makoto Matsumoto i Takuji Nishimura,
opracowali nowy rodzaj generatora liczb pseudolosowych o nazwie Mersenne Twister – w skrócie MT. Generator oparty został na liniowej
rekurencji macierzowej (ang. matrix linear recursion) w skończonej dziedzinie binarnej (ang. finite binary field). Umożliwia on generację
wysokiej jakości liczb pseudolosowych o bardzo dużym okresie powtarzania, będącym liczbą pierwszą Mersenne'a.
Liczby Mersenne'a są potęgami liczby 2 pomniejszonymi o 1:

Mn = 2n - 1

Oto kilka z nich:

1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, ...

Liczbą pierwszą Mersenne'a (ang. Mersenne Prime) nazywamy liczbę Mersenne'a, która jest pierwsza. Nie wszystkie liczby Mersenne'a są
liczbami pierwszymi. Jedno z założeń mówi, iż liczba Mersenne'a może być liczbą pierwszą tylko wtedy, gdy wykładnik n sam jest liczbą
pierwszą. Jeśli wykładnik jest liczbą złożoną, to 2n - 1 nie jest pierwsze. Pozwala to szybko eliminować z ciągu liczb Mersenne'a liczby złożone.
Jednakże pozostałe liczby należy sprawdzać testem pierwszości, gdyż nie ma pewności, iż są one rzeczywiście pierwsze. Na przykład dla
n = 11 otrzymujemy:

211 - 1 = 2047 = 23 × 89 – liczba złożona

Zadanie to jest trudne obliczeniowo, ponieważ liczby Mersenne'a bardzo szybko rosną. Na dzień dzisiejszy największą znaną liczbą Mersenne'a
jest:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 142 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

232582657 - 1

Jest to 44-ta liczba pierwsza Mersenne'a i posiada 9808358 cyfr dziesiętnych. Znaleziono ją 4 września 2006 roku dzięki projektowi GIMPS (ang.
Great Internet Mersenne Prime Search), który polegał na rozproszonym (tj. wykorzystującym moc obliczeniową komputerów podłączonych do
sieci Internet) sprawdzaniu pierwszości liczb Mersenne'a.
Omawiany tutaj generator MT wykorzystuje 24-tą liczbę Mersenne'a o wartości 19937 – stąd bierze się jego oznaczenie MT19937. Generator
posiada olbrzymi okres powtarzania równy 219937 - 1. Ponieważ teoria będąca podstawą działania generatora MT jest bardzo skomplikowana
matematycznie (wykracza poza poziom liceum), ograniczymy się jedynie do podania samego algorytmu, bez wnikania w podstawy
matematyczne jego działania. Wszystkie opisane tutaj fakty pochodzą z oryginalnej publikacji:

Mersenne Twister: A 623-dimensionally equidistributed uniform pseudorandom


number generator
Makoto Matsumoto and Takuji Nishimura
Keio University/Max Planck Institut für Mathematik
Keio University
Cały dokument możesz pobrać stąd.

Generator MT jest jednym, dużym rejestrem przesuwnym ze sprzężeniami zwrotnymi powodującymi rotację oraz mieszanie się bitów. Rejestr
ma długość 19937 bitów (liczba Mersenne), które w pamięci zajmują 624 słowa 32-bitowe. Generowanie liczb pseudolosowych składa się z
trzech etapów.

1. Pierwszy etap polega na zainicjowaniu 624 słów 32-bitowych rejestru wartościami pseudolosowymi. Do tego celu można wykorzystać
zwykły liniowy generator LCG (my wykorzystujemy generator Super-Duper LCG(232,69069,0,X0)).
2. W drugim etapie generujemy liczbę pseudolosową na podstawie zawartości rejestru. Rejestr jest odpowiednio modyfikowany przy
tworzeniu każdej nowej liczby pseudolosowej.
3. W trzecim etapie wygenerowaną liczbę odpowiednio dopasowujemy, aby otrzymać równomierny rozkład bitów.
Algorytm inicjowania rejestru MT
Wejście
MT – tablica 624 liczb 32-bitowych przechowująca 19937 bitów rejestru przesuwnego
X0 – ziarno dla generatora LCG(4294967296,69069,0), który posłuży do generacji zawartości rejestru przesuwnego.
Wartość tę można pozyskiwać np z zegara czasu rzeczywistego.
Wyjście:
Tablica MT wypełniona liczbami pseudolosowymi
Zmienna pomocnicza
i – indeksuje kolejne liczby tablicy MT, i N
Lista kroków:

K01: MT[0] ← X0 and $FFFFFFFF ; ziarno ustawiamy w pierwszym elemencie


K02: Dla i = 1,2,...,623 wykonuj K03
K03: MT[i] ← (69069 × MT[i-1]) and $FFFFFFFF ; kolejne elementy wypełniamy liczbami pseudolosowymi
K04: Zakończ

Algorytm generatora pseudolosowego Mersenne Twister


Wejście
MT – tablica 624 liczb 32-bitowych przechowująca 19937 bitów rejestru przesuwnego
mti – indeks, wg którego algorytm wybiera elementy MT do modyfikacji oraz do generacji wartości wyjściowej. Jest to
zmienna statyczna, która zachowuje swoją wartość pomiędzy kolejnymi wywołaniami funkcji generującej liczby
pseudolosowe. Powinna być zainicjowana wartością 0.
Wyjście:

Liczba pseudolosowa z zakresu od 0 do 232 - 1.


Zmienne pomocnicze
MA – dwuelementowa tablica, która służy do mnożenia elementów MT przez wybraną macierz.
A[0] = 0
A[1] = $9908B0DF
mti – indeks, wg którego algorytm wybiera elementy MT do modyfikacji oraz do generacji wartości wyjściowej. Jest
to zmienna statyczna, która zachowuje swoją wartość pomiędzy kolejnymi wywołaniami funkcji generującej
liczby pseudolosowe. Powinna być zainicjowana wartością 0.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 143 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

y – generowana liczba pseudolosowa


Lista kroków:
y ← (MT[mti] and $80000000) or (MT[(mti + 1) mod 624] and
K01: ; łączymy dwa wyrazy z MT
$7FFFFFFF)
K02: MT[mti] ← MT[(mti + 397) mod 624] xor (y shr 1) xor MA[y and 1] ; tworzymy sprzężenie zwrotne
K03: y ← MT[mti] ; wyznaczamy liczbę pseudolosową
K04: y ← y xor (y shr 11)
K05: y ← y xor ((y shl 7) and $9D2C5680)
K07: y ← y xor ((y shl 15) and $EFC60000)
K08: y ← y xor (y shr 18)
K09: mti ← (mti + 1) mod 624 ; obliczamy następny indeks
K10: Zakończ z wynikiem y

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz sposób
korzystania z nich.

Program odczytuje z pierwszego wiersza trzy liczby a,b oraz n. Liczby a i b określają przedział całkowity, w którym mają być
wygenerowane liczby pseudolosowe. Liczba n określa ile liczb pseudolosowych należy wygenerować. Liczby pseudolosowe są
generowane w kolejnych wierszach.

Lazarus

// Generator Mersenne Twister


// Data : 16.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
uses SysUtils,DateUtils;
var
MT : array[0..623] of longword;
mti : integer;
// Inicjuje MT[]
//--------------
procedure InicjujMT(x0 : longword);
var
i : integer;
x : qword;
begin
MT[0] := x0;
for i := 1 to 623 do
begin
x := MT[i-1];
x := (23023 * x) and $00000000ffffffff;
x := ( 3 * x) and $00000000ffffffff;
MT[i] := x;
end;
mti := 0;
end;
// Inicjuje MT wartościami losowymi
//---------------------------------
procedure UprzypadkowijMT;
var
t : TDateTime;
x0 : longword;
begin
t := Now;
x0 := HourOf(t);
x0 := x0 * 60 + MinuteOf(t);
x0 := x0 * 60 + SecondOf(t);
x0 := x0 * 1000 + MillisecondOf(t);
InicjujMT(x0);
end;
// Generator Mersenne Twister
//--------------------------
function MersenneTwister : longword;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 144 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

const
MA : array[0..1] of longword = (0,$9908b0df);
var
y : longword;
i1,i397 : integer;
begin
i1 := mti + 1; if i1 > 623 then i1 := 0;
i397 := mti + 397; if i397 > 623 then dec(i397,624);
y := (MT[mti] and $80000000) or (MT[i1] and $7fffffff);
MT[mti] := MT[i397] xor (y shr 1) xor MA[y and 1];
y := MT[mti];
y := y xor ( y shr 11);
y := y xor ((y shl 7) and $9d2c5680);
y := y xor ((y shl 15) and $efc60000);
y := y xor ( y shr 18);
mti := i1;
MersenneTwister := y;
end;
var
a,b,i,n : longint;
begin
UprzypadkowijMT;
readln(a,b,n);
for i := 1 to n do write(a + (MersenneTwister mod (b - a + 1)),' ');
writeln;
end.

Code::Blocks

// Generator Mersenne Twister


// Data : 16.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
typedef unsigned long long ulong;
unsigned int MT[624];
int mti = 0;
// Inicjuje MT[]
//--------------
void InicjujMT(unsigned int x0)
{
ulong x;
MT[0] = x0;
for(int i = 1; i < 623; i++)
{
x = MT[i-1];
x = (23023 * x) & 0xffffffffull;
x = ( 3 * x) & 0xffffffffull;
MT[i] = x;
}
}
// Inicjuje MT wartościami losowymi
//---------------------------------
void UprzypadkowijMT()
{
InicjujMT((unsigned int)time(NULL));
}
// Generator Mersenne Twister
//--------------------------
unsigned int MersenneTwister()
{
const unsigned int MA[] = {0,0x9908b0df};
long int y;
int i1,i397;
i1 = mti + 1; if( i1 > 623) i1 = 0;
i397 = mti + 397; if(i397 > 623) i397 -= 624;
y = (MT[mti] & 0x80000000) | (MT[i1] & 0x7fffffff);
MT[mti] = MT[i397] ^ (y >> 1) ^ MA[y & 1];
y = MT[mti];
y ^= y >> 11;
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= y >> 18;
mti = i1;
return y;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 145 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

int main()
{
int a,b,i,n;
UprzypadkowijMT();
cin >> a >> b >> n;
for(i = 1; i <= n; i++) cout << (a + (MersenneTwister() % (b - a + 1))) << " ";
cout << endl << endl;
return 0;
}

Free Basic

' Generator Mersenne Twister


' Data : 16.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim Shared MT(623) As Uinteger
Dim Shared mti As Integer = 0
' Inicjuje MT[]
'--------------
Sub InicjujMT(Byval x0 As Uinteger)
Dim i As Integer, x As Ulongint
MT(0) = x0
For i = 1 To 623
x = MT(i-1)
x = (23023 * x) And &HFFFFFFFF
x = ( 3 * x) And &HFFFFFFFF
MT(i) = x
Next
End Sub
' Inicjuje MT wartościami losowymi
'---------------------------------
Sub UprzypadkowijMT()
InicjujMT(Timer * 1000)
End Sub
' Generator Mersenne Twister
'--------------------------
Function MersenneTwister() As Uinteger
Dim MA(1) As Uinteger => {0,&H9908B0DF}
Dim y As Uinteger
Dim As Integer i1,i397
i1 = mti + 1: If i1 > 623 Then i1 = 0
i397 = mti + 397: If i397 > 623 Then i397 -= 624
y = (MT(mti) And &H80000000) Or (MT(i1) And &H7FFFFFFF)
MT(mti) = MT(i397) Xor (y Shr 1) Xor MA(y And 1)
y = MT(mti)
y = y Xor ( y Shr 11)
y = y Xor ((y Shl 7) And &H9D2C5680)
y = y Xor ((y Shl 15) And &HEFC60000)
y = y Xor ( y Shr 18)
mti = i1
MersenneTwister = y
End Function
Dim As Integer a,b,i,n
UprzypadkowijMT()
Open Cons For Input As #1
Input #1,a,b,n
Close #1
For i = 1 To n
Print a + (MersenneTwister() Mod (b - a + 1));
Next
Print
End

Wynik
1 6 200
6 2 3 6 2 5 1 5 4 3 2 3 2 4 1 1 3 6 4 2 4 5 5 4 4 5 3 4 4 3 3 6 1 3 5 1 1 4 4 3
4 3 2 5 1 2 2 1 6 3 1 1 1 3 4 6 6 4 1 5 1 3 4 2 5 1 6 3 2 5 5 5 5 6 5 6 3 2 5 3
5 5 4 6 5 1 5 4 4 6 5 6 1 5 3 1 6 3 4 5 3 6 5 3 5 6 5 5 6 2 2 1 1 4 6 1 5 3 4 5
2 1 1 3 6 1 1 2 4 5 4 5 4 4 1 1 1 1 4 2 3 3 1 1 5 1 2 3 2 1 4 4 6 5 3 6 6 4 2 3

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 146 / 519


Algorytmy i Struktury Danych - Mersene Twister 2014-10-03

2 1 1 3 6 1 1 2 4 5 4 5 4 4 1 1 1 1 4 2 3 3 1 1 5 1 2 3 2 1 4 4 6 5 3 6 6 4 2 3
6 5 1 6 4 1 5 2 2 6 2 1 6 2 6 2 4 3 4 1 6 3 2 6 2 3 3 4 4 1 6 3 2 5 5 3 3 4 2 6

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0022.php 147 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wbudowane generatory liczb pseudolosowych


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Lazarus
Liczby parzyste i nieparzyste Code::Blocks
Liczby podzielne lub niepodzielne przez zadane podzielniki Free Basic
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Współczesne języki programowania posiadają wbudowane generatory liczb pseudolosowych udostępniające programiście swoje funkcje poprzez
prosty interfejs. W tym rozdziale pokażemy, jak się z tych funkcji korzysta we własnych programach.

Lazarus
Inicjalizacja
Ziarno generatora pseudolosowego (ang. random seed) można zainicjować w języku Lazarus na dwa sposoby. W pierwszym
odwołujemy się bezpośrednio do zmiennej przechowującej ziarno:

randseed := nowa wartość dla ziarna pseudolosowego;

Po takiej inicjalizacji generator pseudolosowy będzie produkował zawsze ten sam ciąg liczb pseudolosowych, co można
wykorzystać do prostej, amatorskiej kryptografii (zwykłe generatory LCG nie są zalecane dla profesjonalnych systemów
kryptograficznych).
Drugi sposób wykorzystuje procedurę:

randomize;

Procedura randomize inicjuje randseed wartością odczytaną z zegara systemowego. Ponieważ zegar systemowy w komputerach
IBM-PC działa niezależnie od reszty sprzętu, możemy go potraktować jako źródło wartości losowych – nie wiadomo przecież, w
którym momencie zostanie wywołana procedura randomize. Procedurę tę wywołujemy zwykle tylko jeden raz na samym początku
programu.
Zupełnie bez sensu jest poniższa konstrukcja programowa:

...
for i := 1 to 10 do

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 148 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

begin
randomize;
writeln(random(100));
end;
...

Jeśli komputer działa szybko, to zegar systemowy, z którego procedura randomize pobiera czas, nie zdąży się zmienić w pętli.
Zatem w każdym obiegu pętli ziarno generatora pseudolosowego będzie inicjowane tą samą wartością. W efekcie generator
wyprodukuje tę samą liczbę pseudolosową – aczkolwiek zwykle różną w kolejnych uruchomieniach programu, gdyż czas zegara
będzie wtedy inny. Poniżej mamy wynik działania programu.

Zawartość okna konsoli


81
81
81
81
81
81
81
81
81
81

Poprawnie program powinien wyglądać tak:

...
randomize; // inicjujemy generator pseudolosowy
...
for i := 1 to 10 do // generujemy liczby pseudolosowe
begin
writeln(random(100));
end;
...

Generacja całkowitych liczb pseudolosowych


Do generacji całkowitych liczb pseudolosowych wykorzystujemy funkcję random(n), która zwraca wartość
pseudolosową w zakresie od 0 do n-1. Parametr n może być liczbą 32 bitową longint lub 64 bitową int64. Wtedy
wynik funkcji również jest tego samego typu. Jeśli chcemy wygenerować liczbę pseudolosową z przedziału
domkniętego <a,b>, to stosujemy następujące wyrażenie:

a + random(a - b + 1)

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Przykładowy program generuje 10 liczb pseudolosowych z przedziału <10,20>:

Lazarus

// Wewnętrzny generator pseudolosowy


// Data: 17.04.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------------
program prg;
var i : integer;
begin
randomize;
for i := 1 to 10 do writeln(10 + random(11));
end.

Zawartość okna konsoli


19

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 149 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

19
20
16
19
19
16
11
15
12
18

Generacja rzeczywistych liczb pseudolosowych


Liczby rzeczywiste generuje funkcja random bez parametrów. Zwraca wtedy wartość zmiennoprzecinkową typu
extended (20 cyfr znaczących) z przedziału <0,1) – od 0 domknięty do 1 otwarty.
Jeśli chcemy wygenerować pseudolosową liczbę rzeczywistą z przedziału <a,b) (a domknięty, b otwarty), stosujemy
wzór:

a + random * (b - a)

Jeśli chcemy wygenerować pseudolosową liczbę rzeczywistą z przedziału (a,b> (a otwarty, b domknięty), stosujemy
wzór:

a + (1 - random) * (b - a)

Jeśli chcemy wygenerować pseudolosową liczbę rzeczywistą z przedziału <a,b> (obustronnie domknięty),
stosujemy wzór:

a + random(2147483647) / 2147483646 * (b - a)

Jeśli chcemy wygenerować pseudolosową liczbę rzeczywistą z przedziału (a,b) (obustronnie otwarty), stosujemy
wzór:

a + (1 + random(2147483646)) / 2147483647 * (b - a)

Code::Blocks
Inicjalizacja
Generator pseudolosowy jest inicjowany za pomocą funkcji:

srand(x0);

Funkcja srand() wymaga dołączenia pliku nagłówkowego cstdlib. Argument x0 zostanie użyty jako ziarno generacji
liczb pseudolosowych. Oby otrzymywać w programach bardziej losowe ciągi liczb pseudolosowych, generator
inicjujemy wybraną wartością losową, np. wynikiem funkcji time(), który zmienia się co sekundę. Funkcja time()
wymaga dołączenia pliku nagłówkowego time.h. Na początku programu umieszczamy następujące wywołanie:

...
srand((unsigned)time(NULL));
...

Ponieważ czas jest zliczany niezależnie od procesów obliczeniowych komputera, nie wiadomo, w którym momencie
program zostanie uruchomiony i wywołana będzie funkcja time(). Dlatego jej wynik możemy potraktować jako
losowy. Wpisanie go do ziarna generatora pseudolosowego spowoduje generowanie innej sekwencji liczb
pseudolosowych przy każdym uruchomieniu programu.
Inicjalizację generatora pseudolosowego wykonujemy tylko jeden raz, zawsze na początku programu, przed
generacją liczb pseudolosowych. Nie ma sensu umieszczanie wywołania funkcji srand(time(NULL)) wewnątrz pętli
tworzącej kolejne liczby pseudolosowe – efekt będzie wręcz odwrotny do zamierzonego. Zobacz na podobny
przykład w Pascalu!

Generacja całkowitych liczb pseudolosowych

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 150 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

Dostęp do kolejnych liczb pseudolosowych uzyskujemy za pomocą funkcji rand(), która zwraca wygenerowaną przez
generator liczbę pseudolosową z zakresu od 0 do RAND_MAX (stała RAND_MAX zdefiniowana jest w pliku
nagłówkowym cstdlib i ma wartość 32767 = 215 - 1). Funkcja rand() nie zwraca całej liczby pseudolosowej, tylko jej
górne 15 bitów. Takie rozwiązanie przyjęto dlatego, iż okazuje się, że generatory LCG generują młodsze bity z
mniejszymi okresami powtarzania niż okresy bitów starszych. Zwracanie starszych bitów po części niweluje tę
wadę.
Jeśli wystarcza nam 15 bitowy zakres liczb pseudolosowych, to do generacji liczby pseudolosowej w przedziale
<a,b> stosujemy prosty wzór:

a + rand() % (b - a + 1)

Lepszym rozwiązaniem będzie sprowadzenie wyniku rand() do wartości zmiennoprzecinkowej w przedziale <0,1>, a
następnie wykorzystanie tej wartości do generacji liczby pseudolosowej w przedziale całkowitym <a,b>.

a + (int)(((double)rand() / (double)RAND_MAX) * (b - a))

Jeśli potrzebujemy większego zakresu liczb pseudolosowych niż 15 bitów, to możemy wykorzystać funkcję rand()
kilkakrotnie:

16 bitów: (rand() | (rand() << 15)) & 0xFFFF

20 bitów: (rand() | (rand() << 15)) & 0xFFFFF

24 bity: (rand() | (rand() << 15)) & 0xFFFFFF

32 bity: (rand() | (rand() << 15) | (rand() << 30)) & 0xFFFFFF

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program demonstruje sposób uzyskania 64-bitowych liczb pseudolosowych przy pomocy wbudowanego generatora
pseudolosowego. Lepszym jednakże rozwiązaniem jest zaprojektowanie własnego generatora pseudolosowego o
okresie 64-bitowym, ponieważ jakość tak otrzymanych liczb pseudolosowych nie jest najlepsza.

Code::Blocks

// Generacja 64-bitwych liczb pseudolosowych


// Data: 18.04.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
typedef unsigned long long ulong;
int main()
{
ulong X;
int i;
srand((unsigned)time(NULL));
for(i = 1; i <= 20; i++)
{
X = (ulong)rand()|((ulong)rand()<<15)|((ulong)rand()<<30)|((ulong)rand()<<45)|((ulong)rand()<<60);
cout << setw(20) << X << endl;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 151 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

cout << endl;


return 0;
}

Zawartość okna konsoli


14287740268258102790
18201948344639034413
5668028842739046048
542349849068285525
4472085174507448855
5136086965452494067
16439939500462121
15973381195178743161
11574466793181441461
14401113533160066682
16435464985454005274
5907038298318477603
3722099091437753545
14124984979157357723
16228289200691414138
11044688875893913486
1226176721192551851
3351322725318429551
14137993131048917066
3949030839947032118

Generacja rzeczywistych liczb pseudolosowych


Do generacji rzeczywistych liczb pseudolosowych wynik funkcji rand() sprowadzamy do przedziału {0...1}, a
następnie otrzymaną wartość wykorzystujemy do uzyskania rzeczywistej liczby pseudolosowej. Poniżej podajemy
prosty sposób realizacji tego zadania.

<0,1) : (double)rand() / (double)(RAND_MAX + 1)

<0,1> : (double)rand() / (double)(RAND_MAX)

(0,1> : 1 - (double)rand() / (double)(RAND_MAX + 1)

(0,1) : (double)(1 + rand()) / (double)(RAND_MAX + 2)

Rzeczywistą liczbę pseudolosową z przedziału {0...1} wykorzystujemy do generacji liczby pseudolosowej w


przedziale {a...b} następująco:

a + liczba_pseudolosowa{0...1} * (b - a)

Na przykład chcemy wygenerować liczbę pseudolosową w przedziale od a = 2.5 do b = 4.0 włącznie. Przedział ma
być domknięty, zatem potrzebujemy liczby pseudolosowej z przedziału domkniętego <0,1>. Wzór jest następujący:

2.5 + (double)rand() / (double)(RAND_MAX) * 1.5

Free Basic
Inicjalizacja
Ziarno generatora pseudolosowego inicjujemy za pomocą instrukcji:

Randomize X0

Aby otrzymywać przy każdym uruchomieniu programu inne sekwencje liczb pseudolosowych, jako ziarno stosuje się
wartość licznika czasu:

Randomize Timer

Inicjalizację generatora pseudolosowego wykonuje się jeden raz na samym początku programu. Zobacz na
odpowiednie przykłady dla języka Pascal – w Basicu jest bardzo podobnie.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 152 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

Generacja całkowitych liczb pseudolosowych


W języku Basic istnieje funkcja Rnd, która zwraca tylko rzeczywistą wartość pseudolosową z przedziału <0,1)
(lewostronnie domkniętego, prawostronnie otwartego). Aby otrzymać liczbę pseudolosową z przedziału całkowitego
<a,b>, stosujemy następujące wyrażenie:

a + Int(Rnd * (b - a + 1)) lub a + Cint(Rnd * (b - a))

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wyświetla 15 liczb pseudolosowych z przedziału całkowitego <20,30>.

Free Basic

' Wbudowany generator liczb pseudolosowych


' Data: 18.04.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------------------
Dim i As Integer
Randomize Timer
For i = 1 To 15
Print Int(Rnd * 11) + 20
Next
End

Zawartość okna konsoli


26
23
30
28
26
27
20
27
25
30
25
27
26
22
25

Generacja rzeczywistych liczb pseudolosowych


W języku Basic funkcja Rnd zwraca rzeczywistą liczbę pseudolosową z przedziału <0,1).
Aby uzyskać rzeczywistą liczbę pseudolosową w przedziale <a,b) stosujemy wyrażenie:

a + Rnd * (b - a)

Aby uzyskać rzeczywistą liczbę pseudolosową w przedziale (a,b> stosujemy wyrażenie:

a + (1 - Rnd) * (b - a)

Aby uzyskać rzeczywistą liczbę pseudolosową w przedziale domkniętym <a,b> stosujemy konstrukcję:

If Rnd > 0.5) Then


x = a + Rnd * (b - a)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 153 / 519


Algorytmy i Struktury Danych - Wbudowane generatory pseudolosowe 2014-10-03

Else
x = a + (1 - Rnd) * (b - a)
End If

Aby uzyskać rzeczywistą liczbę pseudolosową w przedziale otwartym (a,b) stosujemy konstrukcję:

Do
x = a + Rnd * (b - a)
Loop Until x <> a

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0023.php 154 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Generowanie liczb pseudolosowych


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Mieszanie pseudolosowe
Liczby parzyste i nieparzyste Losowanie bez powtórzeń
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Dany, skończony zbiór liczb całkowitych pomieszać pseudolosowo.

Mieszanie pseudolosowe
Zadanie mieszania, tasowania (ang. shuffle) zawartości tablicy sprowadza się do wykonywania w pętli zamiany miejscami dwóch
elementów tablicy o wylosowanych indeksach. Pętla musi być wykonana tyle razy, aby tasowanie objęło wszystkie elementy – w
praktyce wystarcza ilość wykonań równa 3n, gdzie n jest ilością elementów.

Algorytm mieszania pseudolosowego


Wejście
n – liczba elementów w tablicy, n N
Z – tablica elementów, indeksy rozpoczynają się od 0
Wyjście:
Tablica Z z potasowaną zawartością
Zmienne pomocnicze
i – licznik obiegów pętli. i N
– przechowuje
element tablicy Z przy zamianie zawartości. Typ ten sam, co elementy tablicy
x
Z.
losowa(x) – funkcja zwracająca liczbę pseudolosową z zakresu od 0 do x - 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 155 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

Lista kroków:
K01: Dla i = 1,2,...,3n: wykonuj K02...K07 ; tasowanie wykonujemy w pętli
K02: i1 ← losowa(n) ; losujemy pierwszy indeks
K03: i2 ← losowa(n) ; losujemy drugi indeks
K04: x ← Z[i1] ; zamieniamy miejscami Z[i1] i Z[i2]
K05: Z[i1] ← Z[i2]
K06: Z[i2] ← x
K07: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program tasuje tablicę 10 elementów całkowitych o kolejnych wartościach od 0 do 9. Tablica jest najpierw
wyświetlana przed tasowaniem, a następnie po tasowaniu.

Lazarus

// Pseudolosowe tasowanie
// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
var Z : array[0..9] of integer = (0,1,2,3,4,5,6,7,8,9);
i1,i2,i,x : integer;
begin
randomize;
for i := 0 to 9 do write(Z[i],' ');
writeln;
for i := 1 to 30 do
begin
i1 := random(10);
i2 := random(10);
x := Z[i1]; Z[i1] := Z[i2]; Z[i2] := x;
end;
for i := 0 to 9 do write(Z[i],' ');
writeln;
end.

Code::Blocks

// Pseudolosowe tasowanie
// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
int Z[] = {0,1,2,3,4,5,6,7,8,9};
int i1,i2,i,x;
srand((unsigned) time(NULL));
for(i = 0; i < 10; i++) cout << Z[i] << " ";
cout << endl;
for(i = 1; i <= 30; i++)
{
i1 = rand() % 10;
i2 = rand() % 10;
x = Z[i1]; Z[i1] = Z[i2]; Z[i2] = x;
}
for(i = 0; i < 10; i++) cout << Z[i] << " ";
cout << endl;
return 0;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 156 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

Free Basic

' Pseudolosowe tasowanie


' Data: 20.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim Z(9) As Integer => {0,1,2,3,4,5,6,7,8,9}
Dim As Integer i1,i2,i
Randomize Timer
For i = 0 To 9: Print Z(i);: Next
Print
For i = 1 To 30
i1 = Int(Rnd * 10)
i2 = Int(Rnd * 10)
Swap Z(i1),Z(i2)
Next
For i = 0 To 9: Print Z(i);: Next
Print
End

Wynik
0 1 2 3 4 5 6 7 8 9
7 2 3 1 6 4 0 9 5 8

Pseudolosowe tasowanie
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Problem
Wylosować z przedziału całkowitego <a,b> n liczb pseudolosowych bez powtórzeń.

Losowanie bez powtórzeń


Zadanie losowania bez powtórzeń można rozwiązać na kilka różnych sposobów. Jeśli przedział <a,b> jest mały (np. zawiera 80
kolejnych liczb Multi-Lotka), to możemy go odwzorować w tablicy, następnie zawartość tej tablicy potasować poprzednio podanym
algorytmem i jako wynik zwrócić pierwsze n elementów. Tasowanie nie dubluje elementów tablicy, zatem otrzymamy n liczb bez
powtórzeń. Taki algorytm posiada klasę złożoności obliczeniowej O(n).
Jeśli przedział <a,b> jest bardzo duży, a n stosunkowo małe, to postępujemy w sposób następujący. Przygotowujemy pustą
tablicę o n elementach. Losujemy liczbę pseudolosową. Jeśli wylosowana liczba jest już w tablicy, to losujemy ponownie dotąd, aż
wylosowanej liczby nie będzie w tablicy. Liczbę dopisujemy do tablicy. Jeśli tablica jest zapełniona, kończymy. W przeciwnym
razie powtarzamy losowanie. Algorytm w takiej postaci posiada optymistyczną klasę złożoności obliczeniowej O(n2).

Algorytm losowania bez powtórzeń


Wejście
n – liczba określająca, ile liczb pseudolosowych bez powtórzeń należy wylosować, n N
a,b – liczby określające całkowity przedział losowania, b - a ≥ n, a,b C

Wyjście:
n różnych od siebie liczb pseudolosowych z przedziału <a,b>
Zmienne pomocnicze
T – tablica przechowująca wylosowane liczby pseudolosowe. Indeksy od 0 do n-1.
i – licznik wylosowanych liczb pseudolosowych. i N
j – wykorzystywane do przeszukiwania tablicy T. j N
x – wylosowana liczba pseudolosowa
losowa(x) – funkcja zwracająca liczbę pseudolosową z zakresu od 0 do x - 1
Lista kroków:
K01: Dla i = 0,1,...,n-1 wykonuj K02...K06
K02: x ← a + losowa(b - a + 1) ; losujemy liczbę pseudolosową

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 157 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

K03: Dla j = 0,1,...,i - 1 wykonuj K04 ; sprawdzamy, czy wylosowana liczba jest w T
K04: Jeśli T[j] = x, to idź do K02 ; jeśli tak, powtarzamy losowanie
K05: T[i] ← x ; jeśli nie, zapamiętujemy w T wylosowaną liczbę
K06: Wyprowadź x ; wyprowadzamy liczbę na wyjście
K07: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje trzy liczby: a,b – krańce przedziału, n – ilość liczb pseudolosowych do
wylosowania. Jeśli długość przedziału <a,b> pozwala na wygenerowanie zadanej ilości różnych liczb
pseudolosowych, to program je generuje i wyświetla w następnym wierszu. W przeciwnym razie wypisuje odpowiedni
komunikat.

Lazarus

// Losowanie bez powtórzeń


// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
type Tintarray = array of integer;
var a,b,i,j,n,x : integer;
T : Tintarray;
test : boolean;
begin
randomize;
readln(a,b,n);
if n <= b - a + 1 then
begin
setlength(T,n);
for i := 0 to n - 1 do
begin
repeat
test := true;
x := a + random(b-a+1);
for j := 0 to i - 1 do
if T[j] = x then
begin
test := false;
break;
end;
until test;
T[i] := x;
write(x,' ');
end;
end
else
writeln('n jest za duze!');
writeln;
end.

Code::Blocks

// Losowanie bez powtórzeń


// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
int a,b,i,j,n,x, * T;
bool test;
srand((unsigned)time(NULL));
cin >> a >> b >> n;
if(n <= b - a + 1)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 158 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

{
T = new int[n];
for(i = 0; i < n; i++)
{
do
{
test = true;
x = a + rand()%(b-a+1);
for(j = 0; j < i; j++)
if(T[j] == x)
{
test = false;
break;
}
} while(!test);
T[i] = x;
cout << x << " ";
}
}
else
cout << "n jest za duze!\n";
cout << endl;
delete [] T;
return 0;
}

Free Basic

' Losowanie bez powtórzeń


' Data: 20.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim As Integer a,b,i,j,n,x,test
Randomize Timer
Input a,b,n
If n <= b - a + 1 Then
Dim T(n-1) As Integer
For i = 0 To n - 1
Do
test = 1
x = a + Int(Rnd*(b-a+1))
For j = 0 To i - 1
If T(j) = x Then
test = 0
Exit For
End If
Next
Loop Until test = 1
T(i) = x
Print x;
Next
Else
Print "n jest za duze!"
End If
Print
End

Wynik
1 80 20
39 79 12 38 80 3 11 33 9 76 19 21 8 14 37 29 24 71 50 1

Losowanie bez powtórzeń


(C)2012 mgr Jerzy Wałaszek

a= 1 , b= 80 , n = 20

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 159 / 519


Algorytmy i Struktury Danych - Generowanie liczb pseudolosowych 2014-10-03

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0024.php 160 / 519


Algorytmy i Struktury Danych - Liczby Fibonacciego 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Liczby Fibonacciego
Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy
Potęgowanie macierzy a liczby Fibonacciego

Problem
Wyznaczyć n-ty wyraz ciągu Fibonacciego.

Leonardo Fibonacci był włoskim matematykiem żyjącym w latach od 1175 do 1250. Jest on autorem specyficznego ciągu
liczbowego, który pojawia się w wielu zastosowaniach informatycznych (i nie tylko). Wyrazy ciągu Fibonacciego definiujemy
rekurencyjnie w sposób następujący:

F0 = 0
F1 = 1
Fi = Fi-2 + Fi-1, dla i > 1

Oto kilka pierwszych wyrazów ciągu Fibonacciego:

0 1 1 2 3 5 8 13 21 34 55 89 ...

Rozwiązanie pierwsze
Rozwiązanie opieramy bezpośrednio na definicji wykorzystując wywołania rekurencyjne. Jest to bardzo złe rozwiązanie (podajemy
je tylko ze względów dydaktycznych), ponieważ algorytm wielokrotnie oblicza wyrazy ciągu, co w efekcie prowadzi do
wykładniczej klasy złożoności obliczeniowej O(2n). Dla dużych n czas obliczeń może sięgać miliardów ... miliardów tysiącleci.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0025.php 161 / 519


Algorytmy i Struktury Danych - Liczby Fibonacciego 2014-10-03

Algorytm generacji liczb Fibonacciego metodą rekurencyjną


Wejście
n – numer liczby ciągu Fibonacciego do wyliczenia, n N

Wyjście:
n-ta liczba ciągu Fibonacciego
Lista kroków funkcji Fibo(n)
K01: Jeśli n ≤ 1, to zwróć n i zakończ ; f0 lub f1
K02: Zwróć Fibo(n - 2) + Fibo(n - 1) i zakończ ; dwa wywołania rekurencyjne

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje n – numer liczby Fibonacciego do wyliczenia. W następnym wierszu
program wypisuje wartość n-tej liczby Fibonacciego. Z uwagi na wykładniczą klasę złożoności obliczeniowej czas
obliczeń szybko rośnie, zatem nie podawaj zbyt dużych n (<45), inaczej nie doczekasz się wyniku lub komputer
zgłosi przepełnienie pamięci.

Lazarus

// Liczby Fibonacciego
// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program fibnum;
function fibo(n : integer) : longword;
begin
if n <= 1 then fibo := n
else fibo := fibo(n - 2) + fibo(n - 1);
end;
var n : integer;
begin
readln(n);
writeln(fibo(n));
writeln;
end.

Code::Blocks

// Liczby Fibonacciego
// Data: 20.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
unsigned long fibo(int n)
{
if(n <= 1) return n;
else return fibo(n - 2) + fibo(n - 1);
}
int main()
{
int n;
cin >> n;
cout << fibo(n) << "\n\n";
return 0;
}

Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0025.php 162 / 519


Algorytmy i Struktury Danych - Liczby Fibonacciego 2014-10-03

' Liczby Fibonacciego


' Data: 20.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Function fibo(Byval n As Integer) As Ulongint
If n <= 1 Then fibo = n: Else fibo = fibo(n - 2) + fibo(n - 1)
End Function
Dim n As Integer
Open Cons For Input As #1
Input n
Close #1
Print fibo(n)
Print
End

Wynik
25
75025

Rozwiązanie drugie
Poprzednie rozwiązanie jest bardzo proste. Niestety wywołania rekurencyjne powodują, iż komputer wielokrotnie oblicza te same
liczby Fibonacciego. W ramach ćwiczeń proponuję dodać w wywołaniu funkcji Fibo() licznik, który zwiększa swój stan o 1 przy
każdym wywołaniu. Na końcu programu, oprócz wartości Fibo(n), wyświetlamy również stan licznika – da nam to pojęcie o ilości
wywołań rekurencyjnych.
Drugie rozwiązanie wykorzystuje zasadę programowania dynamicznego (ang. dynamic programming). Polega ona na tym, iż
rozwiązanie wyższego poziomu obliczamy z rozwiązań otrzymanych na poziomie niższym, które odpowiednio zapamiętujemy.
Dzięki temu podejściu program nie musi liczyć wszystkich składników od początku, wykorzystuje wyniki poprzednich obliczeń. W
efekcie klasa złożoności obliczeniowej algorytmu spadnie do O(n). Jeszcze lepsze rozwiązanie podajemy w rozdziale dotyczącym
potęgowania macierzy.

Algorytm generacji liczb Fibonacciego metodą iteracyjną


Wejście
n – numer liczby ciągu Fibonacciego do wyliczenia, n N

Wyjście:
n-ta liczba ciągu Fibonacciego
Elementy pomocnicze:
f0,f1,f – kolejne trzy liczby Fibonacciego, f0,f1,f C
Lista kroków:
K01: f0 ← 0 ; pierwsza lub fi-2 liczba Fibonacciego
K02: f1 ← 1 ; druga lub fi-1 liczba Fibonacciego
K03: Dla i = 0,1,...,n wykonuj K04...K08
K04: Jeśli i > 1, to idź do K06
K05: f ← i i następny obieg pętli K03
K06: f ← f0 + f1 ; obliczamy kolejną liczbę Fibonacciego
K07 f0 ← f1 ; zapamiętujemy wyniki obliczeń pośrednich
K08: f1 ← f ; dla następnego obiegu pętli
K09: Pisz f
K10: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza numer n liczby Fibonacciego, a w następnym wierszu wyświetla jej
wartość. Z uwagi na ograniczony zakres liczb 64 bitowych, program wylicza dokładnie maksymalnie 93-cią liczbę

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0025.php 163 / 519


Algorytmy i Struktury Danych - Liczby Fibonacciego 2014-10-03

ciągu Fibonacciego.

Lazarus Code::Blocks Free Basic

// Liczby Fibonacciego // Liczby Fibonacciego ' Liczby Fibonacciego


// Data: 21.04.2008 // Data: 21.04.2008 ' Data: 21.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//--------------------------- //--------------------------- '---------------------------
program fibnum; #include <iostream> Dim As Ulongint f,f0,f1
Dim As Integer i,n
var f,f0,f1 : qword; using namespace std;
i,n : integer; f0 = 0
int main() f1 = 1
begin { Input n
f0 := 0; unsigned long long f,f0,f1; For i = 0 To n
f1 := 1; int i,n; If i > 1 Then
readln(n); f = f0 + f1
for i := 0 to n do f0 = 0; f0 = f1
if i > 1 then f1 = 1; f1 = f
begin cin >> n; Else
f := f0 + f1; for(i = 0; i <= n; i++) f = i
f0 := f1; if(i > 1) End If
f1 := f; { Next
end f = f0 + f1; Print f
else f := i; f0 = f1; End
writeln(f); f1 = f;
end. }
else f = i;
cout << f << endl;
return 0;
}

Wynik
93
12200160415121876738

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0025.php 164 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

System liczbowy Fibonacciego


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Obliczanie wartości liczby zapisanej w systemie Fibonacciego
Liczby parzyste i nieparzyste Przeliczanie liczby dziesiętnej na zapis w systemie Fibonacciego
Liczby podzielne lub niepodzielne przez zadane podzielniki
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Znaleźć wartość dziesiętną liczby L(fib) zapisanej w binarnym systemie Fibonacciego.

Liczbowy system pozycyjny pozwala zapisać dowolnie dużą liczbę przy pomocy skończonej ilości cyfr. Wspólną cechą
wszystkich systemów pozycyjnych jest sposób obliczania wartości liczby. Cyfry stoją na pozycjach, które posiadają swoje
numery oraz wagi:

waga pozycji → wn-1 wn-2 ... w2 w1 w0


cyfra → Cn-1 Cn-2 ... C2 C1 C0
numer pozycji → n-1 n-2 ... 2 1 0

Wartość liczby obliczamy zawsze jako sumę iloczynów cyfr przez wagi pozycji, na których te cyfry stoją. Zatem dla powyższego
przypadku wartość liczby pozycyjnej obliczamy ze wzoru:

W = Cn-1 × wn-1 + Cn-2 × wn-2 + ... + C2 × w2 + C1 × w1 + C0 × w0

gdzie

Ci – i-ta cyfra zapisu liczby


wi – waga i-tej pozycji

Przykład:
W systemie trójkowym wagi pozycji są kolejnymi potęgami podstawy tego systemu, czyli liczby 3. Cyfry należą do

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 165 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

W systemie trójkowym wagi pozycji są kolejnymi potęgami podstawy tego systemu, czyli liczby 3. Cyfry należą do
zbioru {0,1,2}. Mając zatem liczbę trójkową 221012 obliczamy jej wartość następująco:

35 34 33 32 31 30
waga pozycji → 243 81 27 9 3 1
cyfra → 2 2 1 0 1 2 = 2 × 243 + 2 × 81 + 1 × 27 + 0 × 9 + 1 × 3 + 2 × 1
numer pozycji → 5 4 3 2 1 0

Po wyliczeniu iloczynów cyfr przez wagi ich pozycji i zsumowaniu tych iloczynów otrzymujemy wynik:

221012(3) = 680(10)

Obliczanie wartości liczby zapisanej w systemie Fibonacciego


W systemie liczbowym Fibonacciego wagi pozycji są kolejnymi liczbami ciągu Fibonacciego, który definiujemy następująco:

f0 = 0
f1 = 1
fi = fi-2 + fi-1, dla i > 1.

Czyli, począwszy od elementu o numerze 2, kolejne wyrazy ciągu Fibonacciego są sumą dwóch wyrazów poprzedzających. Oto
kilka początkowych liczb Fibonacciego:

0 1 1 2 3 5 8 13 21 34 55 89 ...

I dalej zapis liczby w systemie Fibonacciego wygląda następująco:

waga pozycji → fn fn-1 ... f3 f2 f1


cyfra → Cn-1 Cn-2 ... C2 C1 C0
numer pozycji → n-1 n-2 ... 2 1 0

Cyfry w systemie liczbowym Fibonacciego należą do zbioru {0,1}. Ponieważ zbiór taki możemy odwzorować za pomocą bitów –
cyfr binarnych, system liczbowy Fibonacciego jest odmianą systemu binarnego. Mając daną liczbę 100101101(fib) w systemie
Fibonacciego, jej wartość obliczamy następująco:

f f f f f f f f
waga pozycji → 349 218 137 86 55 34 23 f12 11
cyfra → 1 0 0 1 0 1 1 0 1 = 34 + 8 + 3 + 2 + 1 = 48
numer pozycji → 8 7 6 5 4 3 2 1 0

Zwróć uwagę, iż w systemie liczbowym Fibonacciego liczby można zapisywać na kilka równoważnych sposobów:

7(10) = 10100(fib) = 10011(fib) = 1111(fib)

Umówmy się, iż standardowym zapisem będzie ten, w którym liczba cyfr 1 jest najmniejsza.

Obliczanie wartości liczby Fibonacciego rozpoczniemy od wczytania jej cyfr do tablicy znakowej. Następnie idąc od końca zapisu
do początku będziemy tworzyć kolejne liczby Fibonacciego algorytmem dynamicznym i dodawać je do wartości liczby, jeśli
odpowiednia cyfra zapisu będzie ustawiona na 1.

Algorytm obliczania wartości liczby zapisanej w systemie Fibonacciego


Wejście
cn-1cn-2...c2c1c0 - kolejne cyfry zapisu liczby w systemie Fibonacciego

Wyjście:
W – wartość dziesiętna odczytanej liczby w systemie Fibonacciego
Elementy pomocnicze:
i – indeksuje cyfry zapisu liczby w systemie Fibonacciego, i N
f – obliczona, (i+1)-sza liczba Fibonacciego, f N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 166 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

f1,f2 – zmienne zapamiętujące dwie poprzednio wyznaczone liczby Fibonacciego. f1,f2 N

Lista kroków:
K01: W ← 0 ; zerujemy wartość liczby
K02: f1 ← 1 ; początkowe dwie liczby ciągu Fibonacciego
K03: f2 ← 1
K04: Dla i = 0,1, ..., n- 1 wykonuj ; przeglądamy kolejne cyfry zapisu liczby
K05...K11
K05: Jeśli i > 1, to idź do K08 ; najpierw wyznaczamy (i+1)-szą liczbę Fibonacciego
K06: f ← 1 ; dla i = 0 i i = 1 liczba Fibonacciego ma wartość 1
K07: Idź do K11
K08: f ← f1 + f2 ; dla pozostałych i liczbę Fibonacciego obliczamy jako sumę
dwóch
K09: f1 ← f2 ; poprzednich liczb
K10: f2 ← f ; zawsze pamiętamy dwie ostatnie liczby Fibonacciego
K11: Jeśli ci = 1, to W ← W + f ; jeśli cyfra wynosi 1, to liczbę Fibonacciego dodajemy do
wartości liczby
K12: Pisz W ; wypisujemy wynik
K13: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje cyfry zapisu liczby w systemie Fibonacciego. W następnym wierszu
wypisywana jest wartość dziesiętna liczby. Algorytm obliczeniowy reaguje tylko na cyfry 1. Jeśli napotka inny znak,
to zostanie on potraktowany jak cyfra 0.

Lazarus Code::Blocks Free Basic

// System Fibonacciego // System Fibonacciego ' System Fibonacciego


// Data: 22.04.2008 // Data: 22.04.2008 ' Data: 22.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//--------------------------- //--------------------------- '---------------------------
program prg; #include <iostream> Dim c As String
#include <string> Dim As Ulongint w,f,f1,f2
var c : string; Dim As Integer i,n
w,f,f1,f2 : qword; using namespace std;
i,n : integer; Input c
int main() n = Len(c)
begin { w = 0
readln(c); string c; f1 = 1: f2 = 1
n := length(c); unsigned long long w,f,f1,f2; For i = n To 1 Step -1
w := 0; int i,n; If i >= n - 1 Then
f1 := 1; f = 1
f2 := 1; cin >> c; Else
for i := n downto 1 do n = c.length() - 1; f = f1 + f2
begin w = 0; f1 = f2
if i >= n - 1 then f1 = f2 = 1; f2 = f
f := 1 for(i = n; i >= 0; i--) End If
else { If Mid(c,i,1) = "1" Then w += f
begin if(i >= n - 1) f = 1; Next
f := f1 + f2; else Print w
f1 := f2; { End
f2 := f; f = f1 + f2;
end; f1 = f2;
if c[i] = '1' then inc(w,f); f2 = f;
end; }
writeln(w); if(c[i] == '1') w += f;
end. }
cout << w << endl;
return 0;
}

Wynik
1111
7

System liczbowy Fibonacciego


(C)2012 mgr Jerzy Wałaszek
L(fib) = 1111

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 167 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

Wykonaj

...

Problem
Daną, dodatnią liczbę dziesiętną L zapisać w systemie liczbowym Fibonacciego.

Przeliczanie liczby dziesiętnej na zapis w systemie Fibonacciego


Problem sprowadza się do znalezienia sumy:

L = cn-1×fn + cn-2×fn-1 + ... + c2×f3 + c1×f2 + c0×f1


gdzie
ci – cyfry reprezentacji liczby L w systemie Fibonacciego, i = 0,1,...,n-1, ci {0,1}
fi – liczby ciągu Fibonacciego

Zadanie rozwiążemy następująco:

Zbudujemy tablicę TLF, której elementy będą odpowiadały kolejnym liczbom ciągu Fibonacciego. Dla danych 64
bitowych największą liczbą Fibonacciego jest f93 = 12200160415121876738. Jeśli kodujemy wiele liczb w systemie
Fibonacciego, to tablicę TLF tworzymy tylko jeden raz, a później wykorzystujemy ją przy każdej liczbie. Algorytm
jest następujący:
Liczbę L przyrównujemy do kolejnych liczb Fibonacciego w tablicy TLF poczynając od liczby f93 i idąc w dół do f1.
Jeśli liczba fi jest większa od L, to wyprowadzamy cyfrę 0. W przeciwnym razie wyprowadzamy cyfrę 1, a od liczby L
odejmujemy liczbę Fibonacciego fi i operację powtarzamy dla następnej liczby Fibonacciego. Gdy dojdziemy do f1,
otrzymamy wszystkie kolejne cyfry liczby L w systemie Fibonacciego.

Algorytm możemy wyposażyć w usuwanie zer wiodących. W tym celu wystarczy dodać zmienną logiczną, którą ustawiamy na
false. Jeśli w trakcie konwersji dostaniemy cyfrę 1, to zmienną decyzyjną ustawiamy na true. Wyprowadzanie cyfr możemy
uzależnić od stanu tej zmiennej decyzyjnej: false – cyfry nie wyprowadzamy, true – cyfrę wyprowadzamy. Ostatnia cyfra musi być
zawsze wyprowadzona bezwarunkowo – np. gdy L = 0, to konwersja powinna również dać wynik 0.

Algorytm przeliczania liczby dziesiętnej na zapis w systemie Fibonacciego


Wejście
L – przeliczana nieujemna liczba całkowita
Wyjście:
Ciąg cyfr binarnych ci reprezentujących zapis liczby L w systemie pozycyjnym Fibonacciego

Elementy pomocnicze:
TLF – tablica 93 kolejnych liczb Fibonacciego. Elementy są numerowane od 1 do 93.
c – cyfra binarna, c {0,1}
z – znacznik zer wiodących, z {false, true}
i – indeksuje liczby w TLF, i N

Lista kroków:
K01: TLF[1] ← 1 ; w tablicy ustawiamy 2 pierwsze liczby Fibonacciego
K02: TLF[2] ← 1
K03: Dla i = 3,4,...,93: wykonuj K04 ; tablicę wypełniamy kolejnymi liczbami Fibonacciego
K04: TLF[i] ← TLF[i-2] + TLF[i-1]
K05: z ← false ; ustawiamy znacznik wyprowadzania zer wiodących
K06: Dla i = 93, 92,...,1 wykonuj K07... ; w pętli generujemy kolejne cyfry zapisu
K07: Jeśli L ≥ TLF[i], to idź do K10 ; liczby L w systemie pozycyjnym Fibonacciego
K08: c ←0 ; wagi brak w wartościach liczby
K09: Idź do K13
K10: z ← true ; ponieważ mamy cyfrę 1, zerujemy znacznik zer
wiodących
K11: c ←1
K12: L ← L - TLF[i] ; usuwamy wagę z wartości liczby
K13: Jeśli (z = true) (i = 1), to ; wyprowadzamy cyfrę

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 168 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

wyprowadź c
K14: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje liczbę L. W następnym wierszu wypisywany jest zapis tej liczby w
systemie pozycyjnym Fibonacciego.

Lazarus Code::Blocks Free Basic

// System Fibonacciego // System Fibonacciego ' System Fibonacciego


// Data: 23.04.2008 // Data: 23.04.2008 ' Data: 23.04.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//--------------------------- //--------------------------- '---------------------------
program prg; #include <iostream> Dim As Ulongint TLF(93),L
Dim As Integer i,c,z
var using namespace std;
TLF : array[1..93] of qword; TLF(1) = 1
L : qword; int main() TLF(2) = 1
i,c : integer; { For i = 3 To 93
z : boolean; unsigned long long TLF[94],L; TLF(i) = TLF(i-2) + TLF(i-1)
int i,c; Next
begin bool z; Input L
TLF[1] := 1; z = 0
TLF[2] := 1; TLF[1] = TLF[2] = 1; For i = 93 To 1 Step -1
for i := 3 to 93 do for(i = 3; i <= 93; i++) If L >= TLF(i) Then
TLF[i] := TLF[i-2]+TLF[i-1]; TLF[i] = TLF[i-2]+TLF[i-1]; c = 1: z = 1
readln(L); cin >> L; L -= TLF(i)
z := false; z = false; Else
for i := 93 downto 1 do for(i = 93; i >= 1; i--) c = 0
begin { End If
if L >= TLF[i] then if(L >= TLF[i]) If (z = 1) Or (i = 1) Then
begin { Print Using "#";c;
c := 1; z := true; c = 1; z = true; End If
dec(L,TLF[i]); L -= TLF[i]; Next
end } Print
else c := 0; else c = 0; End
if z or (i = 1) then if(z || (i == 1)) cout << c;
write(c); }
end; cout << endl << endl;
writeln; return 0;
end. }

Wynik
1234567890
100000101010010100000001001001000101010001010

Przeliczanie liczby dziesiętnej na zapis w systemie Fibonacciego


(C)2012 mgr Jerzy Wałaszek
L(10) = 1234567890

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 169 / 519


Algorytmy i Struktury Danych - System liczbowy Fibonacciego 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0026.php 170 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Całkowity pierwiastek kwadratowy


Tematy pokrewne Podrozdziały
Przedziały liczbowe i liczby Rozwiązanie 1
Liczby parzyste i nieparzyste Rozwiązanie 2
Liczby podzielne lub niepodzielne przez zadane podzielniki Rozwiązanie 3
Ciągi arytmetyczne
NWD – algorytm Euklidesa
Liczby względnie pierwsze
Najmniejsza wspólna wielokrotność
Odwrotność modulo – rozszerzony algorytm Euklidesa
Liczby pierwsze – generacja przez sprawdzanie podzielności
Liczby pierwsze – generacja sitem Eratostenesa
Liczby pierwsze – generacja sitem liniowym
Liczby pierwsze – generacja sitem Atkina-Bernsteina
Czynniki pierwsze – metoda próbnych dzieleń
Czynniki pierwsze – metoda Fermata
Pierwszość liczby naturalnej – algorytmy naiwne
Pierwszość liczby naturalnej – Chiński Test Pierwszości
Pierwszość liczby naturalnej – Małe Twierdzenie Fermata
Pierwszość liczby naturalnej – test Millera-Rabina
Liniowe generatory liczb pseudolosowych
Generator pseudolosowy Park-Miller
Generator pseudolosowy Mersenne Twister
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
System liczbowy Fibonacciego
Całkowity pierwiastek kwadratowy

Problem
Znaleźć kwadratowy pierwiastek całkowity nieujemnej liczby rzeczywistej x.

Całkowity pierwiastek kwadratowy (ang. integer square root) jest największą liczbą całkowitą p, która spełnia nierówność:

p2 ≤ x

Rozwiązanie nr 1
Problem możemy rozwiązać następująco.

Tworzymy ciąg kwadratów kolejnych liczb całkowitych począwszy od 0:

02 12 22 32 ...i2

do momentu, gdy dla pewnego i otrzymamy spełnioną nierówność i2 > x. Wtedy p = i - 1.

Pozostaje do rozwiązania efektywny sposób tworzenia kwadratów kolejnych liczb całkowitych. Wypiszmy kilkanaście
początkowych wyrazów tego ciągu:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 ...
i2 0 1 4 9 16 25 36 49 64 81 100 121 144 ...

Teraz policzmy ciąg różnic pierwszego rzędu:

r1i = i2 - (i-1)2, dla i > 0.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 171 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

Różnica pierwszego rzędu powstaje przez odjęcie od wyrazu i-tego jego poprzednika w ciągu, czyli wyrazu (i - 1)-szego.

i 0 1 2 3 4 5 6 7 8 9 10 11 12 ...
i2 0 1 4 9 16 25 36 49 64 81 100 121 144 ...
r1 1 3 5 7 9 11 13 15 17 19 21 23 ...

Ciekawa rzecz – różnice pierwszego rzędu dla naszego ciągu tworzą ciąg kolejnych liczb nieparzystych. Teraz analogicznie
utwórzmy ciąg różnic drugiego rzędu:

r2i = r1i - r1(i-1), dla i > 1

Różnice drugiego rzędu powstają w analogiczny sposób z różnic pierwszego rzędu, jak różnice pierwszego rzędu powstają z
wyrazów ciągu – od i-tej różnicy pierwszego rzędu odejmujemy poprzedzającą ją, (i - 1)-szą różnicę.

i 0 1 2 3 4 5 6 7 8 9 10 11 12 ...
i2 0 1 4 9 16 25 36 49 64 81 100 121 144 ...
r1 1 3 5 7 9 11 13 15 17 19 21 23 ...
r2 2 2 2 2 2 2 2 2 2 2 2 ...

Różnice rzędu drugiego tworzą już ciąg stały o wyrazach równych 2. Nie ma sensu liczyć różnic wyższych rzędów, ponieważ
otrzymamy tylko wyrazy równe 0. W tabelce na czerwono zaznaczyliśmy pierwsze wyrazy odpowiednio:

ciągu kwadratów i2 → 0
ciągu różnic pierwszego rzędu r1i → 1
ciągu różnic drugiego rzędu r2i → 2

Mając te trzy wartości możemy rekurencyjnie konstruować ciąg kolejnych kwadratów:

a = 0, r11 = 1, r2 = 2, gdzie a0 – pierwszy wyraz ciągu kwadratów

Dla i > 0 mamy:

r1i = r1(i-1) + r2 – kolejna różnica pierwszego rzędu powstaje z poprzedniej przez dodanie różnicy drugiego rzędu
ai = ai-1 + r1i – kolejny kwadrat powstaje z poprzedniego przez dodanie wyliczonej różnicy pierwszego rzędu

Zwróć uwagę, iż wykorzystujemy tylko dodawanie, dzięki czemu nasz algorytm jest szybki. Jednakże podany algorytm nie jest
stosowany w praktyce do wyznaczania wartości pierwiastka kwadratowego. Podajemy go tutaj tylko ze względów dydaktycznych.

Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 1


Wejście
x – liczba, której pierwiastek obliczamy, x ≥ 0, x R

Wyjście:
całkowity pierwiastek kwadratowy z x
Elementy pomocnicze:
i – numery wyrazów ciągu kwadratów, i C
a – wyraz ciągu kwadratów, a C
r1 – różnica pierwszego rzędu, r1 N
r2 – różnica drugiego rzędu, r2 N

Lista kroków:
K01: a ← 0 ; pierwszy kwadrat 02
K02: r1 ← 1 ; początkowa wartość różnicy pierwszego rzędu
K03: r2 ← 2 ; wartość różnic drugiego rzędu
K04: i ← 0 ; numer pierwszego wyrazu
K05: Dopóki a ≤ x wykonuj K06...K08 ; szukamy pierwszego wyrazu a większego od x
K06: a ← a + r1 ; następny kwadrat
K07: r1 ← r1 + r2 ; wyliczamy nową różnicę pierwszego rzędu
K08: i ← i + 1 ; następny numer
K09: Zakończ z wynikiem i - 1 ; obliczamy pierwiastek całkowity

Program

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 172 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje liczbę x. Następnie wyznacza jej całkowity pierwiastek kwadratowy i
wypisuje go w wierszu drugim. Dodatkowo w wierszu trzecim program wypisuje kwadrat znalezionego pierwiastka
kwadratowego dla celów porównawczych.

Lazarus

// Całkowity pierwiastek
// Data: 10.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
var
x : double;
i,a,r1,r2 : longword;
begin
readln(x);
a := 0; r1 := 1; r2 := 2; i := 0;
while a <= x do
begin
inc(a,r1); inc(r1,r2);
inc(i);
end;
dec(i);
writeln(i);
writeln(i * i);
writeln;
end.

Code::Blocks

// Całkowity pierwiastek
// Data: 10.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
int main()
{
double x;
unsigned int i,a,r1,r2;
cin >> x;
a = 0; r1 = 1; r2 = 2;
for(i = 0; a <= x; i++)
{
a += r1; r1 += r2;
}
i--;
cout << i << endl
<< i * i << endl
<< endl;
return 0;
}

Free Basic

' Całkowity pierwiastek


' Data: 10.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim As Double x
Dim As Uinteger i,a,r1,r2
Input x
a = 0: r1 = 1: r2 = 2: i = 0
While a <= x
a += r1: r1 += r2: i += 1
Wend
i -= 1
Print i
Print i * i
Print
End

Wynik
135
11
121

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 173 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

Obliczanie całkowitego pierwiastka kwadratowego


(C)2012 mgr Jerzy Wałaszek
x= 144

Wykonaj

...

Rozwiązanie nr 2
Druga metoda znajdowania całkowitego pierwiastka kwadratowego pochodzi od Izaaka Newtona (chociaż podobną metodę
stosowali już starożytni Babilończycy). Jeśli mamy pewne przybliżenie p pierwiastka liczby x, to lepsze przybliżenie otrzymamy
stosując wzór:

1 x
2 (p + p )
p=

Dlaczego to działa? Rozważmy dwa przypadki:

p<√ x
Wtedy iloraz x / p jest większy od √ x i po dodaniu go do p i podzieleniu sumy przez 2 otrzymamy liczbę większą od
poprzedniego p, która przybliża się od dołu do rzeczywistego pierwiastka.

p>√ x
Wtedy iloraz x / p jest mniejszy od √ x i po dodaniu go do p i podzieleniu sumy przez 2 otrzymamy liczbę mniejszą
od poprzedniego p, która przybliża się od góry do rzeczywistego pierwiastka.

Wynika z tego, iż w każdej iteracji otrzymujemy liczbę coraz bliższą wartości pierwiastka. Iterujemy dotąd, aż różnica pomiędzy
dwoma kolejnymi przybliżeniami będzie mniejsza lub równa założonej dokładności ε – w przypadku pierwiastków całkowitych jest
to 1.

Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 2


Wejście
x – liczba, której pierwiastek obliczamy, x ≥ 0, x C

Wyjście:
Całkowity pierwiastek kwadratowy z x
Elementy pomocnicze:
p1, p2 – kolejne przybliżenia pierwiastka z x, p1, p2 C

Lista kroków:
K01: Jeśli x > 1, to idź do K04 ; pierwiastki > 1 liczymy
K02: p2 ← x ; inne nie
K03: Idź do K09
K04: p1 ← 0 ; zapewniamy |p1 - p2| > 1
K05: p2 ← x shr 1 ; pierwsze przybliżenie pierwiastka
K06: Dopóki |p1 - p2| > 1, wykonuj K07...K08 ; w pętli wyliczamy kolejne przybliżenia
K07: p1 ← p2 ; zapamiętujemy bieżące przybliżenie
K08: p2 ← (p2 + x div p1) shr 1 ; wyliczamy nowe przybliżenie
K09: Dopóki p2 × p2 > x, wykonuj p2 ← p2 - 1 ; jeśli przybliżenie było od góry, zmniejszamy je
K09: Zakończ z wynikiem p2

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje liczbę x. Następnie wyznacza jej całkowity pierwiastek kwadratowy i
wypisuje go w wierszu drugim. Dodatkowo w wierszu trzecim program wypisuje kwadrat znalezionego pierwiastka
kwadratowego dla celów porównawczych.

Lazarus Code::Blocks Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 174 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

// Całkowity pierwiastek kwadratowy // Całkowity pierwiastek kwadratowy ' Całkowity pierwiastek kwadratowy
// Data: 11.05.2008 // Data: 11.05.2008 ' Data: 11.05.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//--------------------------------- //--------------------------------- '---------------------------------
program prg; #include <iostream> Dim As Integer x,p1,p2
var using namespace std; Input x
x,p1,p2 : longint; If x <= 1 Then
int main() p2 = x
begin { Else
readln(x); int x,p1,p2; p1 = 0: p2 = x Shr 1
if x <= 1 then p2 := x While Abs(p1 - p2) > 1
else cin >> x; p1 = p2
begin if(x <= 1) p2 = x; p2 = (p2 + x \ p2) Shr 1
p1 := 0; p2 := x shr 1; else Wend
while abs(p1 - p2) > 1 do { While p2 * p2 > x: p2 -= 1: Wend
begin p1 = 0; p2 = x >> 1; End If
p1 := p2; while(abs(p1 - p2) > 1) Print p2
p2 := (p2 + x div p2) shr 1; { Print p2 * p2
end; p1 = p2; Print
while p2 * p2 > x do dec(p2); p2 = (p2 + x / p2) >> 1; End
end; }
writeln(p2); while(p2 * p2 > x) --p2;
writeln(p2 * p2); }
writeln; cout << p2 << endl
end. << (p2 * p2) << endl
<< endl;
return 0;
}

Rozwiązanie nr 3
Istnieje bardzo szybki algorytm wyznaczania wartości całkowitego pierwiastka kwadratowego, który wykorzystuje binarną
reprezentację liczb – czyli idealnie nadaje się do zastosowania dla danych komputerowych, które przecież są liczbami binarnymi.
Algorytm wywodzi się z chińskiego abakusa i nie wymaga skomplikowanych działań arytmetycznych – jedynie dodawania oraz
przesuwania bitów. Dzięki tym zaletom może być z powodzeniem stosowany w prostych systemach mikrokontrolerów
jednoukładowych.

Algorytm obliczania całkowitego pierwiastka kwadratowego – wersja nr 3


Wejście
x – liczba, której pierwiastek obliczamy, x ≥ 0, x C

Wyjście:
Całkowity pierwiastek kwadratowy z x
Elementy pomocnicze:
mb – zawiera maskę bitową z ustawionym jednym bitem. Maska jest 64 bitowa.
px – obliczana wartość pierwiastka, px C

Lista kroków:
K01: px ← 0 ; początkowa wartość pierwiastka
K02: mb ← 1 shl 62 ; maska z ustawionym drugim najstarszym bitem
K03: Dopóki mb > x, wykonuj mb ← mb shr 2 ; szukamy najstarszej potęgi 4, mniejszej od x
K04: Dopóki mb ≠ 0, wykonuj K05...K09 ; wyznaczamy kolejne bity pierwiastka
K05: t ← px + mb ; łączymy bit maski z przybliżeniem pierwiastka
K06: Jeśli x < t, idź do K09 ; sprawdzamy, czy dany bit ma być ustawiony.
K07: x ← x - t ; usuwamy bity z x
K08: px ← t + mb ; dodajemy bit maski do px
K09: px ← px shr 1 ; przesuwamy bity pierwiastka o 1 w prawo
K09: mb ← mb shr 2 ; bity maski przesuwamy o 2 w prawo
K10: Zakończ z wynikiem px

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

W pierwszym wierszu program odczytuje liczbę x. Następnie wyznacza jej całkowity pierwiastek kwadratowy i
wypisuje go w wierszu drugim. Dodatkowo w wierszu trzecim program wypisuje kwadrat znalezionego pierwiastka
kwadratowego dla celów porównawczych.

Lazarus Code::Blocks Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 175 / 519


Algorytmy i Struktury Danych - Całkowity pierwiastek kwadratowy 2014-10-03

// Całkowity pierwiastek kwadratowy // Całkowity pierwiastek kwadratowy ' Całkowity pierwiastek kwadratowy
// Data: 11.05.2008 // Data: 11.05.2008 ' Data: 11.05.2008
// (C)2012 mgr Jerzy Wałaszek // (C)2012 mgr Jerzy Wałaszek ' (C)2012 mgr Jerzy Wałaszek
//--------------------------------- //--------------------------------- '---------------------------------
program prg; #include <iostream> Dim As Ulongint x,px,mb,t
var using namespace std; Input x
x,px,mb,t : qword; px = 0: mb = 1 Shl 62
int main() While mb > x: mb = mb Shr 2: Wend
begin { While mb
readln(x); unsigned long long x,px,mb,t; t = px + mb
px := 0; mb := 1 shl 62; If x >= t Then
while mb > x do mb := mb shr 2; cin >> x; x -= t: px = t + mb
while mb <> 0 do px = 0; mb = 1; mb <<= 62; End If
begin while(mb > x) mb >>= 2; px = px Shr 1: mb = mb Shr 2
t := px + mb; while(mb) Wend
if x >= t then { Print px
begin t = px + mb; Print px * px
dec(x,t); px := t + mb; if(x >= t) Print
end; { End
px := px shr 1; mb := mb shr 2; x -= t; px = t + mb;
end; }
writeln(px); px >>= 1; mb >>= 2;
writeln(px * px); }
writeln; cout << px << endl
end. << (px * px) << endl << endl;
return 0;
}

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0027.php 176 / 519


Algorytmy i Struktury Danych - Tablice 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Tablice – wektory
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy

Tablica (ang. array) lub wektor (ang. vector) jest złożoną strukturą danych (ang. compound data structure) zbudowaną z ciągu elementów
tego samego typu. W pamięci komputera elementy tablicy są ułożone kolejno jeden obok drugiego. Dostęp do elementu odbywa się poprzez
numer zwany indeksem. Na podstawie indeksu, rozmiaru elementu oraz adresu początku tablicy komputer oblicza adres elementu i w ten
sposób uzyskujemy do niego dostęp.
We współczesnych językach programowania tablice są stosowane powszechnie do przechowywania danych podobnego rodzaju. Przy ich
pomocy można zapisywać ciągi liczbowe, wyniki pomiarów różnych wielkości oraz tworzyć złożone bazy danych. Liczba zastosowań tablic jest
w zasadzie ograniczona naszą wyobraźnią. Podstawową zaletą tablic jest prostota przetwarzania ich elementów. Dzięki dostępowi poprzez
indeksy, elementy tablic daje się łatwo przetwarzać w pętlach iteracyjnych.
W tym rozdziale zajmujemy się algorytmami wyszukiwania danych w tablicach zawierających liczby. Jednakże podane tutaj algorytmy można z
powodzeniem uogólnić na dane dowolnego typu, dlatego istotne jest zrozumienie sposobu pracy opisanych algorytmów.
Zapraszam do lektury rozdziału.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0028.php 177 / 519


Algorytmy i Struktury Danych - Tablice 2014-10-03

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0028.php 178 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Podstawowe operacje na tablicach


Tematy pokrewne Podrozdziały
Tablice – wektory Deklarowanie tablic
Podstawowe operacje na tablicach Inicjalizacja tablic
Wyszukiwanie liniowe Tablice dynamiczne
Wyszukiwanie liniowe z wartownikiem Tablice dynamiczne o dynamicznym rozmiarze
Zliczanie wg kryterium Wprowadzanie/wyprowadzanie danych
Wyszukiwanie max lub min Wypełnianie tablicy
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Liczby parzyste i nieparzyste
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Deklarowanie tablic
Przed pierwszym użyciem każda tablica musi być zadeklarowana tak jak wszystkie zmienne używane w programie – tablica jest
zmienną złożoną. Poniżej podajemy sposoby deklaracji tablicy w wybranych przez nas językach programowania:

Lazarus
Deklarację tablicy w języku Pascal umieszczamy w sekcji deklaracji zmiennych var. Składnia deklaracji tablicy jest następująca:

nazwa_tablicy : array[indeks_początkowy..indeks_końcowy] of typ_elementów;


nazwa_tablicy – tworzona jest wg zwykłych reguł tworzenia nazw zmiennych w języku Pascal
indeks_początkowy – określa numer pierwszego elementu tablicy
indeks_końcowy – określa numer ostatniego elementu w tablicy
typ_elementów – określa rodzaj informacji przechowywanej w każdym elemencie tablicy

Słowa array oraz of są słowami kluczowymi, które muszą się pojawić w deklaracji tablicy. Poniżej podajemy kilka przykładów:

var
...
a : array[1..3] of integer; // tablica zawierająca 3 elementy, które mogą przechowywać elementy całkowite
x : array[0..9] of double; // tablica przechowująca 10 liczb typu double
c : array[2..7] of char; // tablica przechowująca 6 wartości znakowych
...

W powyższym przykładzie zadeklarowano trzy tablice a, x oraz c. Posiadają one elementy o następujących indeksach:

Tablica a : a[1] a[2] a[3] - 3 elementy typu integer


Tablica x : x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] - 10 elementów typu double
Tablica c : c[2] c[3] c[4] c[5] c[6] c[7] - 6 elementów typu char

Code::Blocks
Deklarację tablicy umieszczamy w języku C++ na liście deklaracji zmiennych. Składnia jest następująca:

typ_danych nazwa_tablicy[liczba_elementów];
typ_danych – określa rodzaj informacji przechowywanych przez deklarowane zmienne
nazwa_tablicy – tworzona jest wg zwykłych reguł tworzenia nazw zmiennych w języku C++
Liczba_elementów – określa, ile elementów danego typu przechowuje tablica

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 179 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

Poniżej podajemy kilka przykładów deklaracji tablic w C++:

...
int a[3]; // tablica zawierająca 3 elementy typu int
double x[10]; // tablica przechowująca 10 liczb typu double
char c[6]; // tablica przechowująca 6 wartości znakowych
...

W języku C++ indeksy tablic rozpoczynają się od 0. Ma to sens, ponieważ nazwa tablicy jest traktowana zawsze jak adres
początku obszaru pamięci, w którym tablica przechowuje swoje elementy. Naturalne zatem jest, iż pierwszy element leży właśnie
pod adresem tablicy. Stąd jego indeks wynosi 0, czyli nic nie musimy dodawać do adresu początku tablicy, aby uzyskać dostęp
do jej pierwszego elementu.
W powyższym przykładzie zadeklarowano trzy tablice a, x oraz c. Posiadają one elementy o następujących indeksach:

Tablica a : a[0] a[1] a[2] - 3 elementy typu integer


Tablica x : x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] - 10 elementów typu double
Tablica c : c[0] c[1] c[2] c[3] c[4] c[5] - 6 elementów typu char

Zwróć uwagę, iż tablica nie posiada elementu o indeksie równym ilości elementów. Zatem jeśli zadeklarujemy np. tablicę:

double Tlk[168];

to jej ostatnim elementem jest Tlk[167], a nie Tlk[168]. Odwołanie się w programie do Tlk[168] jest błędem, którego kompilator
zwykle nie zgłosi, zakładając, iż programista wie co robi. Niestety, język C++ nie był tworzony z myślą o początkujących.

Free Basic
Deklaracji tablicy w języku Free Basic dokonujemy w obrębie instrukcji Dim wraz z innymi zmiennymi. Składnia jest następująca:

Dim nazwa_tablicy(indeks_końcowy) As typ_elementów


Dim nazwa_tablicy(indeks_początkowy To indeks_końcowy) As typ_elementów
Dim As typ_elementów nazwa_tablicy(indeks_końcowy)
Dim As typ_elementów nazwa_tablicy(indeks_początkowy To indeks_końcowy)

Dwie końcowe składnie pozwalają szybko deklarować jednym poleceniem Dim zmienne tego samego typu, na przykład:

Dim As Integer a, b, x(20), h(3 To 10)

Poniżej podajemy kilka przykładów deklaracji tablic:

...
Dim a(1 To 3) As Integer ' tablica zawierająca 3 elementy, które mogą przechowywać elementy całkowite
Dim As Double x(9) ' tablica przechowująca 10 liczb typu double
Dim c(2 To 7) As Ubyte ' tablica przechowująca 6 wartości 8 bitowych bez znaku
...

W powyższym przykładzie zadeklarowano trzy tablice a, x oraz c. Posiadają one elementy o następujących indeksach:

Tablica a : a[1] a[2] a[3] - 3 elementy typu Integer


Tablica x : x[0] x[1] x[2] x[3] x[4] x[5] x[6] x[7] x[8] x[9] - 10 elementów typu Double
Tablica c : c[2] c[3] c[4] c[5] c[6] c[7] - 6 elementów typu Ubyte

Inicjalizacja tablic
Często zdarza się, iż chcemy utworzyć tablicę z zadaną z góry zawartością (np. tablica zawierająca początkowe liczby pierwsze).
Postępujemy wtedy w sposób następujący:

Lazarus
W bloku deklaracji zmiennych var wpisujemy:

nazwa_tablicy : array[indeks_początkowy..indeks_końcowy] of typ_elementów = (lista_wartości_dla_kolejnych_elementów);

Poniższy przykład tworzy tablicę 10 liczb całkowitych i wypełnia ją kolejnymi liczbami Fibonacciego.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 180 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

var
...
fib : array[0..9] of integer = (0,1,1,2,3,5,8,13,21,33);
...

Code::Blocks
Składnia inicjalizacji tablicy w języku C++ jest następująca:

typ_elementów nazwa_tablicy = {lista_wartości_dla_kolejnych_elementów};

Zwróć uwagę, iż nie musimy podawać liczby elementów. Kompilator utworzy tyle elementów, ile podamy dla nich wartości na liście
inicjalizacyjnej. Poniższy przykład tworzy tablicę 10 liczb całkowitych i wypełnia ją kolejnymi liczbami Fibonacciego.

...
int fib[] = (0,1,1,2,3,5,8,13,21,33);
...

Free Basic
W języku Basic tablicę tworzymy i inicjujemy w obrębie polecenia Dim:

Dim nazwa_tablicy(indeks_końcowy) As typ_elementów => {lista_wartości_dla_kolejnych_elementów}


Dim nazwa_tablicy(i ndek s _poc z ąt k owy To indeks_końcowy) As typ_elementów =>
{lista_wartości_dla_kolejnych_elementów}

W pierwszym przypadku tablica zawiera elementy o indeksach przebiegających od 0 do wartości podanej jako indeks_końcowy.
W przypadku drugim programista może swobodnie określać zakres indeksów tablicy. Liczba wartości na liście inicjalizacyjnej
musi się zgadzać z deklaracją indeksów. Poniższy przykład tworzy tablicę 10 liczb całkowitych i wypełnia ją kolejnymi liczbami
Fibonacciego.

...
Dim fib(9) As Integer => {0,1,1,2,3,5,8,13,21,33}
...

Tablice dynamiczne
Zdarza się, iż w trakcie pisania programu nie wiemy, ile dokładnie elementów będzie zawierała używana w tym programie tablica. W
takim przypadku problem tworzenia tablicy możemy rozwiązać na dwa sposoby:
1. Utworzyć tablicę o maksymalnej, przewidywanej liczbie elementów. Rozwiązanie nieefektywne ze względu na wykorzystanie
pamięci. Jeśli w typowych przypadkach wykorzystujemy małą liczbę elementów tablicy, to i tak musimy rezerwować założoną
ilość komórek dla przypadku pesymistycznego, który pojawia się bardzo rzadko, ale jest prawdopodobny.
2. Utworzyć tablicę dynamicznie o tylu komórkach, ile w danej chwili jest nam potrzebne. Po wykorzystaniu, tablicę dynamiczną
usuwamy, zwalniając w ten sposób zajmowany przez nią obszar pamięci, który teraz można wykorzystać do innych celów – np.
dla nowej tablicy dynamicznej.
Lazarus
Aby utworzyć tablicę dynamiczną w języku Lazarus najpierw deklarujemy jej typ:

type
nazwa_typu_tablicy = array of nazwa_typu_elementów;

Zwróć uwagę, iż w definicji typu za słowem array nie podajemy zakresu indeksów. Ma to sens, ponieważ ilość elementów będzie
określana dynamicznie w czasie wykonywania programu. Teraz kompilator potrzebuje jedynie informacji o typie elementów, które
będzie przechowywała tablica. Pozwoli mu to później zarezerwować odpowiedni obszar pamięci oraz obliczać adresy
indeksowanych elementów. Poniższy przykład tworzy typ dynamicznej tablicy, która będzie przechowywała elementy typu double:

type
Tdouble = array of integer;

Po zdefiniowaniu typu dla tablicy dynamicznej możemy go użyć do tworzenia zmiennych w bloku var.

var
nazwa_zmiennej : nazwa_typu_tablicy;

Poniższy przykład wykorzystuje typ Tdouble do utworzenia trzech zmiennych a,b,c.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 181 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

var
a,b,c : Tdouble;

Każda ze zmiennych a, b, c jest w rzeczywistości wskaźnikiem, czyli zmienną przechowującą adres właściwych danych. Na
początku wszystkie trzy wskaźniki zawierają adres NIL, który w Pascalu nie wskazuje żadnego obiektu. Jeśli chcemy używać
tablicy dynamicznej, to na początku programu musimy zarezerwować w pamięci odpowiedni obszar na elementy tablicy i adres
początku tego obszaru umieścić we wskaźniku. Dokonujemy tego za pomocą procedury:

SetLength(nazwa_zmiennej, liczba_komórek_tablicy);

Po tej operacji mamy dostęp do poszczególnych elementów tablicy za pomocą indeksów. W tablicach dynamicznych indeksy
kolejnych elementów rozpoczynają się od 0, a kończą na liczbie komórek tablicy - 1. Poniższy przykład tworzy trzy tablice
dynamiczne o elementach typu Tdouble zawierające odpowiednio 10, 100 i 1000 elementów:

...
SetLength(a,10); // elementy od a[0] do a[9]
SetLength(b,100); // elementy od b[0] do b[99]
SetLength(c,1000); // elementy od c[0] do c[999]
...

Jeśli tablica dynamiczne została utworzona w procedurze lub funkcji, to jest automatycznie usuwana z pamięci po zakończeniu
działania procedury lub funkcji. Tablicę dynamiczną można usunąć z pamięci ustawiając jej długość na 0. Poniższy przykład
usuwa tablicę c:

...
SetLength(c,0);
...

Należy pamiętać, iż usunięty został obszar pamięci zajmowany przez elementy tablicy c, a nie sama zmienna c, która jest tylko
wskaźnikiem.
Ponieważ zmienne tablic dynamicznych są wskaźnikami, to poniższa operacja nie powoduje przepisania elementów tablicy b[] do
tablicy a[], jak można by przypuszczać:

a := b;

W rzeczywistości w zmiennej a znajdzie się adres przechowywany przez zmienną b. Zmienna a będzie wskazywała ten sam
obszar pamięci co zmienna b. Do elementów tablicy b możemy się odwoływać poprzez elementy tablicy a – to jest ta sama
tablica, tylko jej adres jest teraz w dwóch różnych zmiennych. Należy na to zwrócić baczną uwagę, gdyż złe zrozumienie tych
faktów prowadzi do trudnych do wykrycia błędów w programach.

Code::Blocks
W celu utworzenia w języku C++ tablicy dynamicznej, tworzymy zmienną wskaźnikową na typ danych, które mają być
przechowywane w tablicy:

typ_elementów * nazwa_tablicy_dynamicznej;

Zmienna wskaźnikowa (ang. pointer variable) nie przechowuje danych tylko adres obszaru pamięci komputera, w którym te dane
się znajdują. Deklarację zmiennej wskaźnikowej zawsze poprzedzamy znakiem gwiazdki. W poniższym przykładzie tworzymy
trzy wskaźniki a, b i c do danych typu double (czyli do obszaru pamięci, w którym będą przechowywane liczby
zmiennoprzecinkowe o podwójnej precyzji):

...
double * a, * b, * c;
...

Pamięć rezerwujemy operatorem new i adres zarezerwowanego obszaru umieszczamy w zmiennej wskaźnikowej:

nazwa_tablicy_dynamicznej = new typ_elementów[liczba_elementów];

Poniższy przykład tworzy trzy tablice dynamiczne, w których będzie można przechowywać odpowiednio 10, 100 i 1000 elementów
typu double:

...
a = new double[10]; // elementy od a[0] do a[9]
b = new double[100]; // elementy od b[0] do b[99]

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 182 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

c = new double[1000]; // elementy od c[0] do c[999]


...

Po tej operacji do elementów tablic a, b i c odwołujemy się w zwykły sposób za pomocą indeksów. Istnieje również alternatywna
metoda, wykorzystująca fakt, iż zmienne a, b i c są wskaźnikami. W języku C++ dodanie do wskaźnika liczby całkowitej
powoduje obliczenie adresu elementu o indeksie równym dodawanej liczbie. Zatem wynik takiej operacji jest również wskaźnikiem:

Tablica Wskaźnik

a[2] = 10.54; * (a + 2) = 10.54;


cout << a[2] << endl; cout << * (a + 2) << endl;

W rzeczywistości zapis a[i] kompilator i tak przekształca sobie na zapis * (a + i). Forma tablicowa jest tylko uproszczeniem
zapisu wskaźnikowego.
Tablice dynamiczne nie są automatycznie usuwane z pamięci, jeśli utworzono je w funkcji. Dlatego po zakończeniu korzystania z
tablicy program powinien zwolnić zajmowaną przez tablicę pamięć. Dokonujemy tego poleceniem delete w sposób następujący:

delete [] nazwa_tablicy_dynamicznej;

W poniższym przykładzie zwalniamy pamięć zarezerwowaną wcześniej na elementy tablic b i c.

...
delete [] b; // usuwamy obszar wskazywany przez b
delete [] c; // usuwamy obszar wskazywany przez c
...

Należy również wspomnieć, iż Code::Blocks dopuszcza konstrukcję:

typ_elementów nazwa_tablicy[zmienna];

co pozwala na tworzenie statycznych tablic o liczbie elementów podanej w zmiennej. Na przykład poniższa konstrukcja
programowa tworzy statyczną tablicę a o liczbie elementów odczytanej ze strumienia wejściowego konsoli znakowej:

...
int n;
cin >> n;
double a[n];
...

Jednakże nie jest to zbyt standardowe rozwiązanie i może nie być przenośne na inne kompilatory C++, dlatego odradzam
używania go – lepiej zastosować tablicę dynamiczną.

Free Basic
W języku Basic dynamiczna tablica powstaje, gdy w poleceniu Dim użyjemy zmiennej do określenia rozmiaru tablicy. Poniższy
przykład tworzy tablicę liczb typu double o rozmiarze odczytanym z klawiatury:

Dim n As Integer
Input n
Dim a(n) As Double

W tym samym celu możemy również wykorzystać polecenie Redim. Poniższy przykład tworzy trzy tablice dynamiczne
zawierające odpowiednio 10, 100 i 1000 elementów typu double:

Redim As Double a(9), b(99), c(999)

Gdy tablica dynamiczna przestanie być potrzebna, można ją usunąć z pamięci poleceniem:

Erase nazwa_tablicy_dynamicznej

Inny sposób tworzenia tablic dynamicznych wiąże się z wykorzystaniem wskaźników. Wskaźnik jest zmienną, która zawiera adres
innej zmiennej. Procedura tworzenia tablicy dynamicznej za pomocą wskaźnika jest następująca:

1. Tworzymy wskaźnik do danych, które ma przechowywać tablica:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 183 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

Dim A As typ_danych Ptr

2. Przydzielamy blok pamięci na n danych:

A = New typ_danych [n]

3. Tablica gotowa, dostęp do elementów uzyskujemy za pomocą indeksów w nawiasach kwadratowych:

A[15] = ...

4. Gdy tablica przestanie być potrzebna, należy zwolnić zajmowaną przez nią pamięć:

Delete [] A

Tablice dynamiczne o dynamicznym rozmiarze


Gdy operujemy na dynamicznych strukturach danych, to często okazuje się, że taką strukturę należy zwiększyć lub zmniejszyć w
czasie działania programu. Np. utworzyliśmy na początku algorytmu dynamiczną tablicę 100 elementową, lecz dalej okazało się, że w
tablicy tej należy dodatkowo umieścić jeszcze 50 kolejnych elementów. Co możemy zrobić? Odpowiedź jest prosta – dynamicznie
zmienić rozmiar naszej struktury. Zasada jest następująca:

Oprócz wskazania do tablicy tworzymy dodatkową zmienną, która przechowuje jej maksymalny rozmiar. Oznaczmy tę
zmienną przez nn. Każdą tablicę tworzymy z pewnym zapasem komórek, oznaczmy go przez z. Zmienna n przechowuje
informację o ilości zajętych komórek.
Gdy dodajemy element do tablicy, to najpierw zwiększamy n o 1 i sprawdzamy, czy warunek n >= nn jest spełniony. Jeśli
tak, to tablica zawiera za mało komórek, i należy zwiększyć jej rozmiar. Operację tę przeprowadzamy następująco:
1. Rezerwujemy nowy obszar pamięci o rozmiarze nn + z i zapamiętujemy jego adres w tymczasowej zmiennej.
2. Przepisujemy zawartość starego obszaru tablicy do nowego.
3. Usuwamy stary obszar – w ten sposób system odzyska pamięć.
4. Do wskaźnika tablicy przepisujemy adres ze zmiennej tymczasowej.
5. Zmienną nn zwiększamy o z

Zwróć uwagę, że cała tablica zmieniła swoje położenie w pamięci. Jeśli zatem posiadałeś jakieś zmienne, które
przechowywały adresy jej elementów (wskaźniki), to teraz przestaną one być aktualne, ponieważ wskazują stary obszar,
który już do tej tablicy nie należy (chociaż przez pewien czas może zachowywać swoją zawartość aż system zapisze go
innymi danymi). Należy to wziąć pod uwagę, gdy planujesz wykorzystywanie dynamicznych tablic o dynamicznym
rozmiarze.

Lazarus

...
inc(n);
if n >= Length(A) then SetLength(A,Length(A)+z);
...

Code::Blocks

...
if(++n >= nn)
{
typ_danych * T = new typ_danych[nn+z];
for(int i = 0; i < nn; i++) T[i] = A[i];
delete [] A;
A = T;
nn += z;
}
...

Free Basic

Dim As typ_danych Ptr T


Dim As Integer i
...
n += 1
If n >= nn Then
T = New typ_danych [nn+z]
For i = 0 To nn-1: T[i] = A[i]: Next
Delete [] A
A = T
nn += z
End If
...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 184 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

Przy usuwaniu elementów z tablicy postępujemy podobnie. Gdy usuniemy element, to rozmiar tablicy n zostanie
zmniejszony o 1. Sprawdzamy, czy jest spełniony warunek n ≤ nn - 2z. Jeśli tak, to tablica zajmuje zbyt duży obszar i
powinniśmy zmniejszyć jej rozmiar. Dlaczego w nierówności użyliśmy 2z a nie po prostu z? Chodzi o to, aby po
zmniejszeniu rozmiaru tablica wciąż posiadała zapas z komórek, co zmniejszy ryzyko częstych przeładowań pamięci,
które są przecież kosztowne czasowo. Zmianę rozmiaru tablicy wykonujemy podobnie jak przy zwiększaniu liczby
elementów:

Lazarus

...
dec(n);
if n <= Length(A)-z-z then SetLength(A,Length(A)-z);
...

Code::Blocks

...
if(--n <= nn-z-z)
{
typ_danych * T = new typ_danych[nn-z];
for(int i = 0; i < n; i++) T[i] = A[i];
delete [] A;
A = T;
nn -= z;
}
...

Free Basic

Dim As typ_danych Ptr T


Dim As Integer i
...
n -= 1
If n <= nn-z-z Then
T = New typ_danych [nn-z]
For i = 0 To n-1: T[i] = A[i]: Next
Delete [] A
A = T
nn -= z
End If
...

Wprowadzanie/wyprowadzanie danych
Dane dla programu zwykle muszą być odczytywane ze zewnętrznego źródła – konsoli lub pliku. W takim przypadku nie wiemy z góry
(tzn. w trakcie pisania programu) ile ich będzie. Narzucającym się rozwiązaniem jest zastosowanie tablic dynamicznych. Ze źródła
danych odczytujemy rozmiar tablicy, tworzymy tablicę dynamiczną o odpowiednim rozmiarze, a następnie wczytujemy do jej komórek
poszczególne dane.
Poniżej podajemy sposoby odczytu zawartości tablicy z konsoli. Sposób ten jest bardzo ogólny. Wykorzystanie standardowego wejścia
jako źródła danych daje nam kilka możliwości wprowadzania danych:
1. Dane podajemy bezpośrednio z klawiatury. Sposób skuteczny i prosty dla niedużego zbioru danych. Jednakże przy
większej ich liczbie staje się bardzo uciążliwy.
2. Skopiowanie danych poprzez schowek. Procedura postępowania jest następująca:

– tworzymy w notatniku Windows (aplikacja zawsze pod ręką) odpowiedni zbiór danych
– zbiór kopiujemy do schowka (zaznaczamy całość Ctrl-A i naciskamy Ctrl-C)
– uruchamiamy program
– klikamy prawym przyciskiem myszki w pasek tytułowy okna konsoli
– z menu kontekstowego wybieramy polecenie Edytuj → Wklej
– gotowe

3. Przekierowanie standardowego wejścia z konsoli na plik na dysku. W tym przypadku program będzie pobierał dane z pliku,
a nie z klawiatury. Aby to uzyskać uruchamiamy program w oknie konsoli następująco:
nazwa_programu < nazwa_pliku_wejściowego
Na przykład nasz program nazywa się szukaj.exe, a plik nosi nazwę dane.txt. Odpowiednie polecenie odczytu danych z
pliku przez nasz program wygląda następująco:
szukaj < dane.txt
To rozwiązanie umożliwia również zapis danych wynikowych nie na ekran konsoli, lecz do pliku na dysku. W tym celu
wystarczy wydać polecenie:
nazwa_programu > nazwa_pliku_wynikowego

Wejście i wyjście można przekierować w jednym poleceniu. Np. nasz program szukaj może odczytać dane wejściowe z
pliku dane.txt, a wyniki swojej pracy umieścić w pliku wyniki.txt. W tym celu wydajemy takie oto polecenie:
szukaj < dane.txt > wyniki.txt

Jeśli często korzystasz z takich opcji uruchamiania programu, to zamiast wpisywać polecenie z klawiatury, można
stworzyć sobie prosty plik wsadowy (ang. batch file), w którym umieszczamy niezbędne polecenia. Plikowi można nadać
prostą nazwę, np. !.cmd. Wtedy w celu uruchomienia zawartych w nim poleceń wystarczy wpisać !. Oczywiście plik

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 185 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

wsadowy należy umieścić w katalogu projektowym, ale to chyba już wiesz. Poniżej podaję przykładową zawartość takiego
pliku wsadowego:
@echo off
cls
echo DANE WEJSCIOWE:
echo.
type dane.txt
echo.
prg < dane.txt > wyniki.txt
echo WYNIKI:
echo.
type wyniki.txt
echo.
Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program z pierwszego wiersza odczytuje liczbę n określającą ilość danych. Z następnych n wierszy odczytywane są dane i umieszczane
w tablicy dynamicznej. Odczytane dane zostają następnie wyświetlone jedna obok drugiej. Wypróbuj z tym programem podane powyżej
trzy opcje dostarczania danych i wyprowadzania wyników.

Lazarus

// Odczyt danych
// Data: 25.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
type Tinteger = array of integer;
var T : Tinteger;
n,i : integer;
begin
readln(n);
SetLength(T,n);
for i := 0 to n-1 do readln(T[i]);
writeln;
for i := 0 to n-1 do write(T[i],' ');
writeln;
writeln;
SetLength(T,0);
end.

Code::Blocks

// Odczyt danych
// Data: 25.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
int main()
{
int * T,n,i;
cin >> n;
T = new int[n];
for(i = 0; i < n; i++) cin >> T[i];
cout << endl;
for(i = 0; i < n; i++) cout << T[i] << " ";
cout << endl << endl;
delete [] T;
return 0;
}

Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 186 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

' Odczyt danych


' Data: 25.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim T As Integer Ptr
Dim As Integer n,i
Open Cons For Input As #1
Input #1,n
T = New Integer [n]
For i = 0 To n - 1
Input #1,T[i]
Next
Close #1
Print
For i = 0 To n - 1
Print T[i];
Next
Print
Print
Delete [] T
End

Wynik
6
12
34
28
65
121
83
12 34 28 65 121 83

Wypełnianie tablicy
Czasami algorytm musi wstępnie wypełnić tablicę określoną zawartością. Operację taką przeprowadza się w pętli iteracyjnej, której
zmienna licznikowa przebiega przez wszystkie kolejne indeksy elementów. Następnie wykorzystuje się zmienną licznikową jako indeks
elementu tablicy, w którym umieszczamy określoną zawartość.
W poniższych przykładach zakładamy, iż w programie zadeklarowano tablicę T o 100 elementach typu integer. Indeksy elementów
tablicy T są w zakresie od 0 do 99.

Wypełnianie stałą zawartością x


Lazarus Code::Blocks Free Basic

... ... ...


for i := 0 to 99 do T[i] := x; for(i = 0; i < 100; i++) T[i] = x; For i = 0 To 99
... ... T(i) = x
Next
...

Wypełnianie liczbami parzystymi począwszy od 2


Lazarus Code::Blocks Free Basic

... ... ...


for i := 0 to 99 do for(i = 0; i < 100; i++) For i = 0 To 99
T[i] := (i + 1) shl 1; T[i] = (i + 1) << 1; T(i) = (i + 1) Shl 1
... ... Next
...

Wypełnianie liczbami nieparzystymi począwszy od 1


Lazarus Code::Blocks Free Basic

... ... ...


for i := 0 to 99 do for(i = 0; i < 100; i++) For i = 0 To 99
T[i] := 1 + (i shl 1); T[i] = 1 + (i << 1); T(i) = 1 + (i Shl 1)
... ... Next
...

Wypełnianie liczbami pseudolosowymi z przedziału <a,b>


Lazarus Code::Blocks Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 187 / 519


Algorytmy i Struktury Danych - Operacje na tablicach 2014-10-03

randomize; #include <cstdlib> Randomize


... #include <time.h> ...
for i := 0 to 99 do For i = 0 To 99
T[i] := a + random(b - a + 1); ... T(i) = a + Cint(Rnd * (b - a + 1))
... srand((unsigned)time(NULL)); Next
... ...
for(i = 0; i < 100; i++)
T[i] = a + rand() % (b - a + 1);
...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania
zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w
podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0029.php 188 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie liniowe/sekwencyjne
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z wyszukać element posiadający pożądane własności.

Wyszukiwanie liniowe (ang. linear search), zwane również sekwencyjnym (ang. sequential search) polega na przeglądaniu
kolejnych elementów zbioru Z. Jeśli przeglądany element posiada odpowiednie własności (np. jest liczbą o poszukiwanej wartości),
to zwracamy jego pozycję w zbiorze i kończymy. W przeciwnym razie kontynuujemy poszukiwania aż do przejrzenia wszystkich
pozostałych elementów zbioru Z.
W przypadku pesymistycznym, gdy poszukiwanego elementu nie ma w zbiorze lub też znajduje się on na samym końcu zbioru,
algorytm musi wykonać przynajmniej n obiegów pętli sprawdzającej poszczególne elementy. Wynika z tego, iż pesymistyczna
klasa złożoności obliczeniowej jest równa O(n), czyli jest liniowa – stąd pochodzi nazwa metody wyszukującej.
Często chcemy znaleźć wszystkie wystąpienia w zbiorze poszukiwanej wartości elementu. W takim przypadku algorytm na
wejściu powinien otrzymywać dodatkowo pozycję (indeks) elementu, od którego ma rozpocząć wyszukiwanie. Pozycję tę przy
kolejnym przeszukiwaniu podajemy zawsze o 1 większą od ostatnio znalezionej. Dzięki temu nowe poszukiwanie rozpocznie się
tuż za poprzednio znalezionym elementem.

Algorytm wyszukiwania liniowego/sekwencyjnego


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do przeszukania. Indeksy elementów rozpoczynają się od 0, a
kończą na n-1
p – indeks pierwszego elementu Z, od którego rozpoczniemy poszukiwania. p C
k – poszukiwana wartość, czyli tzw. klucz, wg którego wyszukujemy elementy w Z
Wyjście:
Pozycja elementu zbioru Z o kluczu k lub -1 w przypadku nie znalezienia elementu.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 189 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03

Lista kroków:
K01: Dla i = p,p+1,...,n-1: wykonuj K02 ; przeglądamy kolejne elementy w zbiorze
Jeśli Z[i] = k, t o zakończ z ; jeśli napotkamy poszukiwany element, zwracamy jego
K02:
wynikiem i pozycję
K03: Zakończ z wynikiem -1 ; jeśli elementu nie ma w tablicy, zwracamy -1

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczbę n. Z następnych n wierszy odczytywane jest n danych całkowitych.
W ostatnim wierszu odczytywana jest liczba k, którą należy wyszukać wśród podanych n liczb. Program wypisuje
numer pozycji w odczytanym ciągu liczb, na której znajduje się liczba k lub -1, jeśli liczby nie odnaleziono.

Lazarus

// Wyszukiwanie liniowe
// Data: 26.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
type Tint = array of integer;
function Szukaj(var T : Tint; n,k,p : integer) : integer;
var i : integer;
begin
Szukaj := -1;
for i := p to n - 1 do
if T[i] = k then
begin
Szukaj := i; break;
end
end;
var
Z : Tint;
n,k,i: integer;
begin
readln(n);
SetLength(Z,n);
for i := 0 to n - 1 do readln(Z[i]);
readln(k);
writeln;
writeln(Szukaj(Z,n,k,0));
writeln;
SetLength(Z,0);
end.

Code::Blocks

// Wyszukiwanie liniowe
// Data: 26.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
int Szukaj(int T[], int n, int k, int p)
{
for(int i = p; i < n; i++)
if(T[i] == k) return i;
return -1;
}
int main()

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 190 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03

{
int * Z,n,k,i;
cin >> n;
Z = new int [n];
for(i = 0; i < n; i++) cin >> Z[i];
cin >> k;
cout << endl << Szukaj(Z,n,k,0) << endl << endl;
delete [] Z;
return 0;
}

Free Basic

' Wyszukiwanie liniowe


' Data: 26.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Function Szukaj(T As Integer Ptr, n As Integer, k As Integer, p As Integer) As Integer
Dim i As Integer
Szukaj = -1
For i = p To n - 1
If T[i] = k Then
Szukaj = i
Exit For
End If
Next
End Function
Dim Z As Integer Ptr
Dim As Integer n,k,i
Open Cons For Input As #1
Input #1,n
Z = New Integer [n]
For i = 0 To n - 1
Input #1,Z[i]
Next
Input #1,k
Close #1
Print
Print Szukaj(Z,n,k,0)
Print
Delete [] Z
End

Wynik
5
13
18
954
235
12
111
-1

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia 100 elementową tablicę Z całkowitymi liczbami pseudolosowymi z zakresu od 0 do 9. Następnie
losuje nową liczbę pseudolosową z tego samego zakresu i znajduje wszystkie jej wystąpienia w Z przy pomocy
wyszukiwania liniowego. Na końcu wyświetla wylosowaną liczbę oraz zawartość Z z zaznaczonymi jej
wystąpieniami. Do zapamiętywania wystąpień służy druga tablica L o elementach logicznych. Jeśli i-ty element tej

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 191 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03

tablicy zawiera true, to w i-tym elemencie tablicy Z znajduje się poszukiwany element.

Lazarus

// Wyszukiwanie liniowe
// Data: 26.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
var
Z : array[0..99] of integer;
L : array[0..99] of boolean;
i,w : integer;
begin
randomize;
for i := 0 to 99 do Z[i] := random(10);
w := random(10);
for i := 0 to 99 do L[i] := Z[i] = w;
writeln(w); writeln;
for i := 0 to 99 do
begin
if L[i] then write(' ->')
else write(' ');
write(Z[i]);
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie liniowe
// Data: 26.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
int Z[100],i,w;
bool L[100];
srand((unsigned)time(NULL));
for(i = 0; i < 100; i++) Z[i] = rand() % 10;
w = rand() % 10;
for(i = 0; i < 100; i++) L[i] = (Z[i] == w);
cout << w << endl << endl;
for(i = 0; i < 100; i++)
{
if(L[i]) cout << " ->";
else cout << " ";
cout << Z[i];
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie liniowe


' Data: 26.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Dim As Integer Z(99),i,w
Dim As Byte L(99)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 192 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03
Dim As Byte L(99)
Randomize
For i = 0 To 99
Z(i) = Cint(Rnd * 9)
Next
w = Cint(Rnd * 9)
For i = 0 To 99
L(i) = (Z(i) = w)
Next
Print w;
Print
For i = 0 To 99
If L(i) Then
Print Using " ->#";Z(i);
Else
Print Using " #";Z(i);
End If
Next
Print
End

Wynik
3
4 2 2 5 1 2 6 6 4 4 9 6 0 7 8 0 2 5 4 8
5 2 5 ->3 9 7 6 ->3 1 ->3 6 5 1 7 4 9 5 ->3 7 8
1 5 7 4 6 2 1 2 4 0 6 1 6 4 7 8 1 4 9 2
7 0 6 8 7 6 4 1 7 8 4 8 ->3 8 2 8 7 0 7 6
2 6 0 6 4 0 5 0 8 ->3 2 7 9 0 1 7 5 4 9 9

Wyszukiwanie liniowe
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 193 / 519


Algorytmy i Struktury Danych - Wyszukiwanie liniowe 2014-10-03

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0030.php 194 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie liniowe z wartownikiem


Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z wyszukać element posiadający pożądane własności.

W algorytmie wyszukiwania liniowego sprawdzamy kolejne elementy zbioru aż do napotkania jego końca lub poszukiwanego
elementu. Zachodzi pytanie, czy algorytm ten można przyspieszyć. Aby na nie odpowiedzieć, zapiszmy algorytm w poniższej
postaci:
Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do przeszukania. Indeksy elementów rozpoczynają się od 0, a kończą na n-1
k – poszukiwana wartość, czyli tzw. klucz, wg którego wyszukujemy elementy w Z
Wyjście:
pozycja elementu zbioru Z o kluczu k lub -1 w przypadku nie znalezienia elementu.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C

Numer Operacja
1 i ←0
2 Jeśli i ≥ n, to zakończ z wynikiem -1
3 Jeśli Z[i] = k, to zakończ z wynikiem i
4 i ←i + 1
5 Idź do 2

Sprawdzimy teraz ile operacji wykonuje ten algorytm w dwóch charakterystycznych przypadkach:
Przypadek pierwszy: poszukiwanej liczby nie ma w zbiorze. Poniżej przedstawiamy liczbę wykonań poszczególnych operacji w
algorytmie:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 195 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

Numer Operacja Liczba wykonań


1 i ←0 1
2 Jeśli i ≥ n, to zakończ z wynikiem -1 n+1
3 Jeśli Z[i] = k, to zakończ z wynikiem i n
4 i ←i + 1 n
5 Idź do 2 n
RAZEM: 4n + 2

Przypadek drugi: poszukiwana liczba statystycznie znajduje się w środku zbioru – czasem znajdziemy ją wcześniej, a czasem
później, zatem średnio będzie w środku.

Numer Operacja Liczba wykonań


1 i ←0 1
2 Jeśli i ≥ n, to zakończ z wynikiem -1 n/ + 1
2
3 Jeśli Z[i] = k, to zakończ z wynikiem i n/ + 1
2
4 i ←i + 1 n/
2
5 Idź do 2 n/
2
RAZEM: 2n + 3

Zwróć uwagę, iż w każdym obiegu pętli nasz algorytm wykonuje dwa testy – w instrukcji numer 2 i 3. Usprawnienie pracy
algorytmu będzie polegało na eliminacji testu 2. Jednakże test ten jest niezbędny, aby zakończyć przeglądanie tablicy w
przypadku, gdy poszukiwanego elementu nie ma w zbiorze. Skoro tak, to wstawmy poszukiwany element na koniec zbioru, wtedy
test 2 stanie się zbędny, nieprawdaż. Algorytm w zmienionej postaci wygląda następująco:

Numer Operacja
1a Z[n] ← k
1b i ← 0
2 usunięta
3 Jeśli Z[i] = k, to idź do 6
4 i ←i + 1
5 Idź do 3
6 Jeśli i = n, to zakończ z wynikiem -1
7 Zakończ z wynikiem i

Zmiany w stosunku do pierwotnego algorytmu są następujące:


1a – instrukcja umieszcza na końcu zbioru Z poszukiwany element o wartości k. Dzięki tej operacji dostajemy gwarancję, iż
zawsze znajdziemy w zbiorze element k.
2 – test osiągnięcia końca zbioru stał się zbędny, ponieważ element o wartości k zawsze znajdziemy w zbiorze.
3, 6, 7 – znalezienie elementu o wartości k wymaga sprawdzenia, czy nie jest on elementem wstawionym do zbioru w operacji 1a.
Jeśli tak, to zbiór faktycznie nie zawierał poszukiwanego elementu.

Przypadek pierwszy: poszukiwanej liczby nie ma w zbiorze. Poniżej przedstawiamy liczbę wykonań poszczególnych operacji w
algorytmie:

Numer Operacja Liczba wykonań


1a Z[n] ← k 1
1b i ← 0 1
2
3 Jeśli Z[i] = k, to idź do 6 n+1
4 i ←i + 1 n
5 Idź do 3 n
6 Jeśli i = n, to zakończ z wynikiem -1 1
7 Zakończ z wynikiem i 0
RAZEM: 3n + 4

Przypadek drugi: poszukiwana liczba statystycznie znajduje się w środku zbioru – czasem znajdziemy ją wcześniej, a czasem
później, zatem statystycznie będzie w środku.

Numer Operacja Liczba wykonań


1a Z[n] ← k 1
1b i ← 0 1
2

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 196 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

2
3 Jeśli Z[i] = k, to idź do 6 n/ + 1
2
4 i ←i + 1 n/
2
5 Idź do 3 n/
2
6 Jeśli i = n, to zakończ z wynikiem -1 1
7 Zakończ z wynikiem i 1
RAZEM: 3/ n + 5
2

Porównajmy teraz wyniki z pierwszej i drugiej wersji algorytmu w poniższej tabelce (wartości ułamkowe z ostatniej kolumny należy
rozumieć jako wartości statystyczne).:

Przypadek pierwszy Przypadek drugi


Algorytm Algorytm Algorytm Algorytm
n podstawowy usprawniony podstawowy usprawniony
4n + 2 3n + 4 2n + 3 3/2n + 5
1 6 7 5 6,5
2 10 10 7 8
3 14 13 9 9,5
4 18 16 11 11
5 22 19 13 12,5
6 26 22 15 14
... ... ... ... ...
10 42 34 23 20
100 402 304 203 155
1000 4002 3004 2003 1505
10000 40002 30004 20003 15005
... ... ... ... ...

Chociaż początkowo algorytm pierwszy wygrywa w ilości operacji, to przy wzroście liczby elementów zbioru widzimy wyraźnie, iż
algorytm usprawniony wykonuje mniej operacji (począwszy od n > 3), zatem działa szybciej.
Opisana metoda wyszukiwania nosi nazwę wyszukiwania liniowego z wartownikiem (ang. Search with Sentinel).
Wartownikiem jest dodany na końcu zbioru element równy poszukiwanemu. Dzięki niemu uzyskujemy zawsze pewność
znalezienia poszukiwanego elementu w zbiorze. Jeśli jest to wartownik, to elementu poszukiwanego w zbiorze nie ma i zwracamy
pozycję -1. Jeśli nie jest to wartownik, to znaleźliśmy poszukiwany element w zbiorze i zwracamy jego pozycję i.
Należy podkreślić, iż wyszukiwanie z wartownikiem można stosować tylko wtedy, gdy do zbioru da się dołączyć jeszcze jeden
element.

Algorytm wyszukiwania liniowego z wartownikiem


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do przeszukania. Indeksy elementów rozpoczynają się od 0, a
kończą na n. Ostatnia pozycja Z[n] będzie zajęta przez wartownika, zatem nie może być
używana do innych celów.
p – indeks pierwszego elementu Z, od którego rozpoczniemy poszukiwania. p C
k – poszukiwana wartość, czyli tzw. klucz, wg którego wyszukujemy elementy w Z
Wyjście:
Pozycja elementu zbioru Z o kluczu k lub -1 w przypadku nie znalezienia elementu.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
Lista kroków:
K01: Z[n] ← k ; na końcu zbioru umieszczamy wartownika
K02: i ← p ; przeszukiwanie rozpoczynamy od pozycji p
K03: Jeśli Z[i] = k, to idź do K06 ; sprawdzamy kolejne elementy zbioru
K04: i ← i + 1 ; jeśli element nie znaleziony, przechodzimy do następnego
elementu w zbiorze
K05: Idź do K03
K06: Jeśli i = n, to zakończ z ; sprawdzamy, czy znaleziony element jest wartownikiem
wynikiem -1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 197 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

K07: Zakończ z wynikiem i ; jeśli nie, to zwracamy pozycję znalezionego elementu

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z pierwszego wiersza liczbę n. Z następnych n wierszy odczytywane jest n danych całkowitych.
W ostatnim wierszu odczytywana jest liczba k, którą należy wyszukać wśród podanych n liczb. Program wypisuje
numer pozycji w odczytanym ciągu liczb, na której znajduje się liczba k lub -1, jeśli liczby nie odnaleziono.

Lazarus

// Wyszukiwanie liniowe z wartownikiem


// Data: 27.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
type Tint = array of integer;
function Szukaj(var T : Tint; n,k,p : integer) : integer;
var i : integer;
begin
T[n] := k;
i := p;
while T[i] <> k do inc(i);
if i = n then Szukaj := -1 else Szukaj := i;
end;
var
T : Tint;
n,k,i: integer;
begin
readln(n);
SetLength(T,n + 1);
for i := 0 to n - 1 do readln(T[i]);
readln(k);
writeln;
writeln(Szukaj(T,n,k,0));
writeln;
SetLength(T,0);
end.

Code::Blocks

// Wyszukiwanie liniowe z wartownikiem


// Data: 26.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
using namespace std;
int Szukaj(int T[], int n, int k, int p)
{
int i;
T[n] = k;
for(i = p; T[i] != k; i++) ;
if(i == n) return -1; else return i;
}
int main()
{
int * Z,n,k,i;
cin >> n;
Z = new int [n + 1];
for(i = 0; i < n; i++) cin >> Z[i];
cin >> k;
cout << endl << Szukaj(Z,n,k,0) << endl << endl;
delete [] Z;
return 0;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 198 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

Free Basic

' Wyszukiwanie liniowe z wartownikiem


' Data: 27.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Function Szukaj(T As Integer Ptr, n As Integer, k As Integer, p As Integer) As Integer
Dim i As Integer
T[n] = k
i = p
While T[i] <> k: i += 1: Wend
If i = n Then Szukaj = -1 Else Szukaj = i
End Function
Dim T As Integer Ptr
Dim As Integer n,k,i
Open Cons For Input As #1
Input #1,n
T = New Integer [n+1]
For i = 0 To n - 1
Input #1,T[i]
Next
Input #1,k
Close #1
Print
Print Szukaj(T,n,k,0)
Print
Delete [] T
End

Wynik
5
13
18
954
235
12
111
-1

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 199 / 519


Algorytmy i Struktury Danych - Wyszukiwanie z wartownikiem 2014-10-03

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0031.php 200 / 519


Algorytmy i Struktury Danych - Zliczanie 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Zliczanie wg kryterium
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z zliczyć elementy posiadające pożądane własności.

Zliczanie (ang. counting) jest równoważne wyszukiwaniu. Tworzymy licznik elementów, który wstępnie zostaje wyzerowany.
Następnie przeglądamy kolejne elementy zbioru w poszukiwaniu tych, które spełniają zadane kryterium. Gdy znajdziemy
odpowiedni element, zwiększamy stan licznika i wyszukiwanie kontynuujemy od następnego elementu. Wynikiem jest zawartość
licznika, czyli liczba elementów w zbiorze spełniających podane kryterium.
Algorytm podajemy w dwóch wariantach – z wyszukiwaniem liniowym oraz z wyszukiwaniem liniowym z wartownikiem. W tym
drugim przypadku zawsze zliczamy o jeden element za dużo (elementy zbioru spełniające kryterium plus wartownik). Dlatego
zwracamy zawartość licznika pomniejszoną o 1.
W naszym algorytmie kryterium zliczania jest to, iż element zbioru jest równy kluczowi k. W rzeczywistości kryteria mogą być
zupełnie inne, np. zliczamy wszystkie elementy zbioru o wartości większej od średniej arytmetycznej wszystkich elementów,
zliczamy elementy podzielne przez k, itp.

Algorytm zliczania liniowego


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą
na n - 1.
p – indeks pierwszego elementu Z, od którego rozpoczniemy poszukiwania. p C
k – poszukiwana wartość, czyli tzw. klucz, wg którego zliczamy elementy w Z
Wyjście:
Liczba elementów zbioru równych k.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
L – licznik znalezionych elementów, L C

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0032.php 201 / 519


Algorytmy i Struktury Danych - Zliczanie 2014-10-03

Lista kroków:
K01: L ← 0 ; zerujemy licznik
K02: Dla i = p,p+1,...,n-1: wykonuj K03 ; przeglądamy kolejne elementy w zbiorze
K03: Jeśli Z[i] = k, to L ← L + 1 ; zliczamy elementy spełniające kryterium
K04: Zakończ z wynikiem L

Algorytm zliczania liniowego z wartownikiem


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą
na n. Ostatni element, Z[n], przeznaczony jest na wartownika.
p – indeks pierwszego elementu Z, od którego rozpoczniemy poszukiwania. p C
k – poszukiwana wartość, czyli tzw. klucz, wg którego zliczamy elementy w Z
Wyjście:
Liczba elementów zbioru równych k.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
L – licznik znalezionych elementów, L C

Lista kroków:
K01: Z[n] ← k ; na koniec zbioru wstawiamy wartownika
K02: L ← 0 ; zerujemy licznik znalezionych elementów
K03: i ← p ; przeszukiwanie rozpoczynamy od pozycji p
K04: Jeśli Z[i] ≠ k, to idź do K07 ; sprawdzamy, czy element i-ty spełnia kryterium
K05: L ← L + 1 ; jeśli tak, zliczamy go
K06: Jeśli i = n, to zakończ z wynikiem L - 1 ; sprawdzamy, czy był to wartownik
K07: i ← i + 1 ; jeśli nie, przechodzimy do następnego elementu
K08: Idź do K04

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 40 liczb pseudolosowych z zakresu od 0 do 9. Wygenerowane liczby zapisuje w tablicy Z.


Następnie generuje liczbę pseudolosową k z zakresu od 0 do 9 i zlicza jej wystąpienia w Z. Na końcu wypisuje w
jednym wierszu zawartość tablicy Z, a w następnym liczbę k wraz z ilością jej wystąpień w Z.

Lazarus

// Zliczanie z wartownikiem
// Data: 27.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 40;
var
Z : array[0..N] of integer;
k,i,L : integer;
begin
randomize;
for i := 0 to N - 1 do Z[i] := random(10);
k := random(10);
Z[N] := k;
L := 0;
i := 0;
while true do
begin
if Z[i] = k then
begin
inc(L);
if i = N then break;
end;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0032.php 202 / 519


Algorytmy i Struktury Danych - Zliczanie 2014-10-03

inc(i);
end;
for i := 0 to N - 1 do write(Z[i]:2);
writeln(k:2,' : ',L - 1);
end.

Code::Blocks

// Zliczanie z wartownikiem
// Data: 27.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 40;
int main()
{
int Z[N + 1],k,i,L;
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 10;
Z[N] = k = rand() % 10;
L = 0;
for(i = 0; ; i++)
if(Z[i] == k)
{
L++;
if(i == N) break;
}
for(i = 0; i < N; i++) cout << setw(2) << Z[i];
cout << setw(2) << k << " : " << L - 1 << endl;
return 0;
}

Free Basic

' Zliczanie z wartownikiem


' Data: 27.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 40
Dim As Integer Z(N),k,i,L
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 9)
Next
k = Cint(Rnd * 9)
Z(N) = k
L = 0
i = 0
Do
If Z(i) = k Then
L += 1
If i = N Then Exit Do
End If
i += 1
Loop
For i = 0 To N - 1
Print Using "##";Z(i);
Next
Print Using "## : ##";k;L-1
End

Wynik
2 0 6 8 5 1 7 4 2 8 0 0 0 5 8 7 0 5 5 7 1 5 7 6 0 0 4 6 7 1 7 0 2 4 4 5 1 7 1 1
5 : 6

Zliczanie liniowe z wartownikiem


(C)2012 mgr Jerzy Wałaszek

Wykonaj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0032.php 203 / 519


Algorytmy i Struktury Danych - Zliczanie 2014-10-03

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0032.php 204 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie max lub min


Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z znaleźć element o maksymalnej (minimalnej) wartości.

Zadanie znajdowania elementu maksymalnego lub minimalnego jest typowym zadaniem wyszukiwania, które rozwiązujemy przy
pomocy algorytmu wyszukiwania liniowego. Za tymczasowy maksymalny (minimalny) element przyjmujemy pierwszy element
zbioru. Następnie element tymczasowy porównujemy z kolejnymi elementami. Jeśli któryś z porównywanych elementów jest
większy (mniejszy) od elementu tymczasowego, to za nowy tymczasowy element maksymalny (minimalny) przyjmujemy
porównywany element zbioru. Gdy cały zbiór zostanie przeglądnięty, w elemencie tymczasowym otrzymamy element maksymalny
(minimalny) w zbiorze.
Poniżej podajemy algorytm wyszukiwania max. Wyszukiwanie min wykonuje się identycznie, zmianie ulega tylko warunek
porównujący element tymczasowy z elementem zbioru w kroku K03.

Algorytm wyszukiwania elementu maksymalnego w zbiorze


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą
na n - 1.
Wyjście:
Wartość największego elementu zbioru.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
maxZ – tymczasowy element maksymalny

Lista kroków:
; za tymczasowy element maksymalny bierzemy pierwszy
K01: maxZ ← Z[0]
element
K02: Dla i = 1,2,...,n-1 wykonuj K03 ; przeglądamy następne elementy zbioru

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 205 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

Jeśli Z[i] > maxZ, to maxZ ← ; jeśli natrafimy na większy od maxZ, to zapamiętujemy go w
K03:
Z[i] maxZ
K04: Zakończ z wynikiem maxZ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 15 liczb pseudolosowych z zakresu od 0 do 9999. Wygenerowane liczby zapisuje w tablicy Z.
Następnie wyszukuje liczbę najmniejszą i największą. Jako wynik jest jest wypisywana zawartość całej tablicy w 15
kolejnych wierszach, a w wierszu 17 liczba najmniejsza i największa.

Lazarus

// Wyszukiwanie max i min


// Data: 28.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 15;
var
Z : array[0..N-1] of integer;
i,maxZ,minZ: integer;
begin
randomize;
for i := 0 to N - 1 do Z[i] := random(10000);
maxZ := Z[0]; minZ := Z[0];
for i := 1 to N - 1 do
begin
if Z[i] > maxZ then maxZ := Z[i];
if Z[i] < minZ then minZ := Z[i];
end;
for i := 0 to N - 1 do writeln(Z[i]:5);
writeln;
writeln(minZ:5,maxZ:5);
writeln;
end.

Code::Blocks

// Wyszukiwanie max i min


// Data: 28.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 15;
int main()
{
int Z[N],i,maxZ,minZ;
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 10000;
maxZ = minZ = Z[0];
for(i = 1; i < N; i++)
{
if(Z[i] > maxZ) maxZ = Z[i];
if(Z[i] < minZ) minZ = Z[i];
}
for(i = 0; i < N; i++) cout << setw(5) << Z[i] << endl;
cout << endl << setw(5) << minZ << setw(5) << maxZ << endl << endl;
return 0;
}

Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 206 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

' Wyszukiwanie max i min


' Data: 28.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 15
Dim As Integer Z(N-1),i,maxZ,minZ
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 9999)
Next
maxZ = Z(0): minZ = Z(0)
For i = 1 To N - 1
If Z(i) > maxZ Then maxZ = Z(i)
If Z(i) < minZ Then minZ = Z(i)
Next
For i = 0 To N - 1
Print Using "#####";Z(i)
Next
Print
Print Using "##### ####";minZ;maxZ
Print
End

Wynik
89
282
5838
8656
6423
5088
4859
2017
8291
8524
127
621
3826
4914
1733
89 8656

Wyszukiwanie max i min


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Czasami zależy nam nie na wartości elementu maksymalnego (minimalnego), lecz na jego pozycji w zbiorze. W tym przypadku
musimy, oprócz wartości samego elementu maksymalnego (minimalnego), zapamiętywać jego pozycję, którą na końcu zwracamy
jako wynik pracy algorytmu.

Algorytm wyszukiwania pozycji elementu maksymalnego w zbiorze


Wejście
n – liczba elementów w tablicy Z, n N
Z tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą
– na n - 1.

Wyjście:
Pozycja (indeks) elementu maksymalnego. W maxZ jest wartość elementu maksymalnego.

Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
maxZ – tymczasowy element maksymalny
maxp – pozycja tymczasowego elementu maksymalnego

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 207 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

Lista kroków:
; za tymczasowy element maksymalny bierzemy
K01: maxZ ← Z[0] pierwszy element
K02: maxp ← 0 ; zapamiętujemy jego pozycję
K03: Dla i = 1,2,...,n-1 wykonuj K04...K06 ; przeglądamy następne elementy zbioru
K04: Jeśli Z[i] ≤ maxZ, to następny obieg ; sprawdzamy, czy trafiliśmy na element większy od
pętli K03 maxZ
K05: maxZ ← Z[i] ; jeśli tak, to zapamiętujemy wartość elementu w maxZ
K06: maxp ← i ; oraz jego pozycję w maxp
K07: Zakończ z wynikiem maxp ; zwracamy zapamiętaną pozycję

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 15 liczb pseudolosowych z zakresu od 0 do 9999. Wygenerowane liczby zapisuje w tablicy Z.
Następnie wyszukuje liczbę najmniejszą i największą. Jako wynik jest jest wypisywana zawartość całej tablicy w 15
kolejnych wierszach, a w wierszu 17 podana zostaje pozycja elementu najmniejszego i największego

Lazarus

// Wyszukiwanie pozycji max i min


// Data: 29.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 15;
var
Z : array[0..N-1] of integer;
i,maxZ,maxp,minZ,minp : integer;
begin
randomize;
for i := 0 to N - 1 do Z[i] := random(10000);
maxZ := Z[0]; minZ := maxZ;
maxp := 0; minp := 0;
for i := 0 to N - 1 do
begin
if Z[i] < minZ then
begin
minZ := Z[i]; minp := i;
end;
if Z[i] > maxZ then
begin
maxZ := Z[i]; maxp := i;
end;
end;
for i := 0 to N - 1 do writeln(i:2,' : ',Z[i]:4);
writeln;
writeln(minp,':',maxp);
writeln;
end.

Code::Blocks

// Wyszukiwanie pozycji max i min


// Data: 29.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 15;
int main()
{

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 208 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

int Z[N],i,maxZ,maxp,minZ,minp;
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 10000;
maxZ = minZ = Z[0];
maxp = minp = 0;
for(i = 0; i < N; i++)
{
if(Z[i] < minZ)
{
minZ = Z[i]; minp = i;
}
if(Z[i] > maxZ)
{
maxZ = Z[i]; maxp = i;
}
}
for(i = 0; i < N; i++) cout << setw(2) << i << " : " << setw(4) << Z[i] << endl;
cout << endl << minp << ":" << maxp << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie pozycji max i min


' Data: 29.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 15
Dim As Integer Z(N-1),i,maxZ,maxp
Dim As Integer minZ,minp
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 9999)
Next
maxZ = Z(0): minZ = maxZ
maxp = 0: minp = 0
For i = 0 To N - 1
If Z(i) < minZ Then
minZ = Z(i): minp = i
End If
If Z(i) > maxZ Then
maxZ = Z(i): maxp = i
End If
Next
For i = 0 To N - 1
Print Using "## : ####";i;Z(i)
Next
Print
Print minp;":";maxp
Print
End

Wynik
0 : 7223
1 : 2137
2 : 7026
3 : 5658
4 : 5012
5 : 6046
6 : 5609
7 : 6493
8 : 916
9 : 9623
10 : 2385
11 : 122
12 : 4679
13 : 7316
14 : 2069
11:9

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 209 / 519


Algorytmy i Struktury Danych - Max lub Min 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0033.php 210 / 519


Algorytmy i Struktury Danych - Jednoczesne Max i Min 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Jednoczesne wyszukiwanie max i min


Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z znaleźć element maksymalny i minimalny.

Jeśli do wyszukiwania elementu max i min zastosujemy standardowy algorytm, to otrzymamy:


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą na n - 1.
Wyjście:
Wartość najmniejszego i największego elementu w tablicy Z.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
maxZ – tymczasowy element maksymalny
minZ – tymczasowy element minimalny

Numer Operacja Liczba wykonań


1 minZ ← Z[0] 1
2 maxZ ← Z[0] 1
3 i ←1 1
4 Jeśli i = n, to idź do 9 n
5 Jeśli Z[i] < minZ, to minZ ← Z[i] n-1
6 Jeśli Z[i] > maxZ, to maxZ ← Z[i] n-1
7 i ←i + 1 n-1
8 Idź do 4 n-1
9 Pisz minZ, maxZ 1
10 Zakończ 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0034.php 211 / 519


Algorytmy i Struktury Danych - Jednoczesne Max i Min 2014-10-03

Zastanówmy się, czy można uzyskać lepszy wynik. Algorytm porównuje każdy element zbioru z minZ oraz z maxZ. Tutaj tkwi
nieefektywność. Obie operacje nr 5 i 6 są wykonywane po n - 1 razy, co daje w sumie 2n - 2 porównania w całym zbiorze.
Wyobraźmy sobie, iż ze zbioru bierzemy parę 2 elementów i porównujemy je ze sobą. Element większy nie może być
minimalnym, zatem nie musimy go już porównywać z minZ. Wystarczy porównanie z maxZ. To samo dotyczy elementu
mniejszego – porównujemy go tylko z minZ. Otrzymujemy zysk – poprzednio dwa elementy wymagały wykonania 4 porównań
(każdy z minZ i maxZ). Teraz mamy:

Jedno porównanie dwóch elementów, które rozdzieli je na element mniejszy i element większy.
Element mniejszy porównujemy z minZ.
Element większy porównujemy z maxZ.

Daje to 3 porównania zamiast 4. Ponieważ ze zbioru pobieramy po dwa kolejne elementy, to ich liczba musi być parzysta. Jeśli
zbiór ma nieparzystą liczbę elementów, to po prostu dublujemy ostatni element i dostaniemy parzystą liczbę elementów.
Porównanie pary elementów dzieli zbiór na dwa podzbiory – zbiór elementów większych i mniejszych. W podzbiorze elementów
większych szukamy elementu maksymalnego, a w podzbiorze elementów mniejszych szukamy minimalnego. Ponieważ ich
liczebność jest o połowę mniejsza od liczebności zbioru wejściowego, to wykonamy mniej operacji porównań.
Metoda rozwiązywania problemów przez podział na mniejsze części w informatyce nosi nazwę Dziel i Zwyciężaj (ang. Divide and
Conquer). Dzięki niej często udaje się zmniejszyć złożoność obliczeniową wielu algorytmów.

Algorytm jednoczesnego wyszukiwania max i min w zbiorze


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do zliczania. Indeksy elementów rozpoczynają się od 0, a kończą
na n - 1. Tablica musi umożliwiać dodanie elementu, jeśli n jest nieparzyste.
Wyjście:
Element minimalny i maksymalny w tablicy Z
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
maxZ – tymczasowy element maksymalny
maxp – pozycja tymczasowego elementu maksymalnego

Lista kroków:
K01: Jeśli n mod 2 = 1, Z[n] ← Z[n-1] ; jeśli nieparzysta liczba elementów, dublujemy ostatni
K02: minZ ← największa liczba całkowita ; inicjujemy minZ i maxZ
K03: maxZ ← najmniejsza liczba całkowita
K04: i ← 0
K05: Dopóki i < n wykonuj K06...K12 ; wybieramy pary kolejnych elementów
K06: Jeśli Z[i] > Z[i+1], to idź do K10 ; rozdzielamy je na element mniejszy i większy
K07: Jeśli Z[i] < minZ, to minZ ← Z[i] ; Z[i] - mniejszy, Z[i+1] - większy
K08: Jeśli Z[i+1] > maxZ, to maxZ ← Z[i+1]
K09: Idź do K12
K10: Jeśli Z[i] > maxZ, to maxZ ← Z[i] ; Z[i] - większy, Z[i+1] - mniejszy
K11: Jeśli Z[i+1] < minZ, to minZ ← Z[i+1]
K12: i ← i + 2 ; przechodzimy do następnej pary elementów
K13: Pisz minZ, maxZ ; wyprowadzamy wyniki
K14: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 15 liczb pseudolosowych z zakresu od 0 do 9999. Wygenerowane liczby zapisuje w tablicy Z.
Następnie wyszukuje liczbę najmniejszą i największą. Jako wynik jest jest wypisywana zawartość całej tablicy w 15
kolejnych wierszach, a w wierszu 17 podane zostają element minimalny i maksymalny.

Lazarus

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0034.php 212 / 519


Algorytmy i Struktury Danych - Jednoczesne Max i Min 2014-10-03

// Jednoczesne min i max


// Data: 30.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 15;
var Z : array[0..N] of integer;
minZ,maxZ,i : integer;
begin
randomize;
for i := 0 to N-1 do Z[i] := random(10000);
if N mod 2 = 1 then Z[N] := Z[N-1];
minZ := 10000; maxZ := -1;
i := 0;
while i < N do
begin
if Z[i] > Z[i+1] then
begin
if Z[i] > maxZ then maxZ := Z[i];
if Z[i+1] < minZ then minZ := Z[i+1];
end
else
begin
if Z[i] < minZ then minZ := Z[i];
if Z[i+1] > maxZ then maxZ := Z[i+1];
end;
inc(i,2);
end;
for i := 0 to N-1 do writeln(Z[i]:4);
writeln;
writeln(minZ,' : ',maxZ);
writeln;
end.

Code::Blocks

// Jednoczesne min i max


// Data: 30.04.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 15;
int main()
{
int Z[N+1],minZ,maxZ,i;
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 10000;
if(N % 2) Z[N] = Z[N-1];
minZ = 10000; maxZ = -1;
for(i = 0; i < N; i += 2)
if(Z[i] > Z[i+1])
{
if(Z[i] > maxZ) maxZ = Z[i];
if(Z[i+1] < minZ) minZ = Z[i+1];
}
else
{
if(Z[i] < minZ) minZ = Z[i];
if(Z[i+1] > maxZ) maxZ = Z[i+1];
}
for(i = 0; i < N; i++) cout << setw(4) << Z[i] << endl;
cout << endl << minZ << " : " << maxZ << endl << endl;
return 0;
}

Free Basic

' Jednoczesne min i max


' Data: 30.04.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 15

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0034.php 213 / 519


Algorytmy i Struktury Danych - Jednoczesne Max i Min 2014-10-03

Dim As Integer Z(N),minZ,maxZ,i


Randomize
For i = 0 To N-1
Z(i) = Cint(Rnd * 9999)
Next
If N Mod 2 = 1 Then Z(N) = Z(N-1)
minZ = 10000: maxZ = -1
i = 0
While i < N
If Z(i) > Z(i+1) Then
If Z(i) > maxZ Then maxZ = Z(i)
If Z(i+1) < minZ Then minZ = Z(i+1)
Else
If Z(i) < minZ Then minZ = Z(i)
If Z(i+1) > maxZ Then maxZ = Z(i+1)
End If
i += 2
Wend
For i = 0 To N-1
Print Using "####";Z(i)
Next
Print
Print minZ;" : ";maxZ
Print
End

Wynik
4850
3151
2213
272
7933
2490
1267
4923
713
987
3253
5533
1759
8103
9414
272 : 9414

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0034.php 214 / 519


Algorytmy i Struktury Danych - Jednoczesne Max i Min 2014-10-03

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0034.php 215 / 519


Algorytmy i Struktury Danych - Sortowanie przez wybór 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Sortowanie przez wybór


Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
Posortować rosnąco n-elementowy zbiór Z.

Zadanie sortowania polega na takim ułożeniu elementów zbioru Z, aby zachodził pomiędzy nimi porządek zdefiniowany
odpowiednią relacją porządkującą. Porządek rosnący oznacza, iż w miarę posuwania się w głąb zbioru, wartości elementów stają
się coraz większe – nie napotkamy elementu mniejszego od któregokolwiek z elementów go poprzedzających w zbiorze. Relacją
porządkującą jest relacja < lub ≤ przy dopuszczeniu elementów o równych wartościach – jednakże wtedy nie jest określona
kolejność elementów równych.
Istnieje bardzo wiele różnych algorytmów sortowania (ang. Sorting Algorithms). W tym rozdziale przedstawimy bardzo prosty
algorytm, który wykorzystuje liniowe wyszukiwanie (ang. linear search) elementu minimalnego w zbiorze – sortowanie przez
wybór (ang. Selection Sort). Zasada pracy tego algorytmu jest następująca:

Wyszukujemy w zbiorze Z najmniejszy element minZ. Element najmniejszy w zbiorze posortowanym powinien
znaleźć się na pierwszej pozycji. Zatem znaleziony element wymieniamy z elementem pierwszym. Dalej
postępujemy identycznie ze zbiorem Z pomniejszonym o pierwszy element.. Znajdujemy kolejny element minimalny i
wymieniamy go z elementem na pozycji drugiej. Sortowanie tym samym sposobem kontynuujemy dla kolejnych
podzbiorów Z. Gdy otrzymamy podzbiór jednoelementowy (czyli gdy wykonamy n - 1 wyborów elementów
minimalnych). Kolejno znajdowane elementy minimalne tworzą ciąg niemalejący – to wyjaśnia skuteczność tej
metody przy sortowaniu zbioru.

Algorytm sortowania przez wybór


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica zawierająca elementy do posortowania.
Wyjście:
Tablica Z z posortowanymi rosnąco elementami
Zmienne pomocnicze

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0035.php 216 / 519


Algorytmy i Struktury Danych - Sortowanie przez wybór 2014-10-03

i,j – przebiegają przez kolejne indeksy elementów Z. i,j C


minZ – tymczasowy element minimalny
minp – pozycja tymczasowego elementu minimalnego, minp C

Lista kroków:
K01: Dla i = 0,1,...,n-2 wykonuj K02...K08
K02: minZ ← Z[i] ; tymczasowy element minimalny
K03: minp ← i ; oraz jego tymczasowa pozycja
K04: Dla j = i+1,i+2,...,n-1 wykonuj K05...K07 ; w pozostałych elementach szykamy
minimalnego
Jeśli Z[j] ≥ minZ, to kolejny obieg pętli
K05:
K04...K07
K06: minZ ← Z[j] ; znaleźliśmy, uaktualniamy minZ oraz minp
K07: minp ← j
K08: Z[i] ↔ Z[minp] ; zamieniamy znaleziony element z i-tym
K09: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia 200 elementową tablicę liczbami pseudolosowymi od 0 do 999. Następnie wyświetla zawartość tej
tablicy przed sortowaniem, sortuje ją opisanym algorytmem sortowania przez wybór i na koniec wyświetla tablicę
posortowaną.

Lazarus

// Sortowanie przez wybór


// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 200;
var Z : array[0..N - 1] of integer;
minZ,minp,i,j,x : integer;
begin
randomize;
for i := 0 to N - 1 do Z[i] := random(1000);
// Przed sortowaniem
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
// Sortowanie przez wybór
for i := 0 to N - 2 do
begin
minZ := Z[i];
minp := i;
for j := i + 1 to N - 1 do
if Z[j] < minZ then
begin
minZ := Z[j];
minp := j;
end;
x := Z[i];
Z[i] := Z[minp];
Z[minp] := x;
end;
// Po sortowaniu
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
end.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0035.php 217 / 519


Algorytmy i Struktury Danych - Sortowanie przez wybór 2014-10-03

Code::Blocks

// Sortowanie przez wybór


// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 200;
int main()
{
int Z[N],minZ,minp,i,j,x;
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Przed sortowaniem
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
// Sortowanie przez wybór
for(i = 0; i < N - 1; i++)
{
minZ = Z[i];
minp = i;
for(j = i + 1; j < N; j++)
if(Z[j] < minZ)
{
minZ = Z[j];
minp = j;
}
x = Z[i];
Z[i] = Z[minp];
Z[minp] = x;
}
// Po sortowaniu
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
return 0;
}

Free Basic

' Sortowanie przez wybór


' Data: 1.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 200
Dim As Integer Z(N - 1),minZ,minp,i,j
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 999)
Next
' Przed sortowaniem
For i = 0 To N - 1
Print Using "####";Z(i);
Next
Print
' Sortowanie przez wybór
For i = 0 To N - 2
minZ = Z(i)
minp = i
For j = i + 1 To N - 1
If Z(j) < minZ Then
minZ = Z(j)
minp = j
End If
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0035.php 218 / 519


Algorytmy i Struktury Danych - Sortowanie przez wybór 2014-10-03

Swap Z(i),Z(minp)
Next
' Po sortowaniu
For i = 0 To N - 1
Print Using "####";Z(i);
Next
Print
End

Wynik
996 402 535 550 195 212 312 58 55 550 383 146 299 29 147 723 430 271 70 891
566 958 414 261 157 434 37 866 229 726 619 615 325 147 277 761 824 326 126 999
258 880 123 539 692 469 843 23 719 544 419 894 249 316 965 478 54 329 256 457
400 482 728 190 639 235 287 427 150 182 666 597 554 245 122 659 125 526 385 949
877 483 709 720 867 961 348 598 761 526 997 154 764 750 138 245 183 489 274 524
729 451 165 989 879 812 507 629 294 304 356 68 617 970 113 726 697 222 731 532
583 734 382 934 325 278 998 705 630 337 676 297 401 881 190 531 848 215 807 646
659 669 167 102 461 318 15 911 552 640 525 966 423 263 354 865 331 862 13 279
548 300 620 448 272 779 226 912 807 413 830 849 566 449 111 120 125 329 711 650
196 39 171 6 707 891 517 38 934 118 707 546 478 213 726 319 725 376 328 764
6 13 15 23 29 37 38 39 54 55 58 68 70 102 111 113 118 120 122 123
125 125 126 138 146 147 147 150 154 157 165 167 171 182 183 190 190 195 196 212
213 215 222 226 229 235 245 245 249 256 258 261 263 271 272 274 277 278 279 287
294 297 299 300 304 312 316 318 319 325 325 326 328 329 329 331 337 348 354 356
376 382 383 385 400 401 402 413 414 419 423 427 430 434 448 449 451 457 461 469
478 478 482 483 489 507 517 524 525 526 526 531 532 535 539 544 546 548 550 550
552 554 566 566 583 597 598 615 617 619 620 629 630 639 640 646 650 659 659 666
669 676 692 697 705 707 707 709 711 719 720 723 725 726 726 726 728 729 731 734
750 761 761 764 764 779 807 807 812 824 830 843 848 849 862 865 866 867 877 879
880 881 891 891 894 911 912 934 934 949 958 961 965 966 970 989 996 997 998 999

Sortowanie przez wybór


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0035.php 219 / 519


Algorytmy i Struktury Danych - Sortowanie przez wybór 2014-10-03

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0035.php 220 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie najczęstszej wartości w zbiorze – dominanta


Tematy pokrewne Podrozdziały
Tablice – wektory Rozwiązanie 1
Podstawowe operacje na tablicach Rozwiązanie 2
Wyszukiwanie liniowe Rozwiązanie 3
Wyszukiwanie liniowe z wartownikiem Rozwiązanie 4
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z znaleźć wartość występującą najczęściej.

Zadanie o podobnej treści pojawiło się w zestawie pytań maturalnych z informatyki w 2006 roku.

Rozwiązanie nr 1
Problem wyszukiwania najczęstszej wartości (ang. searching for the most frequent value), czyli tzw. dominanty zbioru, możemy
rozwiązać na kilka różnych sposobów. Pierwszym, najprostszym podejściem jest metoda bezpośrednia – naiwna:

Przeglądamy kolejne elementy zbioru i zliczamy liczbę wystąpień ich wartości w zbiorze. Można to robić tak –
zapamiętujemy i-ty element i ustawiamy licznik wystąpień na 0. Następnie przeglądamy cały zbiór i, jeśli trafimy na
element o takiej samej wartości, to licznik zwiększamy o 1. Po wykonaniu tej operacji porównujemy zawartość
licznika z tymczasową maksymalną liczbą wystąpień (na początku algorytmu jest ona ustawiana na 0). Jeśli stan
licznika jest większy, to zapamiętujemy go w tymczasowej maksymalnej liczbie wystąpień oraz zapamiętujemy
wyszukiwaną wartość elementu. Gdy przeglądniemy w ten sposób i zliczymy wystąpienia wartości wszystkich
elementów w zbiorze Z, to otrzymamy element powtarzający się najczęściej oraz ilość tych powtórzeń.
Klasa złożoności obliczeniowej tak zdefiniowanego algorytmu jest równa O(n2). Jeśli elementów jest niewiele (do
około 5000) i szybkość działania nie gra istotnej roli, to rozwiązanie naiwne jest do przyjęcia – tak właśnie było w
przypadku zadania maturalnego.

Algorytm wygląda następująco:

Algorytm znajdowania najczęstszej wartości – wersja nr 1


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica do wyszukania najczęstszego elementu Elementy są liczbami całkowitymi. Indeksy od 0
do n - 1..

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 221 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

Wyjście:
Element występujący najczęściej w Z oraz liczba jego wystąpień. Jeśli jest kilka elementów o takiej
samej maksymalnej liczbie wystąpień, to algorytm zwraca pierwszy z nich, który został napotkany w
zbiorze.
Zmienne pomocnicze
i, j – przebiega przez kolejne indeksy elementów Z. i,j C
L – licznik wystąpień wartości elementu, L C
W – wartość elementu, której liczbę wystąpień w Z zliczamy.
maxW – najczęstsza wartość elementów tablicy Z
maxL – liczba wystąpień wartości najczęstszej. maxL C

Lista kroków:

K01: maxL ← 0 ; zerujemy maksymalną liczbę wystąpień


K02: Dla i = 0,1,...,n - 1 wykonuj K03...K09 ; przeglądamy kolejne elementy tablicy Z
K03: W ← Z[i] ; zapamiętujemy poszukiwaną wartość elementu
K04: L ← 0 ; zerujemy jej licznik wystąpień
K05: Dla j = 0,1,...,n - 1 wykonuj K06 ; zliczamy wystąpienia W
K06: Jeśli Z[j] = W, to L ← L + 1
Jeśli L ≤ maxL, to następny obieg pętli ; sprawdzamy, czy W jest tymczasowo
K07:
K2 najczęstsze
K08: maxL ← L ; jeśli tak, zapamiętujemy liczbę wystąpień
K09: maxW ← W ; oraz wartość W
K10: Pisz max W , max L ; wypisujemy wyniki
K11: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w tablicy Z 400 liczb pseudolosowych z zakresu od 0 do 99, wyznacza wartość występującą
najczęściej, wyświetla zawartość tej tablicy zaznaczając wartości najczęstsze i wypisuje tę wartość oraz liczbę jej
wystąpień.

Lazarus

// Najczęstsza wartość
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 400;
var
Z : array[0..N-1] of integer;
i,j,L,W,maxL,maxW : integer;
begin
// Generujemy zawartość Z[]
randomize;
for i := 0 to N - 1 do Z[i] := random(100);
// Wyszukujemy najczęstszą wartość
maxL := 0;
for i := 0 to N - 1 do
begin
W := Z[i]; L := 0;
for j := 0 to N - 1 do if Z[j] = W then inc(L);
if L > maxL then
begin
maxL := L; maxW := W;
end;
end;
// Wypisujemy tablicę

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 222 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

for i := 0 to N - 1 do
if Z[i] = maxW then write(' >',Z[i]:2)
else write(Z[i]:4);
// Wypisujemy najczęstszy element
// oraz liczbę wystąpień
writeln;
writeln(maxW,' : ',maxL);
writeln;
end.

Code::Blocks

// Najczęstsza wartość
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
const int N = 400;
int Z[N],i,j,L,W,maxL,maxW;
// Generujemy zawartość Z[]
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 100;
// Wyszukujemy najczęstszą wartość
maxL = 0;
for(i = 0; i < N; i++)
{
W = Z[i]; L = 0;
for(j = 0; j < N; j++) if(Z[j] == W) L++;
if(L > maxL)
{
maxL = L; maxW = W;
}
}
// Wypisujemy tablicę
for(i = 0; i < N; i++)
if(Z[i] == maxW) cout << " >" << setw(2) << Z[i];
else cout << setw(4) << Z[i];
// Wypisujemy najczęstszy element
// oraz liczbę wystąpień
cout << endl << maxW << " : " << maxL << endl << endl;
return 0;
}

Free Basic

' Najczęstsza wartość


' Data: 4.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 400
Dim As Integer Z(N-1)
Dim As Integer i,j,L,W,maxL,maxW
' Generujemy zawartość Z[]
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 99)
Next
' Wyszukujemy najczęstszą wartość
maxL = 0
For i = 0 To N - 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 223 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

W = Z(i): L = 0
For j = 0 To N - 1
If Z(j) = W Then L += 1
Next
If L > maxL Then
maxL = L: maxW = W
End If
Next
' Wypisujemy tablicę
For i = 0 To N - 1
If Z(i) = maxW Then
Print Using " >##";Z(i);
Else
Print Using "####";Z(i);
End If
Next
' Wypisujemy najczęstszy element
' oraz liczbę wystąpień
Print
Print maxW;" : ";maxL
Print
End

Wynik
21 84 16 62 83 81 6 92 96 >85 18 72 70 96 8 37 23 91 74 99
87 14 33 20 58 25 42 55 41 3 91 23 38 98 42 91 51 35 31 39
65 17 46 5 89 >85 39 5 60 >85 30 36 60 80 73 9 6 90 55 51
5 84 25 75 16 71 39 >85 70 6 6 72 30 93 55 93 67 6 35 35
48 18 65 25 50 11 17 90 15 30 84 82 64 5 50 74 47 11 10 92
79 0 6 1 75 79 65 11 33 22 86 24 61 96 12 46 29 82 23 62
35 36 39 26 59 46 97 57 29 58 69 86 27 28 17 81 55 38 26 45
76 39 16 71 78 29 49 42 6 38 24 25 45 52 89 49 52 36 92 79
49 26 57 86 11 17 67 93 96 29 96 16 79 36 70 19 11 20 60 46
43 >85 25 90 11 96 1 92 80 20 60 1 13 30 72 80 >85 47 87 67
29 90 20 98 47 6 95 60 19 77 90 22 13 52 23 50 54 45 29 50
4 21 60 56 75 59 94 5 88 >85 15 49 5 77 64 62 71 82 32 42
59 35 8 45 44 52 71 7 12 79 58 29 9 7 90 >85 87 68 65 38
32 0 55 12 56 76 97 58 24 91 97 62 49 34 49 96 6 18 44 15
50 3 32 13 67 16 38 69 89 66 73 84 82 57 68 65 89 19 49 33
34 94 13 27 19 5 49 59 82 21 25 38 49 94 >85 45 48 63 53 2
1 70 37 13 89 51 44 20 24 46 74 89 2 11 87 28 83 30 23 81
64 27 15 81 95 93 71 29 47 45 17 64 90 77 1 73 61 26 79 47
91 82 17 88 26 22 57 63 12 14 43 74 31 40 50 28 36 61 77 77
15 19 41 71 23 24 3 33 32 65 30 97 >85 38 62 87 82 65 53 24
85 : 10

Najczęstsza wartość w zbiorze


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Podany powyżej algorytm jest bardzo prymitywny i wykonuje wiele niepotrzebnych działań. Postarajmy się je wyeliminować.

1. Ze zbioru bierzemy KAŻDY element Z[i] i zliczamy wystąpienia jego wartości W w CAŁYM zbiorze Z. W ten sposób najczęstsza
wartość maxW zostanie zliczona maxL2 razy. Zatem pierwsze ulepszenie będzie polegało na tym, iż pobrany element W zbioru
zliczamy tylko wtedy, gdy jest on różny od maxW. Wynika z tego, iż na początku pracy algorytmu do maxW musimy wpisać
wartość różną od Z[0] (dlaczego?).
2. Każdy element Z[i] zliczamy w całym zbiorze. Tymczasem, jeśli wartość i-tego elementu wystąpiła wcześniej w zbiorze, to jest
już zliczona. Jeśli nie wystąpiła wcześniej, to nie ma sensu jej tam szukać, nieprawdaż? W obu przypadkach wystarczy, jeśli
zliczanie rozpoczniemy od pozycji i + 1 do końca zbioru. Elementy zliczone po prostu zliczymy ponownie, lecz w mniejszym
zakresie. Elementy nowe zliczymy poprawnie, od miejsca ich pierwszego wystąpienia.
3. Jeśli w trakcie działania algorytmu w maxL mamy chwilową maksymalną liczbę wystąpień pewnej wartości maxW, to zliczanie
nowych elementów możemy przerwać po osiągnięciu pozycji n - maxL. Jeśli nowy element występuje na tej lub dalszej pozycji w
zbiorze Z, to liczba wystąpień jego wartości nie będzie już większa od maxL, gdyż braknie elementów.

Algorytm znajdowania najczęstszej wartości – wersja nr 2


Wejście
n

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 224 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

n – liczba elementów w tablicy Z, n N


Z – tablica do wyszukania najczęstszego elementu Elementy są liczbami całkowitymi. Indeksy od 0 do n - 1..
Wyjście:
Element występujący najczęściej w Z oraz liczba jego wystąpień. Jeśli jest kilka elementów o takiej samej
maksymalnej liczbie wystąpień, to algorytm zwraca pierwszy z nich, który został napotkany w zbiorze.
Zmienne pomocnicze
i, j – przebiega przez kolejne indeksy elementów Z. i,j C
L – licznik wystąpień wartości elementu, L C
W – wartość elementu, której liczbę wystąpień w Z zliczamy.
maxW – najczęstsza wartość elementów tablicy Z
maxL – liczba wystąpień wartości najczęstszej. maxL C

Lista kroków:

K01: maxL ← 0 ; licznik wystąpień najczęstszej wartości


K02: maxW ← Z[0] - 1 ; najczęstsza wartość - musi być różna od Z[0]!
K03: i ← 0 ; rozpoczynamy zliczanie wartości elementów
K04: Dopóki i < n - maxL, wykonuj K05...K13 ; zliczamy do n-maxL
K05: W ← Z[i] ; zapamiętujemy wartość bieżącego elementu
K06: Jeśli W = maxW, to idź do K13 ; jeśli jest równa maxW, to pomijamy element Z[i]
K07: L ← 1 ; Z[i] raz zliczony
K08: Dla j = i+1,i+2,...,n-1 wykonuj K09 ; zliczanie rozpoczynamy od następnych elementów
K09: Jeśli Z[j] = W, to L ← L + 1
K10: Jeśli L ≤ maxL, to idź do K13 ; zliczyliśmy więcej niż maxL?
K11: maxL ← L ; jeśli tak, zapamiętujemy L w maxL
K12: maxW ← W ; oraz W w maxW
K13: i ← i + 1 ; przechodzimy do kolejnego elementu Z[i]
K14: Pisz maxW,maxL ; wyprowadzamy wyniki
K15: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w tablicy Z 400 liczb pseudolosowych z zakresu od 0 do 99, wyznacza wartość występującą najczęściej,
wyświetla zawartość tej tablicy zaznaczając wartości najczęstsze i wypisuje tę wartość oraz liczbę jej wystąpień.

Lazarus

// Najczęstsza wartość
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 400;
var
Z : array[0..N-1] of integer;
i,j,L,W,maxL,maxW : integer;
begin
// Generujemy zawartość Z[]
randomize;
for i := 0 to N - 1 do Z[i] := random(100);
// Wyszukujemy najczęstszą wartość
maxL := 0; maxW := Z[0] - 1;
i := 0;
while i < N - maxL do
begin
W := Z[i];
if maxW <> W then
begin

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 225 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

L := 1;
for j := i + 1 to N - 1 do if Z[j] = W then inc(L);
if L > maxL then
begin
maxL := L; maxW := W;
end;
end;
inc(i);
end;
// Wypisujemy tablicę Z[]
for i := 0 to N - 1 do
if Z[i] = maxW then write(' >',Z[i]:2)
else write(Z[i]:4);
// Wypisujemy najczęstszy element
// oraz liczbę wystąpień
writeln;
writeln(maxW,' : ',maxL);
writeln;
end.

Code::Blocks

// Najczęstsza wartość
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
const int N = 400;
int Z[N],i,j,L,W,maxL,maxW;
// Generujemy zawartość Z[]
srand((unsigned)time(NULL));
for(i = 0; i < N; i++) Z[i] = rand() % 100;
// Wyszukujemy najczęstszą wartość
maxL = 0; maxW = Z[0] - 1;
for(i = 0; i < N - maxL; i++)
{
W = Z[i];
if(maxW != W)
{
L = 1;
for(j = i + 1; j < N; j++) if(Z[j] == W) L++;
if(L > maxL)
{
maxL = L; maxW = W;
}
}
}
// Wypisujemy tablicę
for(i = 0; i < N; i++)
if(Z[i] == maxW) cout << " >" << setw(2) << Z[i];
else cout << setw(4) << Z[i];
// Wypisujemy najczęstszy element
// oraz liczbę wystąpień
cout << endl << maxW << " : " << maxL << endl << endl;
return 0;
}

Free Basic

' Najczęstsza wartość


' Data: 4.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 226 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

Const N = 400
Dim As Integer Z(N-1)
Dim As Integer i,j,L,W,maxL,maxW
' Generujemy zawartość Z[]
Randomize
For i = 0 To N - 1
Z(i) = Cint(Rnd * 99)
Next
' Wyszukujemy najczęstszą wartość
maxL = 0: maxW = Z(0) - 1
i = 0
While i < N - maxL
W = Z(i)
If maxW <> W Then
L = 1
For j = i + 1 To N - 1
If Z(j) = W Then L += 1
Next
If L > maxL Then
maxL = L: maxW = W
End If
End If
i += 1
Wend
' Wypisujemy tablicę
For i = 0 To N - 1
If Z(i) = maxW Then
Print Using " >##";Z(i);
Else
Print Using "####";Z(i);
End If
Next
' Wypisujemy najczęstszy element
' oraz liczbę wystąpień
Print
Print maxW;" : ";maxL
Print
End

Wynik

Rozwiązanie nr 3

Jeśli elementy zbioru tworzą spójny przedział wartości i przedział ten nie jest specjalnie duży, to do wyszukania wartości
najczęstszej można zastosować następującą metodę.

Tworzymy tablicę L, której elementy będą pełniły rolę liczników wystąpień wartości elementów z tablicy Z. Zakres
indeksów w L możemy znaleźć wyszukując w tablicy Z wartość minimalną i maksymalną (można też z góry
przydzielić odpowiednią liczbę liczników, gdy znamy zakres wartości elementów w przeszukiwanej tablicy Z).
Zerujemy wszystkie liczniki w L i przeglądamy tablicę Z zwiększając o 1 liczniki w L, które odpowiadają wartościom
przeglądanych elementów. W efekcie po zakończeniu przeglądania tablicy Z w L będziemy mieli informację o liczbie
wystąpień każdej z wartości. Wyznaczamy w L element maksymalny. Wynikiem jest indeks, który określa wartość
występującą najczęściej w Z oraz zawartość elementu L o tym indeksie, która określa ile razy dana wartość
wystąpiła w Z.

Tak określony algorytm wyszukiwania najczęstszego elementu posiada liniową klasę złożoności obliczeniowej O(m + n), gdzie m
jest liczbą wartości elementów przeszukiwanego zbioru, a n jest liczbą jego elementów.

Algorytm znajdowania najczęstszej wartości – wersja nr 3


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica do wyszukania najczęstszego elementu Elementy są liczbami całkowitymi. Indeksy od 0 do n - 1..
minZ – minimalna wartość w Z.
maxZ – maksymalna wartość w Z.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 227 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

Wyjście:
Element występujący najczęściej w Z oraz liczba jego wystąpień. Jeśli jest kilka elementów o takiej samej
maksymalnej liczbie wystąpień, to algorytm w tej wersji zwraca najmniejszy z nich (jeśli pozostałe elementy są
istotne, to można je odczytać z tablicy L porównując liczbę wystąpień z maksymalną).
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
L – tablica liczników wystąpień wartości elementów z Z.
maxL – tymczasowy element maksymalny w L
maxp – pozycja tymczasowego elementu maksymalnego w L

Lista kroków:

K01: Utwórz L o rozmiarze maxZ - minZ + 1 ; przygotowujemy liczniki wartości


K02: Wyzeruj L ; liczniki ustawiamy na 0
K03: Dla i = 0,1,...,n-1 wykonuj K04 ; zliczamy wystąpienia wartości elementów
K04: Zwiększ o 1 L[Z[i] - minZ] ; w odpowiednich licznikach
K05: maxL ← L[0] ; szukamy max w L
K06: maxp ← 0
K07: Dla i = 1,2,...,maxZ-minZ wykonuj K08...K10
Jeśli L[i] ≤ maxL, to następny obieg pętli
K08:
K07
K09: maxL ← L[i] ; zapamiętujemy wartość maksymalną
K10: max p ← i ; oraz jej pozycję
K11: Pisz maxp + minZ, maxL ; wyprowadzamy najczęstszy element oraz liczbę jego
wystąpień
K12: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w tablicy Z 80 liczb pseudolosowych z zakresu od -9 do 9, wyświetla zawartość tej tablicy i wyznacza w niej
wartość występującą najczęściej. Program wypisuje tę wartość oraz liczbę jej wystąpień w tablicy Z.

Lazarus

// Najczęstsza wartość
// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 80;
type Tint = array of integer;
var
Z : array[0..N-1] of integer;
L : Tint;
i,nL,maxZ,minZ,maxL,maxp : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := -9 + random(19);
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
// Wyszukujemy w Z[] min i max
if Z[0] > Z[1] then
begin
minZ := Z[1]; maxZ := Z[0];
end

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 228 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

else
begin
minZ := Z[0]; maxZ := Z[1];
end;
i := 2;
while i < N do
begin
if Z[i] > Z[i+1] then
begin
if Z[i] > maxZ then maxZ := Z[i];
if Z[i+1] < minZ then minZ := Z[i+1];
end
else
begin
if Z[i] < minZ then minZ := Z[i];
if Z[i+1] > maxZ then maxZ := Z[i+1];
end;
inc(i,2);
end;
// Przygotowujemy liczniki
nL := maxZ - minZ + 1;
SetLength(L,nL);
for i := 0 to nL - 1 do L[i] := 0;
// Zliczamy wystąpienia wartości z Z[]
for i := 0 to N - 1 do inc(L[Z[i] - minZ]);
// Szukamy najczęstszej wartości w L[]
maxL := L[0]; maxp := 0;
for i := 0 to nL - 1 do
if L[i] > maxL then
begin
maxL := L[i]; maxp := i;
end;
// Wyświetlamy wyniki
writeln;
writeln(maxp + minZ,' : ',maxL);
writeln;
end.

Code::Blocks

// Najczęstsza wartość
// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80;
int main()
{
int Z[N], * L,i,nL,maxZ,minZ,maxL,maxp;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = -9 + (rand() % 19);
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
// Wyszukujemy w Z[] min i max
if(Z[0] > Z[1])
{
minZ = Z[1]; maxZ = Z[0];
}
else
{
minZ = Z[0]; maxZ = Z[1];
}
for(i = 2; i < N; i += 2)
{

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 229 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

if(Z[i] > Z[i+1])


{
if(Z[i] > maxZ) maxZ = Z[i];
if(Z[i+1] < minZ) minZ = Z[i+1];
}
else
{
if(Z[i] < minZ) minZ = Z[i];
if(Z[i+1] > maxZ) maxZ = Z[i+1];
}
}
// Przygotowujemy liczniki
nL = maxZ - minZ + 1;
L = new int[nL];
for(i = 0; i < nL; i++) L[i] = 0;
// Zliczamy wystąpienia wartości z Z[]
for(i = 0; i < N; i++) L[Z[i] - minZ]++;
// Szukamy najczęstszej wartości w L[]
maxL = L[0]; maxp = 0;
for(i = 0; i < nL; i++)
if(L[i] > maxL)
{
maxL = L[i]; maxp = i;
}
// Wyświetlamy wyniki
cout << endl << (maxp + minZ) << " : " << maxL
<< endl << endl;
delete [] L;
return 0;
}

Free Basic

' Najczęstsza wartość


' Data: 1.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 80
Dim As Integer Z(N-1),i,nL,maxZ,minZ,maxL,maxp
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = -9 + Cint(Rnd * 18): Next
' Wyświetlamy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
' Wyszukujemy w Z[] min i max
If Z(0) > Z(1) Then
minZ = Z(1): maxZ = Z(0)
Else
minZ = Z(0): maxZ = Z(1)
End If
i = 2
While i < N
If Z(i) > Z(i+1) Then
If Z(i) > maxZ Then maxZ = Z(i)
If Z(i+1) < minZ Then minZ = Z(i+1)
Else
If Z(i) < minZ Then minZ = Z(i)
If Z(i+1) > maxZ Then maxZ = Z(i+1)
End If
i += 2
Wend
' Przygotowujemy liczniki
nL = maxZ - minZ + 1
Dim As Integer L(nL-1)
For i = 0 To nL - 1: L(i) = 0: Next
' Zliczamy wystąpienia wartości z Z[]

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 230 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

For i = 0 To N - 1: L(Z(i) - minZ) += 1: Next


' Szukamy najczęstszej wartości w L[]
maxL = L(0): maxp = 0
For i = 0 To nL - 1
If L(i) > maxL Then
maxL = L(i): maxp = i
End If
Next
' Wyświetlamy wyniki
Print
Print maxp + minZ;" : ";maxL
Print
End

Wynik
-9 -2 7 -1 -1 1 6 7 -2 -2 -2 7 5 8 -7 -4 7 -9 5 2
-2 6 -9 5 6 2 -9 6 3 -1 -9 3 -1 -9 -9 -9 2 8 6 -5
3 0 6 -1 -9 -3 0 -4 2 9 -9 3 -1 -7 1 5 -8 3 -9 0
7 5 2 -7 -4 -1 -7 3 -3 -8 6 -5 2 6 9 7 1 -5 0 5
-9 : 11

Rozwiązanie nr 4
Zbiór Z możemy posortować rosnąco. Wtedy elementy o równych wartościach znajdą się obok siebie. Teraz wystarczy
przeglądnąć zbiór Z od początku do końca, zliczać powtarzające się wartości i zapamiętać tę wartość, która powtarza się
najczęściej. Klasa złożoności obliczeniowej algorytmu w tej wersji zależy od zastosowanego algorytmu sortującego zbiór Z. Samo
przeglądnięcie zbioru i wyłonienie najczęstszej wartości ma liniową klasę złożoności obliczeniowej O(n).
Zaletą tego algorytmu jest to, iż elementy tablicy Z nie muszą być koniecznie liczbami całkowitymi. Mogą to być obiekty
dowolnego typu.

Algorytm znajdowania najczęstszej wartości – wersja nr 4


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica do wyszukania najczęstszego elementu Elementy są liczbami całkowitymi. Indeksy od 0 do n. Tablica
powinna być posortowana rosnąco. Na końcu tablicy – element Z[n] – powinien być umieszczony wartownik o
wartości innej niż wartość ostatniego elementu – Z[n-1].
Wyjście:
Element występujący najczęściej w Z oraz liczba jego wystąpień. Jeśli jest kilka elementów o takiej samej
maksymalnej liczbie wystąpień, to algorytm w tej wersji zwraca najmniejszy z nich.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
maxL – liczba wystąpień najczęstszego elementu w Z, maxL C
maxW – wartość najczęstszego elementu w Z
L – licznik równych elementów, L C

Lista kroków:
K02: maxL ← 0 ; inicjujemy liczbę wystąpień na 0
K03: L ← 1 ; w L zliczamy pierwszy element
K04: Dla i = 1,2,...,n wykonuj K05...K10 ; przeglądamy kolejne elementy aż do wartownika
K05: Jeśli Z[i] = Z[i - 1], to idź do K10 ; dla równych elementów zwiększamy L o 1
K06: Jeśli L ≤ maxL, to idź do K09 ; elementy nie są równe, sprawdzamy licznik L
K07: maxL ← L ; jeśli L > maxL, to zapamiętujemy L
K08: maxW ← Z[i - 1] ; oraz wartość elementu
K09: L ← 0 ; dla nowego elementu licznik L wystartuje od 1
K10: L ← L + 1 ; zliczamy elementy równe
K11: Pisz maxW, maxL ; wypisujemy wyniki
K12: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 231 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

Program umieszcza w tablicy Z 200 liczb pseudolosowych z zakresu od 0 do 9, sortuje tablicę algorytmem sortowania przez
wybór, wyświetla ją oraz wyznacza najczęstszą wartość. Program wyświetla tę wartość oraz liczbę jej wystąpień.

Lazarus

// Najczęstsza wartość
// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 200;
var
Z : array[0..N] of integer;
maxL,maxW,minZ,minp,L,i,j,x : integer;
begin
randomize;
// Inicjujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(10);
// Sortujemy tablicę Z[]
for i := 0 to N - 2 do
begin
minZ := Z[i];
minp := i;
for j := i + 1 to N - 1 do
if Z[j] < minZ then
begin
minZ := Z[j];
minp := j;
end;
x := Z[i]; Z[i] := Z[minp]; Z[minp] := x;
end;
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:2);
writeln;
// Dodajemy wartownika
Z[N] := Z[N - 1] - 1;
// Szukamy najczęstszej wartości
maxL := 0; L := 1;
for i := 1 to N do
begin
if Z[i] <> Z[i - 1] then
begin
if L > maxL then
begin
maxL := L; maxW := Z[i - 1];
end;
L := 0;
end;
inc(L);
end;
// Wyświetlamy wyniki
writeln(maxW,' : ',maxL);
writeln;
end.

Code::Blocks

// Najczęstsza wartość
// Data: 1.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 232 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

using namespace std;


const int N = 200;
int main()
{
int Z[N + 1],maxL,maxW,minZ,minp,L,i,j,x;
srand((unsigned)time(NULL));
// Inicjujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 10;
// Sortujemy tablicę Z[]
for(i = 0; i < N - 1; i++)
{
minZ = Z[i]; minp = i;
for(j = i + 1; j < N; j++)
if(Z[j] < minZ)
{
minZ = Z[j]; minp = j;
}
x = Z[i]; Z[i] = Z[minp]; Z[minp] = x;
}
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(2) << Z[i];
cout << endl;
// Dodajemy wartownika
Z[N] = Z[N - 1] - 1;
// Szukamy najczęstszej wartości
maxL = 0; L = 1;
for(i = 1; i <= N; i++)
{
if(Z[i] != Z[i - 1])
{
if(L > maxL)
{
maxL = L; maxW = Z[i - 1];
}
L = 0;
}
L++;
}
// Wyświetlamy wyniki
cout << maxW << " : " << maxL << endl << endl;
return 0;
}

Free Basic

' Najczęstsza wartość


' Data: 1.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 200
Dim As Integer Z(N),maxL,maxW,minZ,minp,L,i,j
Randomize
' Inicjujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 9): Next
' Sortujemy tablicę Z[]
For i = 0 To N - 2
minZ = Z(i): minp = i
For j = i + 1 To N - 1
If Z(j) < minZ Then
minZ = Z(j): minp = j
End If
Next
Swap Z(i),Z(minp)
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 233 / 519


Algorytmy i Struktury Danych - Wyszukiwanie najczęstszej wartości 2014-10-03

' Wyświetlamy tablicę Z[]


For i = 0 To N - 1: Print Using "##";Z(i);: Next
Print
' Dodajemy wartownika
Z(N) = Z(N - 1) - 1
' Szukamy najczęstszej wartości
maxL = 0: L = 1
For i = 1 To N
If Z(i) <> Z(i - 1) Then
If L > maxL Then
maxL = L: maxW = Z(i - 1)
End If
L = 0
End If
L += 1
Next
' Wyświetlamy wyniki
Print maxW;" : ";maxL
Print
End

Wynik
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 4
4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6
6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8
8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9
8 : 28

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0036.php 234 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie lidera
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W n-elementowym zbiorze Z znaleźć element, którego wartość występuje więcej niż n/2 razy.

Element o takich własnościach nosi nazwę lidera. Lidera można znaleźć przy pomocy jednego z opisanych w poprzednim
rozdziale algorytmów wyszukiwania najczęstszej wartości w zbiorze. Po znalezieniu takiej wartości sprawdzamy, czy liczba jej
wystąpień jest większa od liczby połowy elementów zbioru Z. Jeśli tak, to mamy lidera. Jeśli nie, to zbiór Z nie posiada lidera.
Istnieje jednakże prostszy i szybszy algorytm, który korzysta z następującego twierdzenia:

Jeśli zbiór Z posiada lidera, to usunięcie z niego pary elementów różnych daje zbiór z tym samym liderem.

Dowód tego twierdzenia jest bardzo prosty. Oznaczmy przez nL liczbę elementów będących liderami. Zbiór Z posiada n
elementów, zatem liczba pozostałych elementów wynosi n - nL. Zachodzi nierówność:
nL > n - nL

Przypadek 1
Ze zbioru Z usuwamy dwa elementy, które nie są liderami. W tym przypadku nL nie zmniejsza się, lecz zmniejsza
się o 2 liczba elementów n. Otrzymamy zatem:

nL > (n - 2) - nL
nL > (n - nL) - 2

Jeśli pierwsza nierówność była prawdziwa (a była z założenia, iż nL jest liczebnością liderów), to tym bardziej będzie
prawdziwa druga nierówność. Wynika z tego, iż usunięcie dwóch elementów nie będących liderami nie wpływa na
występowanie lidera.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 235 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

Przypadek 2
Ze zbioru Z usuwamy jeden element lidera oraz jeden element nie będący liderem. Zmniejszeniu o 1 ulega zatem
liczba liderów, a liczba elementów maleje o 2. Otrzymujemy:

nL - 1 > (n - 2) - (nL - 1)
nL - 1 > n - 2 - nL + 1
nL - 1 > (n - nL) - 1

Otrzymaliśmy nierówność wyjściową, w której od obu stron odjęto tę samą liczbę -1. Zatem nierówność jest wciąż
prawdziwa, z czego wynika, iż usunięcie ze zbioru jednego lidera i jeden element nie będący liderem nie wpływa na
występowanie lidera.

Innych przypadków nie ma, zatem dowiedliśmy prawdziwości twierdzenia.

Z powyższego twierdzenia bezpośrednio wynikają dwie dalsze własności:

Jeśli zbiór posiada lidera, to usunięcie z niego wszystkich par elementów różnych daje zbiór zawierający tylko
elementy będące liderem.
Jeśli w wyniku takiej operacji otrzymujemy jednak zbiór pusty, to lidera w zbiorze wejściowym nie było.

Zamiast faktycznie wyrzucać ze zbioru elementy różne – co jest dosyć trudne, możemy wykonać operację odwrotną. Będziemy
zliczali elementy o równych wartościach – wystarczy wtedy zapamiętać wartość elementu oraz ilość jej wystąpień. Algorytm
pracuje w sposób następujący:

Licznik elementów równych L ustawiamy na zero. Rozpoczynamy przeglądanie elementów zbioru od pierwszego do
ostatniego. Jeśli licznik elementów równych L jest równy 0, to kolejny element zbioru zapamiętujemy, zwiększamy
licznik L o 1 i wykonujemy kolejny obieg pętli dla następnego elementu. Jeśli licznik L nie jest równy zero, to
sprawdzamy, czy bieżący element jest równy zapamiętanemu. Jeśli tak, to mamy dwa elementy równe –
zwiększamy licznik L o 1. W przeciwnym razie licznik L zmniejszamy o 1 – odpowiada to wyrzuceniu ze zbioru
dwóch elementów różnych. W obu przypadkach wykonujemy kolejny obieg pętli.
Jeśli zbiór posiadał lidera, to liczba elementów równych jest większa od liczby elementów różnych. Zatem licznik L
powinien mieć zawartość większą od zera. Jeśli zawartość licznika L jest równa zero, to lidera w zbiorze nie ma. W
przeciwnym razie zapamiętany element należy przeliczyć w zbiorze – jest to kandydat na lidera. Jeśli liczba jego
wystąpień jest większa od liczby połowy elementów, to wytypowany element jest liderem. W przeciwnym razie zbiór
Z nie posiada lidera.

Algorytm posiada liniową klasę złożoności obliczeniowej O(n), jest zatem bardziej efektywny od opisanych w poprzednim rozdziale
algorytmów wyszukiwania najczęstszej wartości.

Algorytm wyszukiwania lidera w zbiorze


Wejście
n – liczba elementów w tablicy Z, n N
Z – tablica do wyszukania najczęstszego elementu Elementy są liczbami całkowitymi. Indeksy od 0
do n - 1..
Wyjście:
Element będący liderem, liczba jego wystąpień w Z lub informacja o braku lidera.
Zmienne pomocnicze
i – przebiega przez kolejne indeksy elementów Z. i C
L – licznik wystąpień wartości równych, L C
W – wartość lidera
Lista kroków:
K01: L ← 0 ; licznik wartości równych
K02: Dla i = 0,1,...,n-1 wykonuj K03...K07 ; przeglądamy kolejne elementy
K03: Jeśli L > 0, to idź do K07 ; sprawdzamy, czy licznik równy 0
K04: W ← Z[i] ; jeśli tak, zapamiętujemy bieżący element
K05: L ← 1 ; zaliczamy ten element
K06: Następny obieg pętli K02
K07: Jeśli W = Z[i], to L ← L + 1 ; elementy równe zliczamy
inaczej L ← L - 1 ; elementy różne wyrzucamy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 236 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

K08: Jeśli L = 0, to idź do K15 ; sprawdzamy, czy jest kandydat na lidera


K09: L ← 0 ; jeśli tak, to sprawdzamy, czy jest liderem
K10: Dla i = 0,1,...,n-1 wykonuj K11 ; zliczamy jego wystąpienia w zbiorze
K11: Jeśli Z[i] = W, to L ← L + 1
K12: Jeśli L ≤ [n:2], to idź do K15 ; lider?
K13 Pisz W,L ; wyprowadzamy lidera oraz liczbę jego wystąpień
K14: Zakończ
K15: Pisz "BRAK LIDERA"
K16: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w tablicy Z 80 liczb pseudolosowych z zakresu od 0 do 99 w taki sposób, aby mógł się pojawić
w niej lider. Następnie program wyszukuje lidera podanym powyżej algorytmem, wyświetla zawartość tablicy z
zaznaczonym liderem i wypisuje jego wartość oraz liczbę wystąpień. Jeśli pomimo wszystko zdarzy się, iż w tablicy
Z nie będzie lidera, program wypisze tekst BRAK LIDERA.

Lazarus

// Wyszukiwanie lidera
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 80;
var
Z : array[0..N-1] of integer;
i,L,W : integer;
t : boolean;
begin
randomize;
// Wypełniamy tablicę
W := random(100);
for i := 0 to N - 1 do
if random(2) = 1 then Z[i] := random(100)
else Z[i] := W;
// Wyszukujemy lidera
L := 0;
for i := 0 to N - 1 do
if L = 0 then
begin
W := Z[i]; L := 1;
end
else if W = Z[i] then inc(L)
else dec(L);
// Sprawdzamy, czy mamy lidera
if L = 0 then t := false
else
begin
L := 0;
for i := 0 to N - 1 do if W = Z[i] then inc(L);
t := L > N div 2;
end;
// Wyświetlamy tablicę
for i := 0 to N - 1 do
if t and (Z[i] = W) then write(' >',Z[i]:2)
else write(Z[i]:4);
// Wyświetlamy wyniki
writeln;
if t then writeln(W,' : ',L)
else writeln('BRAK LIDERA');

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 237 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

writeln
end.

Code::Blocks

// Wyszukiwanie lidera
// Data: 4.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80;
int main()
{
int Z[N],i,L,W;
bool t;
srand((unsigned)time(NULL));
// Wypełniamy tablicę
W = rand() % 100;
for(i = 0; i < N; i++)
if(rand() % 2) Z[i] = rand() % 100;
else Z[i] = W;
// Wyszukujemy lidera
L = 0;
for(i = 0; i < N; i++)
if(!L)
{
W = Z[i]; L = 1;
}
else if(W == Z[i]) L++;
else L--;
// Sprawdzamy, czy mamy lidera
if(!L) t = false;
else
{
L = 0;
for(i = 0; i < N; i++) if(W == Z[i]) L++;
t = L > N / 2;
}
// Wyświetlamy tablicę
for(i = 0; i < N; i++)
if(t && (Z[i] == W)) cout << " >" << setw(2) << Z[i];
else cout << setw(4) << Z[i];
// Wyświetlamy wyniki
cout << endl;
if(t) cout << W << " : " << L << endl;
else cout << "BRAK LIDERA\n";
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie lidera


' Data: 4.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 80
Dim As Integer Z(N-1),i,L,W,t
Randomize
' Wypełniamy tablicę
W = Cint(Rnd * 99)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 238 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

For i = 0 To N - 1
If Rnd > 0.5 Then
Z(i) = Cint(Rnd * 99)
Else
Z(i) = W
End If
Next
' Wyszukujemy lidera
L = 0
For i = 0 To N - 1
If L = 0 Then
W = Z(i): L = 1
Elseif W = Z(i) Then
L += 1
Else
L -= 1
End If
Next
' Sprawdzamy, czy mamy lidera
If L = 0 Then
t = 0
Else
L = 0
For i = 0 To N - 1
If W = Z(i) Then L += 1
Next
t = (L > N \ 2)
End If
' Wyświetlamy tablicę
For i = 0 To N - 1
If t And (Z(i) = W) Then
Print Using " >##";Z(i);
Else
Print Using "####";Z(i);
End If
Next
' Wyświetlamy wyniki
Print
If t Then
Print W;" : ";L
Else
Print "BRAK LIDERA"
End If
Print
End

Wynik
47 32 47 38 47 40 64 28 47 47 11 47 47 52 46 65 47 47 47 73
47 35 6 4 42 83 17 47 51 47 47 47 47 47 47 90 47 47 47 47
64 41 47 53 22 47 47 68 47 86 47 47 47 94 47 47 89 47 47 49
63 47 13 47 76 47 47 31 60 98 30 32 47 3 47 54 59 47 93 62
BRAK LIDERA

>37 46 80 >37 >37 >37 >37 >37 95 46 >37 17 >37 86 >37 30 >37 >37 70 33
>37 >37 77 20 24 >37 79 6 >37 65 88 >37 13 >37 >37 51 >37 6 >37 77
>37 66 99 20 >37 34 >37 94 >37 10 48 >37 >37 >37 >37 34 >37 >37 14 64
79 >37 56 >37 >37 >37 >37 96 47 10 >37 >37 >37 >37 >37 >37 68 >37 >37 >37
37 : 44

Wyszukiwanie lidera
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 239 / 519


Algorytmy i Struktury Danych - Wyszukiwanie lidera 2014-10-03

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0037.php 240 / 519


Algorytmy i Struktury Danych - Wyszukiwanie binarne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie binarne
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W posortowanym rosnąco zbiorze Z wyszukać element o wartości (kluczu) k.

Postąpimy w sposób następujący.

Wyznaczymy element środkowy zbioru. Sprawdzimy, czy jest on poszukiwanym elementem. Jeśli tak, to element
został znaleziony i możemy zakończyć poszukiwania. Jeśli nie, to poszukiwany element jest albo mniejszy od
elementu środkowego, albo większy. Ponieważ zbiór jest uporządkowany, to elementy mniejsze od środkowego
będą leżały w pierwszej połówce zbioru, a elementy większe w drugiej połówce. Zatem w następnym obiegu zbiór
możemy zredukować do pierwszej lub drugiej połówki – jest w nich o połowę mniej elementów. Mając nowy zbiór
postępujemy w sposób identyczny – znów wyznaczamy element środkowy, sprawdzamy, czy jest poszukiwanym
elementem. Jeśli nie, to zbiór dzielimy znów na dwie połowy – elementy mniejsze od środkowego i elementy większe
od środkowego. Poszukiwania kontynuujemy w odpowiedniej połówce zbioru aż znajdziemy poszukiwany element
lub do chwili, gdy po podziale połówka zbioru nie zawiera dalszych elementów.

Ponieważ w każdym obiegu pętli algorytm redukuje liczbę elementów o połowę, to jego klasa złożoności obliczeniowej jest równa
O(log n). Jest to bardzo korzystna złożoność. Na przykład w algorytmie wyszukiwania liniowego przy 1000000 elementów należało
wykonać aż 1000000 porównań, aby stwierdzić, iż elementu poszukiwanego nie ma w zbiorze. W naszym algorytmie wystarczy 20
porównań.
Oczywiście algorytm wyszukiwania liniowego może być zastosowany dla dowolnego zbioru. Nasz algorytm można stosować tylko
i wyłącznie w zbiorze uporządkowanym. Ze względu na podział zbioru na kolejne połówki, ćwierci itd. algorytm nosi nazwę
wyszukiwania binarnego (ang. binary search).
Musimy uściślić sposób podziału zbioru na coraz mniejsze fragmenty. Zbiór Z odwzorowujemy w tablicy Z składającej się z n
elementów ponumerowanych kolejno od 0 do n-1. Określmy dwa indeksy:

ip – indeks pierwszego elementu zbioru


ik – indeks końcowego elementu zbioru
indeksy elementów tablicy Z 0 1 2 ... n-2 n-1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0038.php 241 / 519


Algorytmy i Struktury Danych - Wyszukiwanie binarne 2014-10-03

indeksy początku i końca ip ik

Indeksy ip i ik określają obszar zbioru w tablicy Z. Początkowo będą oczywiście równe: ip = 0 oraz ik = n - 1.
Na podstawie tych dwóch indeksów obliczamy indeks elementu środkowego isr:

i +i
isr = p k
2

Jeśli zbiór Z jest uporządkowany rosnąco, to:

Z[0] Z[1] ... Z[isr-1] Z[isr] Z[isr+1] ... Z[n-2] Z[n-1]


ip ← isr → ik
elementy ≤ Z[isr] elementy ≥ Z[isr]

Zatem w przypadku, gdy k < Z[isr], to przechodzimy do pierwszej połówki, w której indeksy przebiegają od ip do isr - 1. Element
Z[isr] pomijamy, gdyż wiadomo, że o niego nam nie chodzi.
Jeśli k > Z[isr], to przechodzimy do drugiej połówki o indeksach od isr + 1 do ik.

Algorytm wyszukiwania binarnego


Wejście
ip – indeks pierwszego elementu w tablicy Z, ip C
ik – indeks ostatniego elementu w tablicy Z, ik C
Z – tablica do wyszukania elementu. Indeksy od ip do ik
k – wartość poszukiwanego elementu – tzw. klucz
Wyjście:
p = indeks elementu o kluczu k lub
p = -1, jeśli nie odnalezienia elementu o takim kluczu.
Zmienne pomocnicze
isr – indeks elementu środkowego. isr C

Lista kroków:
K01: p ← -1 ; zakładamy, iż k nie występuje w zbiorze
Dopóki ip ≤ ik wykonuj
K02: ; w pętli poszukujemy elementu o wartości k
K02...K10
i +i
K03: isr = p k ; wyznaczamy element środkowy
2
K04: Jeśli k ≠ Z[isr], to idź do K07
K05: p ← isr ; element znaleziony, kończymy
K06: Idź do K11
K07: Jeśli k < Z[isr], to idź do K10 ; wybieramy odpowiednią połówkę zbioru na dalsze
poszukiwania
K08: ip ← isr + 1 ; k > Z[isr] → druga połówka
K09: Następny obieg pętli K02
K10: ik ← isr - 1 ; k < Z[isr] → pierwsza połówka
K11: Zakończ z wynikiem p

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 100 elementową Z liczbami naturalnymi w taki sposób, aby powstał z nich ciąg rosnący.
Następnie generuje liczbę pseudolosową k z zakresu od Z[0] do Z[99] i wyszukuje binarnie jej położenie w tablicy.
Na koniec wyświetlana jest liczba k, jej położenie w Z oraz zawartość tablicy Z z zaznaczonym elementem o
wartości k. Jeśli k nie występuje w Z, to zamiast położenia program wyświetla słowo BRAK. Dodatkowo program

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0038.php 242 / 519


Algorytmy i Struktury Danych - Wyszukiwanie binarne 2014-10-03

zlicza wykonane obiegi pętli i wypisuje ich liczbę.

Lazarus

// Wyszukiwanie binarne
// Data: 7.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 100;
var
Z : array[0..N - 1] of integer;
i,ip,ik,isr,k,L,p : integer;
begin
randomize;
// wypełniamy tablicę Z[]
Z[0] := random(5);
for i := 1 to N - 1 do Z[i] := Z[i - 1] + random(5);
// generujemy klucz k
k := Z[0] + random(Z[N - 1] - Z[0] + 1);
// poszukujemy binarnie elementu k
L := 0; p := -1; ip := 0; ik := N - 1;
while ip <= ik do
begin
inc(L);
isr := (ip + ik) shr 1;
if Z[isr] = k then
begin
p := isr; break;
end
else if k < Z[isr] then ik := isr - 1
else ip := isr + 1;
end;
// wyświetlamy wyniki
write(k,' : ');
if p >= 0 then write(p) else write('BRAK');
writeln(' : obiegi = ',L);
writeln;
for i := 0 to N - 1 do
begin
write(Z[i]:3);
if p = i then write('<') else write(' ');
end;
writeln;
writeln;
end.

Code::Blocks

// Wyszukiwanie binarne
// Data: 7.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 100;
int main()
{
int Z[N],i,ip,ik,isr,k,L,p;
srand((unsigned)time(NULL));
// wypełniamy tablicę Z[]
Z[0] = rand() % 5;
for(i = 1; i < N; i++) Z[i] = Z[i - 1] + (rand() % 5);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0038.php 243 / 519


Algorytmy i Struktury Danych - Wyszukiwanie binarne 2014-10-03

// generujemy klucz k
k = Z[0] + (rand() % (Z[N - 1] - Z[0] + 1));
// poszukujemy binarnie elementu k
p = -1; L = ip = 0; ik = N - 1;
while(ip <= ik)
{
L++;
isr = (ip + ik) >> 1;
if(Z[isr] == k)
{
p = isr; break;
}
else if(k < Z[isr]) ik = isr - 1;
else ip = isr + 1;
}
// wyświetlamy wyniki
cout << k << " : ";
if(p >= 0) cout << p; else cout << "BRAK";
cout << " : obiegi = " << L << endl << endl;
for(i = 0; i < N; i++)
{
cout << setw(3) << Z[i];
if(p == i) cout << "<"; else cout << " ";
}
cout << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie binarne


' Data: 7.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 100
Dim As Integer Z(N-1),i,ip,ik,isr,k,L,p
Randomize
' wypełniamy tablicę Z[]
Z(0) = Cint(Rnd * 4)
For i = 1 To N - 1: Z(i) = Z(i-1) + Cint(Rnd * 4): Next
' generujemy klucz k
k = Z(0) + Cint(Rnd * (Z(N - 1) - Z(0)))
' poszukujemy binarnie elementu k
L = 0: p = -1: ip = 0: ik = N - 1
While ip <= ik
L += 1
isr = (ip + ik) Shr 1
If Z(isr) = k Then
p = isr: Exit While
Elseif k < Z(isr) Then
ik = isr - 1
Else
ip = isr + 1
End If
Wend
' wyświetlamy wyniki
Print k;" : ";
If p >= 0 Then Print p;: Else Print "BRAK";
Print " : obiegi =";L
Print
For i = 0 To N - 1
Print Using "###";Z(i);
If p = i Then Print "<";: Else Print " ";
Next
Print
Print
End

Wynik
42 : BRAK : obiegi = 7
2 6 10 14 17 17 19 22 23 26 27 27 27 29 32 35 39 43 43 47

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0038.php 244 / 519


Algorytmy i Struktury Danych - Wyszukiwanie binarne 2014-10-03

2 6 10 14 17 17 19 22 23 26 27 27 27 29 32 35 39 43 43 47
47 48 48 49 49 50 53 57 57 58 62 62 63 67 68 72 75 75 77 81
85 86 88 92 94 96 98 100 101 103 105 106 106 109 109 113 115 116 116 116
120 123 125 126 128 128 129 133 137 137 139 142 143 145 149 149 149 149 153 157
161 162 166 167 167 168 169 173 176 180 180 180 180 184 186 186 190 194 198 202
92 : 45 : obiegi = 5
4 6 7 11 14 18 22 23 27 30 33 36 37 37 40 44 47 47 47 47
49 51 53 54 58 62 62 62 62 63 67 69 69 71 71 73 73 77 79 80
83 86 90 91 91 92< 93 93 97 99 99 101 105 107 110 111 111 115 115 115
118 121 122 122 122 126 128 130 130 131 135 136 139 143 145 147 147 151 153 153
154 155 157 160 164 164 168 169 173 174 176 180 183 187 190 193 194 198 198 202

Wyszukiwanie binarne
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0038.php 245 / 519


Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie interpolacyjne
Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W posortowanym rosnąco zbiorze Z wyszukać element o wartości (kluczu) k.

Opisane w poprzednim rozdziale wyszukiwanie binarne (ang. binary search) zawsze szuka elementu o kluczu k w środku
zbioru. Tymczasem, jeśli założymy liniowy rozkład wartości elementów w przeszukiwanym zbiorze, to możemy precyzyjniej
wytypować spodziewaną pozycję poszukiwanego klucza na podstawie jego wartości. Wzór jest następujący:

Z – przeszukiwany zbiór
k – poszukiwana wartość (k - Z[ip]) x (ik - ip)
ip – indeks pierwszego elementu partycji isr = ip +
ik – indeks końcowego elementu partycji Z[ik] - Z[ip]
isr – wyznaczona, przypuszczalna pozycja

Powyższy wzór wyprowadzamy z prostej proporcji. Jeśli zbiór posiada liniowy rozkład elementów, to odległość wartości
poszukiwanej k od Z[ip] jest w przybliżeniu proporcjonalna do liczby elementów pomiędzy isr a ip:

ip ... isr ... ... ... ik

Z[ip] ... k ... ... ... Z[ik]

Skoro tak, to zachodzi przybliżona proporcja:

isr - ip k - Z[ip]
≈ , mnożymy obustronnie przez ik - ip
ik - ip Z[ik] - Z[ip]

(k - Z[ip]) x (ik - ip)


isr - ip ≈ , dodajemy obustronnie ip
Z[ik] - Z[ip]

(k - Z[ip]) x (ik - ip)


i =i +
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 246 / 519
Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03

(k - Z[ip]) x (ik - ip)


isr = ip + , zaokrąglamy do wartości całkowitej i otrzymujemy wzór końcowy.
Z[ik] - Z[ip]

Aby zrozumieć zaletę tego sposobu wyznaczania pozycji, przyjrzyjmy się poniższemu przykładowi, gdzie wyliczyliśmy pozycję
wg wzoru z poprzedniego rozdziału oraz wg wzoru podanego powyżej. Poszukiwany element zaznaczono kolorem czerwonym:

nr pozycji = 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
elementy Z = 10 11 13 13 15 16 18 19 20 22 22 23 25 27 27 28 29 30 32
binarnie isr = X
interpolacyjnie isr = X

Binarne wyznaczenie pozycji:

ip + ik 0 + 18 18
isr = = = =9
2 2 2

Interpolacyjne wyznaczenie pozycji:

(k - Z[ip]) x (ik - ip) (15 - 10) x (18 - 0) 5 x 18 90


isr = ip + =0+ =0+ =0+ =0+4=4
Z[ik] - Z[ip] 32 - 10 22 22

Metoda interpolacyjna wyznacza pozycję zwykle dużo bliżej poszukiwanego elementu niż metoda binarna (w przykładzie trafiamy
za pierwszym razem). Zauważ, iż w metodzie binarnej nie korzystamy zupełnie z wartości elementów na krańcach dzielonej
partycji, czyli działamy niejako na ślepo. Natomiast metoda interpolacyjna wyznacza położenie w zależności od wartości
elementów zbioru oraz wartości poszukiwanego elementu. Działa zatem celowo dostosowując się do zastanych w zbiorze
warunków. Stąd jej dużo większa efektywność.
Po wyznaczeniu położenia isr pozostała część algorytmu jest bardzo podobna do wyszukiwania binarnego. Sprawdzamy, czy
element na pozycji isr posiada poszukiwany klucz k. Jeśli tak, to wyszukiwanie kończy się zwróceniem pozycji isr. W przeciwnym
razie jeśli k jest mniejsze od klucza elementu Z[isr], to poszukiwania kontynuujemy w lewej części zbioru od elementu Z[ip] do
Z[isr - 1]. W przeciwnym razie szukamy w prawej części od Z[isr + 1] do Z[ik]. Wyszukiwanie kończy się porażką, jeśli
poszukiwany klucz wyjdzie poza przedział <Z[ip],Z[ik]>. W takim przypadku kończymy zwracając jako pozycję liczbę -1.
Wyszukiwanie interpolacyjne posiada klasę czasowej złożoności obliczeniowej równą O(log log n), zatem wyszukuje znacząco
szybciej w zbiorach o liniowym rozkładzie elementów niż wyszukiwanie binarne o klasie O(log n).

Algorytm wyszukiwania interpolacyjnego


Wejście
ip – indeks pierwszego elementu w tablicy Z, ip C
ik – indeks ostatniego elementu w tablicy Z, ik C
Z– tablica do wyszukania elementu. Indeksy od ip do ik
k– wartość poszukiwanego elementu – tzw. klucz
Wyjście:
p = indeks elementu o kluczu k lub
p = -1, jeśli nie odnalezienia elementu o takim kluczu.
Zmienne pomocnicze
isr – indeks elementu środkowego. isr C

Lista kroków:
K01: p ← -1 ; zakładamy, iż k nie występuje w zbiorze
Dopóki Z[ip] ≤ k ≤ Z[ik] wykonuj
K02: ; w pętli poszukujemy elementu o wartości k
K02...K10
(k - Z[ip]) x (ik - ip)
K03: isr ← ip + ; wyznaczamy pozycję elementu interpolowanego
Z[ik] - Z[ip]
K04: Jeśli k ≠ Z[isr], to idź do K07
K05: p ← isr ; element znaleziony, kończymy
K06: Idź do K11
K07: Jeśli k < Z[isr], to idź do K10 ; wybieramy odpowiednią połówkę zbioru na dalsze

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 247 / 519


Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03
< [isr], K10
poszukiwania
K08: ip ← isr + 1 ; k > Z[isr] → druga połówka
K09: Następny obieg pętli K02
K10: ik ← isr - 1 ; k < Z[isr] → pierwsza połówka
K11: Zakończ z wynikiem p

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 100 elementową Z liczbami naturalnymi w taki sposób, aby powstał z nich ciąg rosnący.
Następnie generuje liczbę pseudolosową k z zakresu od Z[0] do Z[99] i wyszukuje interpolacyjnie jej położenia w
tablicy. Na koniec wyświetlana jest liczba k, jej położenie w Z oraz zawartość tablicy Z z zaznaczonym elementem o
wartości k. Jeśli k nie występuje w Z, to zamiast położenia program wyświetla słowo BRAK. Dodatkowo program
zlicza wykonane obiegi pętli i wypisuje ich liczbę.

Lazarus

// Wyszukiwanie interpolacyjne
// Data: 8.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 100;
var
Z : array[0..N - 1] of integer;
i,ip,ik,isr,k,L,p : integer;
begin
randomize;
// wypełniamy tablicę Z[]
Z[0] := random(5);
for i := 1 to N - 1 do Z[i] := Z[i - 1] + random(5);
// generujemy klucz k
k := Z[0] + random(Z[N - 1] - Z[0] + 1);
// poszukujemy interpolacyjnie elementu k
L := 0; p := -1; ip := 0; ik := N - 1;
while (Z[ip] <= k) and (k <= Z[ik]) do
begin
inc(L);
isr := ip + (k-Z[ip])*(ik-ip) div (Z[ik]-Z[ip]);
if Z[isr] = k then
begin
p := isr; break;
end
else if k < Z[isr] then
ik := isr - 1
else
ip := isr + 1;
end;
// wyświetlamy wyniki
write(k,' : ');
if p >= 0 then write(p) else write('BRAK');
writeln(' : obiegi = ',L);
writeln;
for i := 0 to N - 1 do
begin
write(Z[i]:3);
if p = i then write('<') else write(' ');
end;
writeln;
writeln;
end.

Code::Blocks

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 248 / 519


Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03

// Wyszukiwanie interpolacyjne
// Data: 8.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 100;
int main()
{
int Z[N],i,ip,ik,isr,k,L,p;
srand((unsigned)time(NULL));
// wypełniamy tablicę Z[]
Z[0] = rand() % 5;
for(i = 1; i < N; i++) Z[i] = Z[i - 1] + (rand() % 5);
// generujemy klucz k
k = Z[0] + (rand() % (Z[N - 1] - Z[0] + 1));
// poszukujemy interpolacyjnie elementu k
p = -1; L = ip = 0; ik = N - 1;
while((Z[ip] <= k) && (k <= Z[ik]))
{
L++;
isr = ip + (k-Z[ip])*(ik-ip) / (Z[ik]-Z[ip]);
if(Z[isr] == k)
{
p = isr; break;
}
else if(k < Z[isr])
ik = isr - 1;
else
ip = isr + 1;
}
// wyświetlamy wyniki
cout << k << " : ";
if(p >= 0) cout << p; else cout << "BRAK";
cout << " : obiegi = " << L << endl << endl;
for(i = 0; i < N; i++)
{
cout << setw(3) << Z[i];
if(p == i) cout << "<"; else cout << " ";
}
cout << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie interpolacyjne


' Data: 8.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 100
Dim As Integer Z(N-1),i,ip,ik,isr,k,L,p
Randomize
' wypełniamy tablicę Z[]
Z(0) = Cint(Rnd * 4)
For i = 1 To N - 1: Z(i) = Z(i-1) + Cint(Rnd * 4): Next
' generujemy klucz k
k = Z(0) + Cint(Rnd * (Z(N - 1) - Z(0)))
' poszukujemy interpolacyjnie elementu k
L = 0: p = -1: ip = 0: ik = N - 1
While (Z(ip) <= k) And (k <= Z(ik))

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 249 / 519


Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03

L += 1
isr = ip + (k - Z(ip)) * (ik - ip) \ (Z(ik) - Z(ip))
If Z(isr) = k Then
p = isr: Exit While
Elseif k < Z(isr) Then
ik = isr - 1
Else
ip = isr + 1
End If
Wend
' wyświetlamy wyniki
Print k;" : ";
If p >= 0 Then Print p;: Else Print "BRAK";
Print " : obiegi =";L
Print
For i = 0 To N - 1
Print Using "###";Z(i);
If p = i Then Print "<";: Else Print " ";
Next
Print
Print
End

Wynik
151 : BRAK : obiegi = 1
4 5 9 12 15 15 15 19 21 24 24 24 25 28 32 33 33 35 39 40
41 43 44 45 47 47 51 54 56 57 58 59 62 64 66 68 69 71 71 72
72 74 77 81 84 87 91 91 95 95 95 99 99 101 103 104 108 111 114 116
117 120 120 122 125 129 130 133 136 139 140 141 145 149 153 155 157 161 164 166
168 170 170 170 171 174 176 177 181 183 183 186 188 191 191 195 195 199 199 201
83 : 44 : obiegi = 2
3 4 6 8 10 10 13 14 14 14 18 18 20 24 24 24 24 25 25 25
26 28 31 33 34 36 36 38 42 42 43 47 51 52 54 55 59 63 63 67
71 75 76 80 83< 86 86 87 90 92 96 98 101 103 103 107 108 111 113 116
119 122 125 128 128 128 128 131 135 139 139 142 142 143 145 149 150 150 152 153
156 160 161 163 164 164 165 168 169 169 169 170 171 172 174 176 176 179 180 184

Wyszukiwanie interpolacyjne
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 250 / 519


Algorytmy i Struktury Danych - Wyszukiwanie interpolacyjne 2014-10-03

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0039.php 251 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie k-tego największego elementu


Tematy pokrewne Podrozdziały
Tablice – wektory Rozwiązanie 1
Podstawowe operacje na tablicach Rozwiązanie 2
Wyszukiwanie liniowe Rozwiązanie 3
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W zbiorze Z wyszukać element, od którego w tym zbiorze jest dokładnie k - 1 elementów większych.

Rozwiązanie nr 1
Problem wyszukiwania k-tego największego elementu (ang. the k-th largest/greatest element search) można rozwiązać na
wiele różnych sposobów. Na przykład tak:

Zbiór Z sortujemy rosnąco. Wtedy elementy ułożą się w kolejności od najmniejszego do największego. Wystarczy
zatem zwrócić wartość k-tego od końca elementu.

Ponieważ znajdowanie k-tego największego elementu w zbiorze posortowanym posiada stałą klasę złożoności obliczeniowej O(1),
t o faktyczna klasa złożoności całego rozwiązania zależy od zastosowanego algorytmu sortującego zbiór Z. Sortowanie zbioru
zmienia wzajemne położenie elementów. Zatem można je stosować tylko wtedy, gdy nie musimy zachowywać oryginalnej
struktury zbioru. Jeśli zbiór jest niewielki, to sortowanie może być wykonane na jego duplikacie.

Algorytm wyszukiwania k-tego największego elementu – wersja nr 1


Wejście
n – liczba elementów w tablicy Z, n > 0, n N
Z – tablica do wyszukania k-tego największego elementu. Indeksy od 0 do n - 1
k – określa k-ty największy element tablicy Z, k > 0, k ≤ n, k N

Wyjście:
Wartość k-tego największego elementu w Z.
Lista kroków:
K01: Posortuj rosnąco tablicę Z ; ustalamy kolejność elementów
K02: Zakończ z wynikiem Z[n - k] ; zwracamy k-ty największy element

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 252 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 40 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999. Następnie losuje k z
zakresu od 1 do 10. Wypisuje zawartość tablicy nieposortowanej, sortuje tablicę Z algorytmem sortowania przez
wybór (może być inny, ale ten typ sortowania opisaliśmy w tym artykule), wypisuje zawartość tablicy posortowanej,
k oraz k-ty największy element w tablicy.

Lazarus

// Wyszukiwanie k-tego największego elementu


// Data: 9.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
program prg;
const N = 40;
var Z : array[0..N-1] of integer;
i,j,k,x,minZ,minp : integer;
begin
randomize;
// Inicjujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Losujemy k
k := random(10) + 1;

// Wyświetlamy zawartość Z[]


for i := 0 to N - 1 do write(Z[i]:4);
writeln;
writeln;
// Sortujemy Z[]
for i := 0 to N - 2 do
begin
minZ := Z[i];
minp := i;
for j := i + 1 to N - 1 do
if Z[j] < minZ then
begin
minZ := Z[j]; minp := j;
end;
x := Z[i]; Z[i] := Z[minp]; Z[minp] := x;
end;
// Wyświetlamy zawartość Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
writeln;
// Wyświetlamy k oraz Z[N-k]
writeln(k,' : ',Z[N - k]);
writeln;
end.

Code::Blocks

// Wyszukiwanie k-tego największego elementu


// Data: 9.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 253 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

#include <time.h>
using namespace std;
const int N = 40;
int main()
{
int Z[N],i,j,k,x,minZ,minp;
srand((unsigned)time(NULL));
// Inicjujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Losujemy k
k = (rand() % 10) + 1;
// Wyświetlamy zawartość Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Sortujemy Z[]
for(i = 0; i < N - 1; i++)
{
minZ = Z[i];
minp = i;
for(j = i + 1; j < N; j++)
if(Z[j] < minZ)
{
minZ = Z[j]; minp = j;
}
x = Z[i]; Z[i] = Z[minp]; Z[minp] = x;
}
// Wyświetlamy zawartość Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Wyświetlamy k oraz Z[N-k]
cout << k << " : " << Z[N - k] << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie k-tego największego elementu


' Data: 9.05.2008
' (C)2012 mgr Jerzy Wałaszek
'------------------------------------------
Const N = 40
Dim As Integer Z(N-1),i,j,k,minZ,minp
Randomize
' Inicjujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Losujemy k
k = Cint(Rnd * 9) + 1

' Wyświetlamy zawartość Z[]


For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
Print
' Sortujemy Z[]
For i = 0 To N - 2
minZ = Z(i)
minp = i
For j = i + 1 To N - 1
If Z(j) < minZ Then
minZ = Z(j): minp = j
End If
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 254 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

Swap Z(i),Z(minp)
Next
' Wyświetlamy zawartość Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
Print
' Wyświetlamy k oraz Z[N-k]
Print k;" : ";Z(N - k)
Print
End

Wynik
249 47 995 892 293 567 801 212 308 539 805 516 568 204 316 8 81 347 372 345
758 699 45 855 533 206 274 703 645 95 162 473 406 330 271 177 618 526 247 414
8 45 47 81 95 162 177 204 206 212 247 249 271 274 293 308 316 330 345 347
372 406 414 473 516 526 533 539 567 568 618 645 699 703 758 801 805 855 892 995

7 : 703

Wyszukiwanie k-tego największego elementu


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Sortowanie zbioru jest kosztowne czasowo i zmienia położenie elementów w zbiorze, które czasami musimy zachować. W takim
przypadku można zastosować inny algorytm wyszukiwania k-tego największego elementu:

Przygotowujemy pusty zbiór M, w którym będziemy składowali k największych elementów. Przeglądamy zbiór Z i
kolejne elementy próbujemy wstawić do M. Element zbioru Z trafia do M wtedy, gdy zbiór M nie jest jeszcze
zapełniony lub gdy element zbioru Z jest większy od pierwszego elementu zbioru M. Elementy wstawiamy tak, aby
zbiór M był uporządkowany nierosnąco. Gdy zakończymy przeglądanie zbioru Z w pierwszym elemencie zbioru M
będzie k-ty największy element zbioru Z.

Wyjaśnienia wymaga sposób działania na zbiorze M. Odwzorujemy go w tablicy (k + 1) elementowej. Do wstawiania elementów
wykorzystamy ideę wartowników, którą opisaliśmy w rozdziale o wyszukiwaniu liniowym z wartownikiem. Tablicę M wypełnimy od
M[0] do M[k-1] najmniejszą liczbą całkowitą – zagwarantuje to wstawianie najmniejszych elementu zbioru Z, jeśli w M jest jeszcze
miejsce. Na ostatniej pozycji M[k] umieścimy największą liczbę całkowitą – zagwarantuje nam ona, iż wstawiane elementy nie
wyjdą poza k-1 pozycję.

Algorytm wyszukiwania k-tego największego elementu – wersja nr 2


Wejście
n – liczba elementów w tablicy Z, n > 0, n N
Z – n-elementowa tablica, w której poszukujemy k-tej największej wartości
k – określa numer największej wartości, której szukamy w Z, k > 0, k ≤ n, k N
Wyjście:
k-ta największa wartość elementu w tablicy Z. Dodatkowo M[0]...M[k- 1 ] tworzy ciąg wartości
maksymalnych z tablicy Z
Elementy pomocnicze:
M – k + 1 elementowa tablica do przechowywania wartości maksymalnych z Z
i,j – indeksy w tablicach Z i M, i,j C

Lista kroków:
K01: Dla i = 0,1,...,k - 1, wykonuj K02 ; inicjujemy tablicę M
K02: M[i] ← najmniejsza liczba całkowita
K03: M[k] ← największa liczba całkowita
K04: Dla i = 0,1,...,n-1 wykonuj K05...K10 ; przeglądamy kolejne elementy Z

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 255 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

K05: x ← Z[i] ; zapamiętujemy i-ty element Z


K06: j ← -1
K07: Dopóki x > M[j+1] wykonuj K08...K09 ; szukamy miejsca dla x w M
K08: j ←j + 1 ; przesuwamy się na następną pozycję w M
K09: M[j] ← M[j+1] ; przesuwamy elementy, aby zrobić miejsce dla x
K10: Jeśli j ≥ 0, to M[j] ← x ; wstawiamy element x do M
K11: Zakończ z wynikiem M[0] ; w M[0] jest k-ty największy element

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 40 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999. Następnie losuje k z
zakresu od 5 do 10. Wypisuje zawartość tablicy Z, następnie wyszukuje k-ty największy element opisanym powyżej
algorytmem i wypisuje k oraz całą zawartość tablicy M od elementu M[0] do M[k-1].

Lazarus

// Wyszukiwanie k-tego największego elementu


// Data: 14.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
program prg;
const N = 40;
var Z : array[0..N-1] of integer;
M : array[0..10] of integer;
i,j,k,x : integer;
begin
randomize;
// Inicjujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Losujemy k
k := random(6) + 5;
// Ustawiamy tablicę M
for i := 0 to k - 1 do M[i] := -1;
M[k] := 1000;
// Szukamy k-tego największego elementu
for i := 0 to N - 1 do
begin
x := Z[i];
j := -1;
while x > M[j+1] do
begin
inc(j); M[j] := M[j+1];
end;
if j >= 0 then M[j] := x;
end;
// Wypisujemy zawartość tablicy Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
// Wypisujemy zawartość tablicy M
write('k = ',k,' : ');
for i := 0 to k - 1 do write(M[i]:4);
writeln;
writeln;
end.

Code::Blocks

// Wyszukiwanie k-tego największego elementu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 256 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

// Data: 14.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 40;
int main()
{
int Z[N],M[11],i,j,k,x;
srand((unsigned)time(NULL));
// Inicjujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Losujemy k
k = 5 + (rand() % 6);
// Ustawiamy tablicę M
for(i = 0; i < k; i++) M[i] = -1;
M[k] = 1000;
// Szukamy k-tego największego elementu
for(i = 0; i < N; i++)
{
x = Z[i];
for(j = -1; x > M[j+1];)
{
j++; M[j] = M[j+1];
}
if(j >= 0) M[j] = x;
}
// Wypisujemy zawartość tablicy Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
// Wypisujemy zawartość tablicy M
cout << "k = " << k << " : ";
for(i = 0; i < k; i++) cout << setw(4) << M[i];
cout << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie k-tego największego elementu


' Data: 14.05.2008
' (C)2012 mgr Jerzy Wałaszek
'------------------------------------------
Const N = 40
Dim As Integer Z(N-1),M(10),i,j,k,x
Randomize
' Inicjujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Losujemy k
k = Cint(Rnd * 5) + 5
' Ustawiamy tablicę M
For i = 0 To k - 1: M(i) = -1: Next
M(k) = 1000
' Szukamy k-tego największego elementu
For i = 0 To N - 1
x = Z(i)
j = -1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 257 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

While x > M(j + 1)


j += 1: M(j) = M(j + 1)
Wend
If j >= 0 Then M(j) = x
Next
' Wypisujemy zawartość tablicy Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
' Wypisujemy zawartość tablicy M
Print "k = ";k;" : ";
For i = 0 To k - 1: Print Using "####";M(i);: Next
Print
Print
End

Wynik
779 147 424 546 363 390 95 226 179 440 576 347 15 271 860 362 870 290 476 453
594 382 782 189 369 965 622 44 579 350 968 622 775 924 250 982 7 667 733 149
k = 10 : 733 775 779 782 860 870 924 965 968 982

484 665 40 452 26 296 300 379 206 173 215 696 61 34 120 255 460 332 961 740
123 349 604 435 859 299 189 813 471 593 335 899 976 791 190 209 529 954 437 750
k = 7 : 791 813 859 899 954 961 976

Rozwiązanie nr 3
Jeśli zbiór Z jest bardzo duży lecz wartości jego elementów tworzą względnie mały przedział liczb całkowitych, to do wyznaczenia
k-tego największego elementu można wykorzystać następujące rozwiązanie:

Tworzymy tablicę L posiadającą tyle elementów, ile liczb całkowitych zawiera przedział <minZ,maxZ> (wartość
minimalna i maksymalna elementów zbioru Z). Elementy L zerujemy – będą one pełniły rolę liczników elementów
zbioru Z. Teraz przeglądamy zbiór Z od pierwszego do ostatniego elementu i odpowiednio zliczamy napotkane
elementy Z w licznikach L. Na koniec przeglądamy liczniki L od ostatniego (odpowiada maxZ) do pierwszego
(odpowiada minZ). Ponieważ licznik L[i] zawiera informację o tym, ile razy wartość i występuje w Z, to przy każdym
kolejnym liczniku odejmujemy jego stan od k. Jeśli k wyzeruje się lub przyjmie wartość ujemną, to poszukiwana
wartość jest równa indeksowi licznika, który spowodował tę sytuację.

Klasa złożoności obliczeniowej tak zdefiniowanego algorytmu wynosi O(n + m), gdzie n oznacza liczbę elementów w zbiorze Z, a
m liczbę ich możliwych wartości. Dodatkowo algorytm wymaga O(m) komórek pamięci na przechowanie liczników. Musimy
również znać zakres wartości elementów zbioru Z – można tutaj wykorzystać algorytm jednoczesnego wyszukiwania min i max.

Algorytm wyszukiwania k-tego największego elementu – wersja nr 3


Wejście
n – liczba elementów w tablicy Z, n > 0, n N
Z – n-elementowa tablica, w której poszukujemy k-tej największej wartości
k – określa numer największej wartości, której szukamy w Z, k > 0, k ≤ n, k N
minZ – minimalna wartość w Z
maxZ – maksymalna wartość w Z

Wyjście:
k-ta największa wartość elementu w tablicy Z.
Elementy pomocnicze:
L – tablica liczników. Elementy są liczbami całkowitymi.
i – indeksy w tablicach Z i L, i C
m – liczba wartości elementów Z, m N

Lista kroków:

K01: m ← maxZ - minZ + 1 ; przygotowujemy tablicę liczników


K02: Twórz L o m elementach
K03: Dla i = 0,1,...,m-1 wykonuj K04
K04: L[i] ← 0 ; zerujemy liczniki wartości
K05: Dla i = 0,1,...,n-1 wykonuj K06 ; zliczamy wartości elementów Z
K06: Zwiększ o 1 L[Z[i] - minZ]

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 258 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

K07: i ← m - 1 ; szukamy k-tego największego elementu


K08: Dopóki k > 0 wykonuj K09...K10
K09: k ← k - L[i] ; od k odejmujemy liczbę elementów o wartości i
K10: i ← i - 1 ; przechodzimy do niższej wartości
K11: Zakończ z wynikiem i + 1 + minZ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 400 elementową Z liczbami pseudolosowymi z zakresu od -99 do 99. Następnie losuje k z
zakresu od 1 do 100. Wypisuje zawartość tablicy Z, znajduje min i max, wyszukuje k-ty największy element
opisanym powyżej algorytmem i wypisuje k oraz znaleziony element.

Lazarus

// Wyszukiwanie k-tego największego elementu


// Data: 16.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
program prg;
const N = 400;
type Tint = array of integer;
var
Z : array[0..N-1] of integer;
L : Tint;
i,j,k,kt,m,minZ,maxZ : integer;
begin
randomize;
// Generujemy zbiór Z[]
for i := 0 to N - 1 do Z[i] := -99 + random(199);
// Szukamy jednocześnie min i max
minZ := 100; maxZ := -100;
i := 0;
while i < N do
begin
if Z[i] > Z[i+1] then
begin
if Z[i] > maxZ then maxZ := Z[i];
if Z[i+1] < minZ then minZ := Z[i+1];
end
else
begin
if Z[i] < minZ then minZ := Z[i];
if Z[i+1] > maxZ then maxZ := Z[i+1];
end;
inc(i,2);
end;
// Przygotowujemy liczniki
m := maxZ - minZ + 1;
setlength(L,m);
for i := 0 to m - 1 do L[i] := 0;
// Zliczamy wartości zbioru Z[]
for i := 0 to N - 1 do inc(L[Z[i] - minZ]);
// Losujemy k
k := 1 + random(100);
// Szukamy k-tego największego elementu
j := m - 1; kt := k;
while kt > 0 do
begin
dec(kt,L[j]); dec(j);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 259 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

end;
// Wypisujemy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
// Wypisujemy k oraz k-ty największy element
writeln('k = ',k,' : max k-ty = ',j + 1 + minZ);
writeln;
end.

Code::Blocks

// Wyszukiwanie k-tego największego elementu


// Data: 16.05.2008
// (C)2012 mgr Jerzy Wałaszek
//------------------------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 400;
int main()
{
int Z[N], * L,i,j,k,kt,m,minZ,maxZ;
srand((unsigned)time(NULL));
// Generujemy zbiór Z[]
for(i = 0; i < N; i++) Z[i] = -99 + (rand() % 199);
// Szukamy jednocześnie min i max
minZ = 100; maxZ = -100;
for(i = 0; i < N; i += 2)
if(Z[i] > Z[i+1])
{
if(Z[i] > maxZ) maxZ = Z[i];
if(Z[i+1] < minZ) minZ = Z[i+1];
}
else
{
if(Z[i] < minZ) minZ = Z[i];
if(Z[i+1] > maxZ) maxZ = Z[i+1];
}
// Przygotowujemy liczniki
m = maxZ - minZ + 1;
L = new int[m];
for(i = 0; i < m; i++) L[i] = 0;
// Zliczamy wartości zbioru Z[]
for(i = 0; i < N; i++) L[Z[i] - minZ]++;
// Losujemy k
k = 1 + (rand() % 100);
// Szukamy k-tego największego elementu
for(j = m - 1, kt = k; kt > 0; j--) kt -= L[j];
// Wypisujemy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
// Wypisujemy k oraz k-ty największy element
cout << "k = " << k
<< " : max k-ty = " << (j + 1 + minZ)
<< endl << endl;
delete [] L;
return 0;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 260 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

Free Basic

' Wyszukiwanie k-tego największego elementu


' Data: 16.05.2008
' (C)2012 mgr Jerzy Wałaszek
'------------------------------------------
Const N = 400
Dim As Integer Z(N - 1),i,j,k,kt,m,minZ,maxZ
Randomize
' Generujemy zbiór Z[]
For i = 0 To N - 1: Z(i) = -99 + Cint(Rnd * 198): Next
' Szukamy jednocześnie min i max
minZ = 100: maxZ = -100
i = 0
While i < N
If Z(i) > Z(i+1) Then
If Z(i) > maxZ Then maxZ = Z(i)
If Z(i+1) < minZ Then minZ = Z(i+1)
Else
If Z(i) < minZ Then minZ = Z(i)
If Z(i+1) > maxZ Then maxZ = Z(i+1)
End If
i += 2
Wend
' Przygotowujemy liczniki
m = maxZ - minZ + 1
Dim As Integer L(m - 1)
For i = 0 To m - 1: L(i) = 0: Next
' Zliczamy wartości zbioru Z[]
For i = 0 To N - 1: L(Z(i) - minZ) += 1: Next
' Losujemy k
k = 1 + Cint(Rnd * 99)
' Szukamy k-tego największego elementu
j = m - 1: kt = k
While kt > 0
kt -= L(j): j -= 1
Wend
' Wypisujemy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
' Wypisujemy k oraz k-ty największy element
Print "k =";k;" : max k-ty =";j + 1 + minZ
Print
End

Wynik
75 76 12 -93 -34 -80 -88 12 36 -80 92 84 -93 -46 -2 -51 44 -29 52 -77
83 11 -97 93 12 -42 -2 66 71 38 -33 80 -40 25 44 -15 -33 39 -95 -72
-51 87 -74 88 -8 -85 -98 10 42 36 -82 6 -12 73 -46 40 94 5 84 37
-48 35 61 84 -16 -30 47 -91 -38 -65 53 26 -13 -72 -73 77 -4 -96 -14 54
74 -96 -14 -29 -54 93 58 -8 -65 -26 30 27 -53 -2 67 -8 10 0 -88 -96
18 -48 49 -72 54 -39 -41 44 -99 37 83 32 -57 -60 41 -97 55 23 -13 0
-52 68 -5 20 80 26 -62 -40 53 -56 2 57 -88 -22 -61 50 -73 6 69 62
-88 47 -52 -97 -13 57 -88 -72 90 92 -17 95 2 -64 -52 -59 -21 -52 -5 74
51 -63 -9 -82 -22 -45 65 -4 -94 52 82 77 -86 86 -16 63 -4 47 24 -9
59 48 -94 10 -79 3 -92 -39 7 -72 85 -46 1 -2 -39 -81 -44 -77 -38 -99
-15 88 -34 70 -79 86 31 -11 -29 -57 62 46 -54 -94 -6 -91 -24 83 8 -67
39 48 -77 3 -27 -14 -72 -86 0 -39 -50 -53 -85 87 88 20 46 -27 -69 55
-84 -24 -9 22 -52 92 53 33 -92 -28 0 87 -52 74 76 -26 68 67 -98 -71
49 58 90 30 -84 -42 -41 28 41 -60 89 -4 -89 -55 -59 6 -95 -59 55 42
59 1 54 -55 41 4 -30 40 57 96 -42 -71 9 83 -74 -45 49 59 23 12
-3 48 -50 -73 -36 -54 -9 -6 25 2 42 -12 52 13 60 -40 -31 -63 87 -8
64 -13 -38 -82 -9 45 75 -88 54 -45 39 -14 95 30 11 6 86 -10 36 15
39 93 9 -86 -44 -70 14 -30 -51 70 47 3 -41 -27 9 -89 -48 35 14 13
56 2 17 16 -20 -15 62 29 25 54 86 36 30 29 -95 10 23 41 36 -46
-73 39 32 68 -66 -84 69 80 7 -45 -68 25 29 99 49 52 -91 -38 -88 -58
k = 32 : max k-ty = 83

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 261 / 519


Algorytmy i Struktury Danych - Wyszukiwanie k-tego największego elementu 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0040.php 262 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szybkie wyszukiwanie k-tego największego elementu


Tematy pokrewne Podrozdziały
Tablice – wektory Podział zbioru na dwie partycje
Podstawowe operacje na tablicach Wyszukiwanie szybkie
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
W zbiorze Z wyszukać element, od którego w tym zbiorze jest dokładnie k - 1 elementów większych.

Jeśli nie musimy zachowywać oryginalnej kolejności elementów w zbiorze Z, to istnieje szybki algorytm znajdowania k-tego
największego elementu, który posiada oczekiwaną klasę złożoności obliczeniowej równą O(n log n) (liniowo logarytmiczna).
Algorytm ten nosi nazwę Szybkiego Wyszukiwania (ang. Quick Select) i został opracowany przez profesora Tony Hoare'a,
twórcę jednego z najszybszych algorytmów sortujących – Sortowania Szybkiego (ang. Quick Sort).
Działanie algorytmu Szybkiego Wyszukiwania oparte jest na zasadzie Dziel i Zwyciężaj (ang. Divide and Conquer). Polega ona
na rekurencyjnym podziale pierwotnego problemu na problemy prostsze tego samego typu. Podział wykonywany jest dotąd, aż
rozwiązanie stanie się oczywiste. Następnie z rozwiązań podproblemów tworzymy rozwiązania na wyższych poziomach aż
dojdziemy do rozwiązania problemu pierwotnego
W przypadku Szybkiego Wyszukiwania postępujemy w sposób następujący:

W zbiorze Z wybieramy dowolny element. Oznaczmy go przez v i nazwijmy elementem zwrotnym (ang. pivot).
Następnie dokonujemy podziału zbioru Z na dwa podzbiory ZL i ZP (lewy i prawy). W podzbiorze ZP powinny znaleźć
się elementy o wartościach nie większych od v. Z kolei w podzbiorze ZP powinny być elementy o wartościach nie
mniejszych od v. Sam element v musi być pierwszym elementem podzbioru ZP. Po takim podziale sprawdzamy, czy
v jest (n - k)-tym elementem zbioru Z. Jeśli tak, to v jest k-tym największym elementem w tym zbiorze. Jeśli nie, to
za nowy zbiór do podziału przyjmujemy ten z podzbiorów ZL lub ZP, w którym występuje pozycja (n - k)-ta i całą
procedurę powtarzamy aż do znalezienia k-tego największego elementu.

Podział zbioru na dwie partycje


Podstawowym problemem w algorytmie Szybkiego Wyszukiwania jest podział zbioru na dwa podzbiory, partycje, o wymaganych
własnościach. Ponieważ zbiór Z będziemy odwzorowywali w tablicy n-elementowej Z, to zdefiniujmy dwie zmienne, które będą
przechowywały indeksy pierwszego i końcowego elementu podzbioru:

ip – przechowuje indeks pierwszego elementu podzbioru – początek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 263 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

ik – przechowuje indeks ostatniego elementu podzbioru – koniec

Początkowo podzbiór obejmuje cały zbiór Z, zatem zmienne te przyjmują odpowiednio wartości:

ip = 0
ik = n - 1, gdzie n jest liczbą elementów tablicy Z

Odwzorowanie zbioru Z w tablicy Z


Z[0] Z[1] Z[2] Z[3] ... ... ... ... Z[n-3] Z[n-2] Z[n-1]
ip ik

Element zwrotny można wybierać na różne sposoby.

1. Jako pierwszy element partycji, v ← Z[ip]


2. Jako ostatni element partycji, v ← Z[ik]
3. Jako element środkowy partycji, v ← Z[(ip + ik) shr 1]
4. Jako element o losowym indeksie, v ← Z[ip + losowe(ik - ip + 1)]

Poniżej podajemy algorytm partycjonowania zbioru wg pierwszego elementu partycji głównej. Jeśli zechcemy partycjonować wg
innego elementu zwrotnego, to po prostu wymieniamy wybrany element zwrotny z pierwszym elementem partycji i dalej
wykorzystujemy podany poniżej algorytm.

Algorytm partycjonowania zbioru wg pierwszego elementu


Wejście
Z tablica, której podzbiór partycjonujemy. Za ostatnim elementem partycji należy umieścić

wartownika o wartości większej od każdego elementu partycji.
ip – indeks pierwszego elementu partycji, ip C
ik – indeks końcowego elementu partycji, ik C

Wyjście:
j – pozycja elementu zwrotnego w Z. Element ten dzieli partycję wejściową na dwie partycje:

{ Z[ip] ... Z[j - 1] } – elementy mniejsze lub równe v – podzbiór ZL


{ Z[j] ... Z[ik] } – elementy większe lub równe v – podzbiór ZP

Elementy pomocnicze:
v – wartość elementu zwrotnego
i,j – indeksy w tablicy Z, i,j C

Lista kroków funkcji Dziel_na_partycje(Z,ip,ik):

K01: v ← Z[ip] ; zapamiętujemy wartość elementu zwrotnego


K02: i ← ip ; indeksem i będziemy szukali elementów ≥ v
K03: j ← ik + 1 ; indeksem j będziemy szukali elementów ≤ v
Dopóki i < j, wykonuj ; w pętli elementy większe umieszczamy w ZP, a mniejsze w ZL
K04: K05...K09
K05: i ← i + 1 ; przesuwamy się na następną pozycję w ZL
K06: Jeśli Z[i] < v, to idź do K05 ; szukamy elementu, który nie należy do ZL
K07: j ← j - 1 ; przesuwamy się na poprzednią pozycję w ZP
K08: Jeśli Z[j] > v, to idź do K07 ; szukamy elementu, który nie należy do ZP
K09: Jeśli i < j, to Z[i] ↔ Z[j] ; znalezione elementy zamieniamy miejscami
K10: Z[ip] ← Z[j] ; zwalniamy pozycję elementu zwrotnego
K11: Z[j] ← v ; na zwolnionej pozycji umieszczamy element zwrotny
K12: Zakończ z wynikiem j ; kończymy zwracając pozycję elementu zwrotnego

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 264 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

Program wypełnia tablicę 20 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999. Następnie dokonuje
partycjonowania tablicy wg pierwszego elementu. Wyświetla zawartość tablicy Z z zaznaczeniem punktu
podziałowego.

Lazarus

// Podział zbioru na dwie partycje


// Data: 17.05.2008
// (C)2012 mgr Jerzy Wałaszek
//--------------------------------
program prg;
const N = 20;
var
Z : array[0..N] of integer;
i,j,v,x : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Na końcu Z[] umieszczamy wartownika
Z[N] := 1000;
// Wyświetlamy Z[] przed podziałem
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
// Dzielimy Z[] na dwie partycje
v := Z[0]; i := 0; j := N;
while i < j do
begin
repeat inc(i); until Z[i] >= v;
repeat dec(j); until Z[j] <= v;
if i < j then
begin
x := Z[i]; Z[i] := Z[j]; Z[j] := x;
end;
end;
Z[0] := Z[j]; Z[j] := v;
// Wyświetlamy Z[] po podziale
for i := 0 to N - 1 do
if i = j then write('|---') else write('----');
for i := 0 to N - 1 do
if i = j then write('|',Z[i]:3) else write(Z[i]:4);
for i := 0 to N - 1 do
if i = j then write('|---') else write('----');
writeln;
writeln;
end.

Code::Blocks

// Podział zbioru na dwie partycje


// Data: 17.05.2008
// (C)2012 mgr Jerzy Wałaszek
//--------------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 20;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 265 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

int main()
{
int Z[N + 1],i,j,v,x;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Na końcu Z[] umieszczamy wartownika
Z[N] = 1000;
// Wyświetlamy Z[] przed podziałem
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
// Dzielimy Z[] na dwie partycje
v = Z[0]; i = 0; j = N;
while(i < j)
{
while(Z[++i] < v) ;
while(Z[--j] > v) ;
if(i < j)
{
x = Z[i]; Z[i] = Z[j]; Z[j] = x;
}
}
Z[0] = Z[j]; Z[j] = v;
// Wyświetlamy Z[] po podziale
for(i = 0; i < N; i++)
cout << (i == j ? "|---" : "----");
for(i = 0; i < N; i++)
if(i == j) cout << "|" << setw(3) << Z[i];
else cout << setw(4) << Z[i];
for(i = 0; i < N; i++)
cout << (i == j ? "|---" : "----");
cout << endl << endl;
return 0;
}

Free Basic

' Podział zbioru na dwie partycje


' Data: 17.05.2008
' (C)2012 mgr Jerzy Wałaszek
'--------------------------------
Const N = 20
Dim As Integer Z(N),i,j,v
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Na końcu Z[] umieszczamy wartownika
Z(N) = 1000
' Wyświetlamy Z[] przed podziałem
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
' Dzielimy Z[] na dwie partycje
v = Z(0): i = 0: j = N
While i < j
Do: i += 1: Loop Until Z(i) >= v
Do: j -= 1: Loop Until Z(j) <= v
If i < j Then Swap Z(i),Z(j)
Wend
Z(0) = Z(j): Z(j) = v
' Wyświetlamy Z[] po podziale

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 266 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

For i = 0 To N - 1
If i = j Then Print "|---";: Else Print "----";
Next
For i = 0 To N - 1
If i = j Then Print Using "|###";Z(i); Else Print Using "####";Z(i);
Next
For i = 0 To N - 1
If i = j Then Print "|---";: Else Print "----";
Next
Print
Print
End

Wynik
382 983 4 321 701 483 706 214 816 904 310 366 408 372 896 583 808 308 774 212
--------------------------------|-----------------------------------------------
310 212 4 321 308 372 366 214|382 904 816 706 408 483 896 583 808 701 774 983
--------------------------------|-----------------------------------------------

Podział zbioru Z na dwie partycje wg pierwszego elementu


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Wyszukiwanie szybkie
Algorytm szybkiego wyszukiwania k-tego największego elementu
Wejście
n – liczba elementów w zbiorze Z
Z tablica (n+1)-elementowa odwzorowująca zbiór Z, w którym poszukujemy k-tego największego
– elementu. Na pozycji Z[n] należy umieścić wartownika o wartości większej od każdego elementu
zbioru.
k – określa numer porządkowy największego elementu do znalezienia w Z, k > 0, k N

Wyjście:
Wartość k-tego największego elementu zbioru Z.
Elementy pomocnicze:
ip – indeks początku partycji, ip C
ik – indeks końca partycji, ik C
pv – zawiera pozycję elementu zwrotnego
Dziel_na_partycje(Z,ip,ik) – funkcja dzieląca na dwie partycje wg elementu zwrotnego. Funkcja opisana
powyżej.
Lista kroków:

K01: ip ← 0 ; startowa partycja obejmuje cały zbiór Z


K02: ik ← n - 1
K03: pv ← Dziel_na_partycje(Z,ip,ik) ; dokonujemy podziału na dwie partycje ZL i ZP
Jeśli pv = n - k, to zakończ z wynikiem
K04: ; sprawdzamy, czy znaleźliśmy poszukiwany element
Z[pv]
K05: Jeśli n - k < pv, idź do K08 ; jeśli nie, to w zależności od pozycji elementu
zwrotnego
K06: ip ← pv + 1 ; elementu będziemy szukać w ZP
K07: Idź do K03 ; lub
K08: ik ← pv - 1 ; elementu będziemy szukać w ZL
K09: Idź do K03

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 267 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

Program wypełnia tablicę 20 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999, losuje liczbę k z
zakresu od 1 do 20, wyszukuje k-ty największy element i na koniec wyświetla k, wyszukany element oraz
zawartość tablicy Z z zaznaczoną pozycją elementu.

Lazarus

// Szybkie wyszukiwanie
// Data: 18.05.2008
// (C)2012 mgr Jerzy Wałaszek
//--------------------------------
program prg;
const N = 20;
// Funkcja dzieli podany zbiór Z na dwie partycje:
// ZL - elementy mniejsze od elementu zwrotnego
// ZP - elementy większe od elementu zwrotnego
// Zwraca pozycję elementu zwrotnego
//------------------------------------------------
function Dziel_na_partycje(var Z : array of integer;
ip,ik : integer) : integer;
var i,v,x : integer;
begin
v := Z[ip]; i := ip; inc(ik);
while i < ik do
begin
repeat inc(i); until Z[i] >= v;
repeat dec(ik); until Z[ik] <= v;
if i < ik then
begin
x := Z[i]; Z[i] := Z[ik]; Z[ik] := x;
end;
end;
Z[ip] := Z[ik]; Z[ik] := v;
Dziel_na_partycje := ik;
end;
var
Z : array[0..N] of integer;
i,ip,ik,k,pv : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Na końcu Z[] umieszczamy wartownika
Z[N] := 1000;
// Wyświetlamy Z[] przed podziałem
for i := 0 to N - 1 do write(Z[i]:4);
writeln;
// Losujemy k
k := random(20) + 1;
// Szukamy k-tego największego elementu
ip := 0; ik := N - 1;
while true do
begin
pv := Dziel_na_partycje(Z,ip,ik);
if pv = N - k then break
else if N - k < pv then ik := pv - 1
else ip := pv + 1;
end;
// Wyświetlamy k i Z[N-k]
writeln('k = ',k,
', k-ty najwiekszy element = ',Z[N - k]);
writeln;
// Wyświetlamy Z[] po podziale
for i := 0 to N - 1 do write(Z[i]:4);
for i := 0 to pv - 1 do write(' ');
writeln(' ---');
writeln;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 268 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

writeln;
end.

Code::Blocks

// Szybkie wyszukiwanie
// Data: 18.05.2008
// (C)2012 mgr Jerzy Wałaszek
//--------------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 20;
// Funkcja dzieli podany zbiór Z na dwie partycje:
// ZL - elementy mniejsze od elementu zwrotnego
// ZP - elementy większe od elementu zwrotnego
// Zwraca pozycję elementu zwrotnego
//------------------------------------------------
int Dziel_na_partycje(int * Z, int ip, int ik)
{
int i,v,x;
v = Z[ip]; i = ip; ik++;
while(i < ik)
{
while(Z[++i] < v) ;
while(Z[--ik] > v) ;
if(i < ik)
{
x = Z[i]; Z[i] = Z[ik]; Z[ik] = x;
}
}
Z[ip] = Z[ik]; Z[ik] = v;
return ik;
}
int main()
{
int Z[N + 1],i,ip,ik,k,pv;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Na końcu Z[] umieszczamy wartownika
Z[N] = 1000;
// Wyświetlamy Z[] przed podziałem
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl;
// Losujemy k
k = 1 + (rand() % 20);
// Szukamy k-tego największego elementu
ip = 0; ik = N - 1;
while(true)
{
pv = Dziel_na_partycje(Z,ip,ik);
if(pv == N - k) break;
else if(N - k < pv) ik = pv - 1;
else ip = pv + 1;
}
// Wyświetlamy k i Z[N-k]
cout << "k = " << k
<< ", k-ty najwiekszy element = " << Z[N-k]
<< endl << endl;
// Wyświetlamy Z[] po podziale
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
for(i = 0; i < pv; i++) cout << " ";
cout << " ---\n\n";
return 0;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 269 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

Free Basic

' Szybkie wyszukiwanie


' Data: 18.05.2008
' (C)2012 mgr Jerzy Wałaszek
'--------------------------------
Const N = 20
' Funkcja dzieli podany zbiór Z na dwie partycje:
' ZL - elementy mniejsze od elementu zwrotnego
' ZP - elementy większe od elementu zwrotnego
' Zwraca pozycję elementu zwrotnego
'------------------------------------------------
Function Dziel_na_partycje(Z() As Integer, Byval ip As Integer, Byval ik As Integer) As Integer
Dim As Integer i,v
v = Z(ip): i = ip: ik += 1
While i < ik
Do: i += 1: Loop Until Z(i) >= v
Do: ik -= 1: Loop Until Z(ik) <= v
If i < ik Then Swap Z(i),Z(ik)
Wend
Z(ip) = Z(ik): Z(ik) = v
Dziel_na_partycje = ik
End Function
Dim As Integer Z(N),i,ip,ik,k,pv
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Na końcu Z[] umieszczamy wartownika
Z(N) = 1000
' Wyświetlamy Z[] przed podziałem
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print
' Losujemy k
k = Cint(Rnd * 19) + 1
' Szukamy k-tego największego elementu
ip = 0: ik = N - 1
Do
pv = Dziel_na_partycje(Z(),ip,ik)
If pv = N - k Then Exit Do
If N - k < pv Then ik = pv - 1: Else ip = pv + 1
Loop
' Wyświetlamy k i Z[N-k]
Print "k =";k;", k-ty najwiekszy element =";Z(N - k)
Print
' Wyświetlamy Z[] po podziale
For i = 0 To N - 1: Print Using "####";Z(i);: Next
For i = 0 To pv - 1: Print " ";: Next
Print " ---"
Print
End

Wynik
673 357 95 402 445 481 444 474 738 913 231 5 421 967 618 238 428 145 905 760
k = 11, k-ty najwiekszy element = 444
145 5 95 231 238 402 357 421 428 444 445 474 481 618 673 967 913 738 905 760
---

Szybkie wyszukiwanie k-tego największego elementu


(C)2012 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 270 / 519


Algorytmy i Struktury Danych - Szybkie wyszukiwanie k-tego największego elementu 2014-10-03

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0041.php 271 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie mediany
Tematy pokrewne Podrozdziały
Tablice – wektory Rozwiązanie 1
Podstawowe operacje na tablicach Rozwiązanie 2
Wyszukiwanie liniowe Rozwiązanie 3
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych

Problem
Dla zbioru Z znaleźć element, od którego w tym zbiorze jest tyle samo elementów większych lub równych co
mniejszych lub równych.

Element zbioru o powyższej własności nosi nazwę mediany (ang. median). Mediana posiada wiele ważnych zastosowań
praktycznych w statystyce, grafice, obróbce dźwięku i wielu innych dziedzinach.
Jeśli zbiór Z jest posortowany rosnąco, to

przy nieparzystej liczbie elementów n > 1 mediana jest elementem środkowym Z[n/2] (indeksy elementów rozpoczynają się
od 0).
Na przykład dla zbioru Z = {1,3,5,8,9} medianą jest element 5 – poprzedzają go dwa elementy 1 i 3 oraz wyprzedzają dwa
elementy 8 i 9.
przy parzystej liczbie elementów n > 1 mediana jest średnią arytmetyczną dwóch środkowych elementów Z[n/2-1] i Z[n/2].
Na przykład dla zbioru Z = {1,3,5,8,9,9} mediana jest równa (5 + 8) / 2 = 6,5. Od tej wartości jest dokładnie tyle samo
elementów mniejszych (1,3,5) co większych (8,9,9).
Istnieją również pojęcia dolnej mediany (ang. lower median) i górnej mediany (upper median), które w tym przypadku
oznaczają odpowiednio element Z[n/2-1] i Z[n/2] w ciągu uporządkowanym o parzystej liczbie elementów.

Rozwiązanie nr 1
Pierwsze nasuwające się rozwiązanie jest następujące:

Posortować zbiór rosnąco. Zwrócić element na pozycji n/2 (dla n nieparzystego – mediana, dla n parzystego –
mediana dolna).

Koszt czasowy zależy od zastosowanego algorytmu sortującego. Zwykle wykorzystuje się Sortowanie Szybkie (ang. Quick
Sort), opracowane przez prof. Tony'ego Hoare'a. Algorytm ten sortuje w czasie liniowo logarytmicznym – O(n log n). Zaletą tego
sposobu jest to, iż wiele współczesnych języków programowania posiada w swoich bibliotekach funkcję sortującą qsort(), która
wykorzystuje ten właśnie algorytm. Wadą z kolei jest to, iż do otrzymania wartości jednego elementu musimy sortować cały zbiór
danych.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 272 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

W podanym poniżej algorytmie sortowania szybkiego wykorzystujemy funkcję Dziel_na_partycję(Z,ip,ik), którą opisaliśmy
dokładnie w poprzednim rozdziale. Funkcja ta dzieli partycję zbioru Z określoną dwoma indeksami ip oraz ik na dwie mniejsze
partycje. Zwraca indeks podziałowy iv. Pierwsza partycja zawiera elementy od ip do iv - 1, a druga od iv do ik. Elementy w partycji
pierwszej są niewiększe od elementów w partycji drugiej.

Algorytm szybkiego sortowania i znajdowania mediany


Wejście
n – liczba elementów w zbiorze Z, n N.
Z – tablica (n+1)-elementowa odwzorowująca zbiór Z, w który sortujemy. Na pozycji Z[n] należy
umieścić wartownika o wartości większej od każdego elementu zbioru.
Wyjście:
Mediana zbioru Z.
Elementy pomocnicze:
iv – zawiera pozycję elementu zwrotnego, iv C
v – wartość elementu zwrotnego
i,j – indeksy w tablicy Z, i,j C
ip – indeks początku partycji, ip C
ik – indeks końca partycji, ik C

Lista kroków funkcji Dziel_na_partycje(Z,ip,ik):

K01: v ← Z[ip] ; zapamiętujemy wartość elementu zwrotnego


K02: i ← ip ; indeksem i będziemy szukali elementów ≥ v
K03: j ← ik + 1 ; indeksem j będziemy szukali elementów ≤ v
Dopóki i < j, wykonuj ; w pętli elementy większe umieszczamy w Z , a mniejsze w Z
K04: K05...K10 P L

K05: i ← i + 1 ; przesuwamy się na następną pozycję w ZL


K06: Jeśli Z[i] < v, to idź do K05 ; szukamy elementu, który nie należy do ZL
K07: j ← j - 1 ; przesuwamy się na poprzednią pozycję w ZP
K08: Jeśli Z[j] > v, to idź do K07 ; szukamy elementu, który nie należy do ZP
K09: Jeśli i < j, to Z[i] ↔ Z[j] ; znalezione elementy zamieniamy miejscami
K10: Z[ip] ← Z[j] ; zwalniamy pozycję elementu zwrotnego
K11: Z[j] ← v ; na zwolnionej pozycji umieszczamy element zwrotny
K12: Zakończ z wynikiem j ; kończymy zwracając pozycję elementu zwrotnego
Lista kroków: funkcji rekurencyjnej Sortuj_szybko(Z,ip,ik)

K01: iv ← Dziel_na_partycje(Z,ip,ik) ; wyznaczamy punkt podziałowy


K02: Jeśli ip < iv - 1, to Sortuj_szybko(Z,ip,iv - 1) ; sortujemy rekurencyjnie pierwszą partycję
K03: Jeśli ik > iv + 1, to Sortuj_szybko(Z,iv + 1,ik) ; sortujemy rekurencyjnie drugą partycję
K04: Zakończ
Lista kroków:
; na ostatniej pozycji umieszczamy
K01: Z[n] ← Największa liczba
wartownika
K02: Sortuj_szybko(Z,0,n-1) ; sortujemy cały zbiór
K03: Jeśli
n mod 2 = 1, to zakończ z wynikiem Z[n ; zwracamy medianę zwykłą
shr 1]
K04 Zakończ
z wynikiem (Z[(n shr 1) - 1] + Z[n shr 1]) ; mediana średnia z dolnej i górnej
:2

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 99 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999, wyświetla ją, sortuje
szybko, wyświetla posortowaną i zwraca medianę. Na wydruku zbioru posortowanego mediana znajduje się w środku
trzeciego wiersza.

Lazarus

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 273 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

// Wyszukiwanie mediany
// Data: 21.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------
program prg;
const N = 99;
// Funkcja dzieli podany zbiór Z na dwie partycje:
// ZL - elementy mniejsze od elementu zwrotnego
// ZP - elementy większe od elementu zwrotnego
// Zwraca pozycję elementu zwrotnego
//------------------------------------------------
function Dziel_na_partycje(var Z : array of integer;
ip,ik : integer) : integer;
var
i,v,x : integer;
begin
v := Z[ip]; i := ip; inc(ik);
while i < ik do
begin
repeat inc(i); until Z[i] >= v;
repeat dec(ik); until Z[ik] <= v;
if i < ik then
begin
x := Z[i]; Z[i] := Z[ik]; Z[ik] := x;
end;
end;
Z[ip] := Z[ik]; Z[ik] := v;
Dziel_na_partycje := ik;
end;
// Procedura sortuje rosnąco podany zbiór
//---------------------------------------
procedure Sortuj_szybko(var Z : array of integer;
ip,ik : integer);
var
iv : integer;
begin
iv := Dziel_na_partycje(Z,ip,ik);
if ip < iv - 1 then Sortuj_szybko(Z,ip,iv - 1);
if ik > iv + 1 then Sortuj_szybko(Z,iv + 1,ik);
end;
var
Z : array[0..N] of integer;
i,ip,ik,k,pv : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Na końcu Z[] umieszczamy wartownika
Z[N] := 1000;
// Wyświetlamy Z[] przed podziałem
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Sortujemy szybko tablicę Z[]
Sortuj_szybko(Z,0,N - 1);
// Wyświetlamy Z[] po sortowaniu
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Wyświetlamy medianę
writeln(Z[N shr 1]);
writeln;
writeln;
end.

Code::Blocks

// Wyszukiwanie mediany
// Data: 21.05.2008
// (C)2012 mgr Jerzy Wałaszek
//---------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 274 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 99;
// Funkcja dzieli podany zbiór Z na dwie partycje:
// ZL - elementy mniejsze od elementu zwrotnego
// ZP - elementy większe od elementu zwrotnego
// Zwraca pozycję elementu zwrotnego
//------------------------------------------------
int Dziel_na_partycje(int * Z, int ip, int ik)
{
int i,v,x;
v = Z[ip]; i = ip; ik++;
while(i < ik)
{
while(Z[++i] < v) ;
while(Z[--ik] > v) ;
if(i < ik)
{
x = Z[i]; Z[i] = Z[ik]; Z[ik] = x;
}
}
Z[ip] = Z[ik]; Z[ik] = v;
return ik;
}
// Procedura sortuje rosnąco podany zbiór
//---------------------------------------
void Sortuj_szybko(int * Z, int ip, int ik)
{
int iv;
iv = Dziel_na_partycje(Z,ip,ik);
if(ip < iv - 1) Sortuj_szybko(Z,ip,iv - 1);
if(ik > iv + 1) Sortuj_szybko(Z,iv + 1,ik);
}
int main()
{
int Z[N + 1],i,ip,ik,k,pv;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Na końcu Z[] umieszczamy wartownika
Z[N] = 1000;
// Wyświetlamy Z[] przed podziałem
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Sortujemy szybko tablicę Z[]
Sortuj_szybko(Z,0,N - 1);
// Wyświetlamy Z[] po sortowaniu
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Wyświetlamy medianę
cout << Z[N >> 1] << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie mediany


' Data: 21.05.2008
' (C)2012 mgr Jerzy Wałaszek
'---------------------------
Const N = 99
' Funkcja dzieli podany zbiór Z na dwie partycje:
' ZL - elementy mniejsze od elementu zwrotnego

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 275 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

' ZP - elementy większe od elementu zwrotnego


' Zwraca pozycję elementu zwrotnego
'------------------------------------------------
Function Dziel_na_partycje(Z() As Integer, Byval ip As Integer, Byval ik As Integer) As Integer
Dim As Integer i,v
v = Z(ip): i = ip: ik += 1
While i < ik
Do: i += 1: Loop Until Z(i) >= v
Do: ik -= 1: Loop Until Z(ik) <= v
If i < ik Then Swap Z(i),Z(ik)
Wend
Z(ip) = Z(ik): Z(ik) = v
Dziel_na_partycje = ik
End Function
' Procedura sortuje rosnąco podany zbiór
'---------------------------------------
Sub Sortuj_szybko(Z() As Integer, Byval ip As Integer, Byval ik As Integer)
Dim iv As Integer
iv = Dziel_na_partycje(Z(),ip,ik)
If ip < iv - 1 Then Sortuj_szybko(Z(),ip,iv - 1)
If ik > iv + 1 Then Sortuj_szybko(Z(),iv + 1,ik)
End Sub
Dim As Integer Z(N),i,ip,ik,k,pv
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Na końcu Z[] umieszczamy wartownika
Z(N) = 1000
' Wyświetlamy Z[] przed podziałem
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Sortujemy szybko tablicę Z[]
Sortuj_szybko(Z(),0,N - 1)
' Wyświetlamy Z[] po sortowaniu
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Wyświetlamy medianę
Print Z(N Shr 1)
Print
Print
End

Wynik
627 599 221 107 893 882 757 177 649 711 877 285 138 542 419 771 562 342 674 360
484 758 915 591 581 7 996 841 164 463 333 427 280 402 146 92 105 552 245 50
301 610 674 700 198 717 51 928 59 200 772 75 580 945 642 981 278 678 294 464
621 284 844 74 328 320 882 921 302 263 358 102 299 823 164 944 119 189 389 829
583 893 857 357 781 216 205 243 152 504 51 830 625 24 21 3 333 241 270
3 7 21 24 50 51 51 59 74 75 92 102 105 107 119 138 146 152 164 164
177 189 198 200 205 216 221 241 243 245 263 270 278 280 284 285 294 299 301 302
320 328 333 333 342 357 358 360 389 402 419 427 463 464 484 504 542 552 562 580
581 583 591 599 610 621 625 627 642 649 674 674 678 700 711 717 757 758 771 772
781 823 829 830 841 844 857 877 882 882 893 893 915 921 928 944 945 981 996
402

Rozwiązanie nr 2
Lepszym podejściem do znajdowania mediany zbioru od jego sortowania jest zastosowanie prostszego algorytmu – Szybkiego
Wyszukiwania (ang. Quick Search), który opisaliśmy dokładnie w poprzednim rozdziale. Szukanym elementem będzie
oczywiście (n/2 + 1)-szy największy element zbioru. Algorytm Szybkiego Wyszukiwania jest podobny w działaniu do algorytmu
Szybkiego Sortowania. Różnica polega jedynie na tym, iż Szybkie Wyszukiwanie przetwarza zawsze tylko jedną z dwóch partycji,
mianowicie tę, w której występuje poszukiwany element. Druga partycja pozostaje przetworzona tylko wstępnie. Algorytm
Sortowania Szybkiego przetwarza do końca cały zbiór. W efekcie, chociaż klasa złożoności obu metod jest liniowo logarytmiczna
O(n log n), to jednak Wyszukiwanie Szybkie da szybciej wynik od Szybkiego Sortowania.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 276 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

Algorytm szybkiego wyszukiwania mediany


Wejście
n – liczba elementów w zbiorze Z
Z – tablica (n+1)-elementowa odwzorowująca zbiór Z, w którym poszukujemy mediany. Na pozycji Z[n]
należy umieścić wartownika o wartości większej od każdego elementu zbioru.
Wyjście:
Mediana zbioru Z. Jeśli zbiór posiada parzystą liczbę elementów, to wynikiem jest mediana górna.
Elementy pomocnicze:
m – indeks mediany, m C
ip – indeks początku partycji, ip C
ik – indeks końca partycji, ik C
i,j – indeksy w tablicy Z, i,j C
v – wartość elementu zwrotnego
Lista kroków:
K01: m ← n shr 1 ; wyznaczamy docelową pozycję mediany
K02: Z[n] ← Największa liczba ; do zbioru wstawiamy wartownika
K03: ip ← 0; ik ← n - 1 ; partycja startowa obejmuje cały zbiór
K04: v ← Z[ip] ; wybieramy element zwrotny
K05: i ← ip ; indeksem i poszukujemy elementów ≥ v
K06: j ← ik + 1 ; indeksem j poszukujemy elementów ≤ v
K07: Dopóki i < j, wykonuj K08...K12 ; w pętli zamieniamy elementy większe z mniejszymi
K08: i ← i + 1 ; szukamy elementów ≥ v
K09: Jeśli Z[i] < v, to idź do K08
K10: j ← j - 1 ; teraz szukamy elementów ≤ v
K11: Jeśli Z[j] > v, to idź do K10
K12: Jeśli i < j, to Z[i] ↔ Z[j] ; jeśli elementy są w złych partycjach, zamieniamy je
K13: Z[ip] ← Z[j] ; robimy miejsce pod v
K14: Z[j] ← v ; v umieszczamy na początku drugiej partycji
K15: Jeśli j = m, to zakończ z wynikiem Z[m] ; sprawdzamy, czy znaleziono medianę
K16: Jeśli m < j, to ik ← j - 1 ; wybieramy jedną z dwóch partycji, w której będziemy
inaczej ip ← j + 1 ; dalej szukać mediany
K17: Idź do K04 ; i kontynuujemy pętlę

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wypełnia tablicę 99 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999, wyświetla ją, szybko
wyszukuje medianę, wyświetla przetworzoną tablicę i zwraca medianę. Na wydruku zbioru przetworzonego mediana
znajduje się w środku trzeciego wiersza. Zwróć uwagę, iż zbiór Z przetworzony tym algorytmem nie jest
posortowany. Jednakże mediana znajduje się na właściwym miejscu – elementy wcześniejsze w zbiorze są od niej
mniejsze (lub równe). Elementy dalsze w zbiorze są większe od mediany (lub równe).

Lazarus

// Szybkie wyszukiwanie mediany


// Data: 21.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const N = 99;
var
Z : array[0..N] of integer;
m,ip,ik,i,j,v,x : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 277 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

for i := 0 to N - 1 do Z[i] := random(1000);


// Na końcu umieszczamy wartownika
Z[N] := 1000;
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Ustalamy pozycję mediany w zbiorze
m := N shr 1;
// Szukamy mediany
ip := 0; ik := N - 1;
repeat
v := Z[ip]; i := ip; j := ik + 1;
while i < j do
begin
repeat inc(i); until Z[i] >= v;
repeat dec(j); until Z[j] <= v;
if i < j then
begin
x := Z[i]; Z[i] := Z[j]; Z[j] := x;
end;
end;
Z[ip] := Z[j]; Z[j] := v;
if m = j then break;
if m < j then ik := j - 1 else ip := j + 1;
until false;
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Wyświetlamy medianę
writeln(Z[m]);
writeln; writeln;
end.

Code::Blocks

// Szybkie wyszukiwanie mediany


// Data: 21.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 99;
int main()
{
int Z[N + 1],m,ip,ik,i,j,v,x;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Na końcu umieszczamy wartownika
Z[N] = 1000;
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Ustalamy pozycję mediany w zbiorze
m = N >> 1;
// Szukamy mediany
ip = 0; ik = N - 1;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 278 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

for(;;)
{
v = Z[ip]; i = ip; j = ik + 1;
while(i < j)
{
while(Z[++i] < v) ;
while(Z[--j] > v) ;
if(i < j)
{
x = Z[i]; Z[i] = Z[j]; Z[j] = x;
}
}
Z[ip] = Z[j]; Z[j] = v;
if(m == j) break;
if(m < j) ik = j - 1; else ip = j + 1;
}
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Wyświetlamy medianę
cout << Z[m] << endl << endl;
return 0;
}

Free Basic

' Szybkie wyszukiwanie mediany


' Data: 21.05.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 99
Dim As Integer Z(N),m,ip,ik,i,j,v
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Na końcu umieszczamy wartownika
Z(N) = 1000
' Wyświetlamy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Ustalamy pozycję mediany w zbiorze
m = N Shr 1
' Szukamy mediany
ip = 0: ik = N - 1
Do
v = Z(ip): i = ip: j = ik + 1
While i < j
Do: i += 1: Loop Until Z(i) >= v
Do: j -= 1: Loop Until Z(j) <= v
If i < j Then Swap Z(i),Z(j)
Wend
Z(ip) = Z(j): Z(j) = v
If m = j Then Exit Do
If m < j Then ik = j - 1 Else ip = j + 1
Loop
' Wyświetlamy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Wyświetlamy medianę
Print Z(m)
Print: Print
End

Wynik
537 306 974 949 529 397 188 217 993 999 445 717 451 685 277 934 693 562 253 693
570 308 932 394 633 7 670 21 85 416 153 787 534 739 166 28 648 757 6 955

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 279 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

570 308 932 394 633 7 670 21 85 416 153 787 534 739 166 28 648 757 6 955
186 506 805 802 145 795 882 936 945 371 662 447 859 854 710 132 310 131 831 672
800 381 766 589 704 179 153 616 354 30 884 540 411 811 148 307 564 903 621 56
807 706 599 769 175 229 637 507 758 2 412 400 834 893 895 860 338 157 337
371 306 337 157 529 397 188 217 338 400 445 412 451 2 277 507 229 175 253 56
307 308 148 394 411 7 30 21 85 416 153 354 534 153 166 28 179 381 6 131
186 506 310 132 145 447 537 540 562 564 570 589 599 637 621 616 633 648 693 672
662 670 685 693 704 706 739 766 787 757 800 831 805 811 769 802 710 758 795 717
807 834 903 932 884 895 854 934 859 882 860 893 936 999 955 945 993 949 974
564

Rozwiązanie nr 3
Profesor Niklaus Wirth (twórca języków programowania Pascal, Modula 2, Oberon) w swojej książce pt. Algorytmy + Struktury
Danych = Programy przedstawia interesujący algorytm wyszukiwania mediany zbioru, który jest wariacją algorytmu Szybkiego
Wyszukiwania Tony'ego Hoare'a. Pod względem funkcjonalnym i klasy złożoności obliczeniowej oba algorytmy są równoważne.
Dodatkowo w zbiorze Z nie musimy umieszczać wartownika.

Algorytm Wirtha szybkiego wyszukiwania mediany


Wejście
n – liczba elementów w zbiorze Z
Z – tablica n-elementowa odwzorowująca zbiór Z, w którym poszukujemy mediany.
Wyjście:
Mediana zbioru Z. Jeśli zbiór posiada parzystą liczbę elementów, to wynikiem jest mediana górna.
Elementy pomocnicze:
m – indeks mediany
ip – indeks początku partycji, ip C
ik – indeks końca partycji, ik C
i,j – indeksy w tablicy Z, i,j C
v – wartość elementu zwrotnego
Lista kroków:
K01: m ← n shr 1 ; wyznaczamy docelową pozycję mediany
K02: ip ← 0; ik ← n - 1 ; określamy partycję początkową
K03: Dopóki ip < ik wykonuj K04...K12 ; szukamy m-tego najmniejszego elementu
K04: v ← Z[m] ; podziału dokonujemy wg m-tego elementu zbioru
K05: i ← ip ; indeksem i będziemy szukali elementów ≥ v
K06: j ← ik ; indeksem j będziemy szukali elementów ≤ v
K07: Dopóki Z[i] < v wykonuj i ← i + 1 ; szukamy elementu nie pasującego do dolnej partycji
K08: Dopóki v < Z[j] wykonuj j ← j - 1 ; szukamy elementu nie pasującego do górnej partycji
K09: Jeśli i ≤ j, to Z[i] ↔ Z[j]; i ← i + 1; j ; elementy zamieniamy ze sobą, aby trafiły do
←j -1 właściwych
; partycji. Po zamianie indeksy i,j muszą być
zmodyfikowane.
K10: Jeśli i ≤ j, to idź do K07
K11: Jeśli j < m, to ip ← i ; wybieramy nową partycję do poszukiwań mediany
K12: Jeśli m < i, to ik ← j
K13: Zakończ z wynikiem Z[m] ; mediana znaleziona

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych
programów oraz sposób korzystania z nich.

Program wypełnia tablicę 99 elementową Z liczbami pseudolosowymi z zakresu od 0 do 999, wyświetla


ją, wyszukuje medianę algorytmem Wirtha, wyświetla przetworzoną tablicę i zwraca medianę. Na
wydruku zbioru przetworzonego mediana znajduje się w środku trzeciego wiersza.

Lazarus

// Szybkie wyszukiwanie mediany


// Data: 22.05.2008
// (C)2012 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 280 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

//-----------------------------
program prg;
const N = 99;
var
Z : array[0..N - 1] of integer;
m,ip,ik,i,j,v,x : integer;
begin
randomize;
// Przygotowujemy tablicę Z[]
for i := 0 to N - 1 do Z[i] := random(1000);
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Ustalamy pozycję mediany w zbiorze
m := N shr 1;
// Szukamy mediany
ip := 0; ik := N - 1;
while ip < ik do
begin
v := Z[m]; i := ip; j := ik;
repeat
while Z[i] < v do inc(i);
while v < Z[j] do dec(j);
if i <= j then
begin
x := Z[i]; Z[i] := Z[j]; Z[j] := x;
inc(i); dec(j);
end;
until i > j;
if j < m then ip := i;
if m < i then ik := j;
end;
// Wyświetlamy tablicę Z[]
for i := 0 to N - 1 do write(Z[i]:4);
writeln; writeln;
// Wyświetlamy medianę
writeln(Z[m]);
writeln; writeln;
end.

Code::Blocks

// Szybkie wyszukiwanie mediany


// Data: 22.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
const int N = 99;
int Z[N - 1],m,ip,ik,i,j,v,x;
srand((unsigned)time(NULL));
// Przygotowujemy tablicę Z[]
for(i = 0; i < N; i++) Z[i] = rand() % 1000;
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 281 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

// Ustalamy pozycję mediany w zbiorze


m = N >> 1;
// Szukamy mediany
ip = 0; ik = N - 1;
while(ip < ik)
{
v = Z[m]; i = ip; j = ik;
do
{
while(Z[i] < v) i++;
while(v < Z[j]) j--;
if(i <= j)
{
x = Z[i]; Z[i] = Z[j]; Z[j] = x;
i++; j--;
}
} while(i <= j);
if(j < m) ip = i;
if(m < i) ik = j;
}
// Wyświetlamy tablicę Z[]
for(i = 0; i < N; i++) cout << setw(4) << Z[i];
cout << endl << endl;
// Wyświetlamy medianę
cout << Z[m] << endl << endl << endl;
return 0;
}

Free Basic

' Szybkie wyszukiwanie mediany


' Data: 22.05.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 99
Dim As Integer Z(N - 1),m,ip,ik,i,j,v
Randomize
' Przygotowujemy tablicę Z[]
For i = 0 To N - 1: Z(i) = Cint(Rnd * 999): Next
' Wyświetlamy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Ustalamy pozycję mediany w zbiorze
m = N Shr 1
' Szukamy mediany
ip = 0: ik = N - 1
While ip < ik
v = Z(m): i = ip: j = ik
Do
While Z(i) < v: i += 1: Wend
While v < Z(j): j -= 1: Wend
If i <= j Then
Swap Z(i),Z(j)
i += 1: j -= 1
End If
Loop Until i > j
If j < m Then ip = i
If m < i Then ik = j
Wend
' Wyświetlamy tablicę Z[]
For i = 0 To N - 1: Print Using "####";Z(i);: Next
Print: Print
' Wyświetlamy medianę
Print Z(m)
Print: Print
End

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 282 / 519


Algorytmy i Struktury Danych - Wyszukiwanie mediany 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042.php 283 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Zbiory rozłączne – implementacja w tablicy


Tematy pokrewne
Tablice – wektory
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Zliczanie wg kryterium
Wyszukiwanie max lub min
Jednoczesne wyszukiwanie max i min
Zastosowania wyszukiwania – sortowanie przez wybór
Wyszukiwanie najczęstszej wartości w zbiorze – dominanta
Wyszukiwanie lidera
Wyszukiwanie binarne
Wyszukiwanie interpolacyjne
Wyszukiwanie k-tego największego elementu
Wyszukiwanie szybkie k-tego największego elementu
Wyszukiwanie mediany zbioru
Zbiory rozłączne – implementacja w tablicy
Wbudowane generatory liczb pseudolosowych
Zbiory rozłączne – implementacja listowa
Zbiory rozłączne – implementacja za pomocą drzew

Problem
Zaprojektować strukturę zbiorów rozłącznych opartą na tablicy.

Struktura zbiorów rozłącznych (ang. disjoint sets structure) pozwala reprezentować zbiory, które nie posiadają części wspólnych.
Podstawowym problemem jest określenie sposobu rozpoznawania przynależności elementu do jednego z podzbiorów. Otóż umówmy się, że w
każdym z podzbiorów wybierzemy dokładnie jeden element. Element ten będzie reprezentantem danego podzbioru.

Na powyższym rysunku widzimy cztery rozłączne zbiory. W każdym ze zbiorów jeden element jest reprezentantem i oznaczyliśmy go kolorem
czerwonym. Teraz możemy powiedzieć, że są to zbiory H, K, L i E. Zbiór H jest jest reprezentowany przez element H, zbiór K reprezentuje
element K, itd.
W strukturze zbiorów rozłącznych definiuje się zwykle dwie operacje:
1. Find(x) – zwraca reprezentanta podzbioru, do którego należy element x. Np. w naszym przykładzie Find(G) = K, Find(F) = L, itd. Operacja
ta pozwala określić przynależność elementu x do jednego z podzbiorów struktury.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 284 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

2. Union(x,y) – łączy ze sobą podzbiory zawierające elementy x i y w jeden nowy podzbiór. Reprezentantem nowego podzbioru staje się
jeden z reprezentantów poprzednich podzbiorów zawierających x lub y. Np. Union(C,A) tworzy podzbiór:

Strukturę zbiorów rozłącznych można prosto zrealizować w postaci dwóch tablic o tym samym rozmiarze. W jednej tablicy przechowujemy
elementy. W drugiej tablicy przechowujemy reprezentantów podzbiorów, zawierających poszczególne elementy. Nazwijmy te tablice
odpowiednio E (elementy) i R (reprezentanci). W naszym przykładzie tablice te posiadają następującą zawartość:

Tablica E Tablica R
indeks zawartość indeks zawartość
0 A 0 L
1 B 1 H
2 C 2 H
3 D 3 E
4 E 4 E
5 F 5 L
6 G 6 K
7 H 7 H
8 I 8 K
9 J 9 L
10 K 10 K
11 L 11 L

Operacja Find otrzymuje indeks elementu w tablicy E i zwraca element o tym indeksie z tablicy R. Wykonywana jest zatem w czasie stałym
O(1).
Operacja Union jest tutaj bardziej skomplikowana. Otrzymuje ona indeksy elementów x i y w tablicy E. Najpierw Union musi sprawdzić, czy oba
elementy należą do różnych podzbiorów. W tym celu wykonuje Find(x) i Find(y), zapamiętując wyniki w rx i ry. Jeśli rx i ry są różnymi
reprezentantami, to Union musi połączyć reprezentowane przez nie podzbiory w jeden podzbiór, który będzie zawierał elementy z obu
podzbiorów. Operacja ta polega na przypisaniu wszystkim elementom podzbioru ry reprezentanta rx (lub na odwrót). Wymaga to przeglądnięcia
całej tablicy R i zamienienia wszystkich elementów ry na rx. Otrzymujemy złożoność liniową O(n).
Jeśli elementy tworzą spójny zbiór wartości, to można uprościć strukturę zbiorów rozłącznych do jednej tablicy R. W takim przypadku indeksy
odpowiadają bezpośrednio elementom zbioru, a zawartości komórek odpowiadają reprezentantom (indeksom elementów, które są
reprezentantami). Zamieńmy w naszym przykładzie literki na numery elementów od 0 do 11.Otrzymamy:

Tablica R
indeks zawartość
0 11
1 7
2 7
3 4
4 4
5 11
6 10
7 7
8 10
9 11
10 10
11 11

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 285 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

Operacja Find dla danego elementu (indeksu) zwraca zawartość komórki tablicy R o tym indeksie, czyli zwraca reprezentanta podzbioru, który
zawiera dany element. Np. Find(6) = 10, czyli element nr 6 znajduje się w podzbiorze o reprezentancie 10.
Operacja Union(x,y) najpierw zapamiętuje w zmiennych rx i ry odpowiednio komórki R[x] i R[y]. Jeśli rx i ry są różne, to elementy x i y leżą w
rozłącznych podzbiorach. W takim razie Union zastępuje w tablicy R wszystkie wystąpienia ry przez rx (lub na odwrót, wg uznania lub potrzeb).
Na samym początku korzystania ze struktury zbiorów rozłącznych tablicę R należy odpowiednio zainicjować. W tym przypadku wystarczy
wprowadzić do jej komórek ich indeksy. Będzie to odpowiadało utworzeniu n rozłącznych zbiorów jednoelementowych:

Tablica R
indeks zawartość
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
11 11

Algorytmy obsługi struktury zbiorów rozłącznych w zrealizowanej w tablicy


Find(R,x)
Wejście
R – tablica reprezentantów
x – element zbioru, indeks w tablicy R. x C

Wyjście:
Reprezentant podzbioru, w którym znajduje się element x.
Lista kroków
K01: Zakończ z wynikiem R[x]

Union(R,n,x,y)
Wejście
R – tablica reprezentantów
n – liczba elementów, n C
x,y – elementy zbiorów, indeksy w tablicy R. x,y C

Wyjście:
W tablicy R zostają połączone w jeden zbiór zbiory zawierające elementy x i y.
Elementy pomocnicze
rx, ry – reprezentanci podzbiorów, w których znajdują się elementy x i y. rx,ry C
i – indeks, i C

Lista kroków
K01: rx ← R[x] ; znajdujemy reprezentanta zbioru, który zawiera element x
K02: ry ← R[y] ; znajdujemy reprezentanta zbioru, który zawiera element y
K03: Jeśli rx = ry, to zakończ ; elementy x i y muszą być w rozłącznych zbiorach
K04: Dla i = 0,1,...,n-1 wykonuj: ; zastępujemy reprezentanta ry przez rx dla wszystkich elementów
Jeśli R[i] = ry, to R[i] ← rx
K05: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 286 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

Program tworzy tablicę R 10-cio elementową, inicjuje ją, a następnie losuje 10 razy pary elementów. Dla
wylosowanych elementów wykonywana jest operacja Union, co powoduje połączenie zbiorów, które te elementy
zawierają. Na końcu wypisana zostaje przynależność każdego elementu do określonego podzbioru, liczba
podzbiorów oraz zawartość tych podzbiorów.

Lazarus

// Struktura zbiorów rozłącznych


// Data: 25.03.2014
// (C)2014 mgr Jerzy Wałaszek
//---------------------------
program DSStruct;
const N = 10; // Liczba elementów
var
R : array[0..N-1] of integer;
// Łączy dwa podzbiory w jeden
//----------------------------
procedure Union(x,y : integer);
var
rx,ry,i : integer;
begin
rx := R[x]; // Wyznaczamy reprezentanta zbioru zawierającego x
ry := R[y]; // i reprezentanta zbioru zawierającego y
if rx <> ry then // Łączenie wymaga różnych zbiorów
for i := 0 to N-1 do // Przeglądamy kolejne elementy tablicy R
if R[i] = ry then R[i] := rx; // i zastępujemy w nich reprezentanta ry przez rx
end;
// **********************
// *** PROGRAM GŁÓWNY ***
// **********************
var
i,j,x,y,c : integer;
begin
Randomize; // Inicjujemy generator pseudolosowy
for i := 0 to N - 1 do
R[i] := i; // Ustawiamy tablicę R
for i := 1 to N do
begin
x := random(N); // Generujemy losowe x i y
y := random(N);
Union(x,y); // Łączymy zbiory
end;
c := 0; // Licznik podzbiorów w R
// Wyświetlamy wyniki
for i := 0 to N - 1 do
begin
if R[i] = i then inc(c); // Zliczamy reprezentantów
writeln(i,' is in set ',R[i]);
end;
writeln;
writeln('Number of sets = ',c);
writeln;
for i := 0 to N - 1 do
if R[i] = i then
begin
write('Set ',i,' : ');
for j := 0 to N - 1 do
if R[j] = i then write(j,' ');
writeln;
end;
writeln;
end.

Code::Blocks

// Struktura zbiorów rozłącznych


// Data: 25.03.2014
// (C)2014 mgr Jerzy Wałaszek
//---------------------------
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 287 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03
using namespace std;
const int N = 10; // Liczba elementów
int R[N];
// Łączy dwa podzbiory w jeden
//----------------------------
void Union(int x, int y)
{
int rx,ry,i;
rx = R[x]; // Wyznaczamy reprezentanta zbioru zawierającego x
ry = R[y]; // i reprezentanta zbioru zawierającego y
if(rx != ry) // Łączenie wymaga różnych zbiorów
for(i = 0; i < N; i++) // Przeglądamy kolejne elementy tablicy R
if(R[i] == ry) R[i] = rx; // i zastępujemy w nich reprezentanta ry przez rx
}
// **********************
// *** PROGRAM GŁÓWNY ***
// **********************
int main()
{
int i,j,x,y,c;
srand(time(NULL)); // Inicjujemy generator pseudolosowy
for(i = 0; i < N; i++)
R[i] = i; // Ustawiamy tablicę R
for(i = 0; i < N; i++)
{
x = rand() % N; // Generujemy losowe x i y
y = rand() % N;
Union(x,y); // Łączymy zbiory
}
c = 0; // Licznik podzbiorów w R
// Wyświetlamy wyniki
for(i = 0; i < N; i++)
{
if(R[i] == i) c++; // Zliczamy reprezentantów
cout << i << " is in set " << R[i] << endl;
}
cout << endl << "Number of sets = " << c << endl << endl;
for(i = 0; i < N; i++)
if(R[i] == i)
{
cout << "Set " << i << " : ";
for(j = 0; j < N; j++)
if(R[j] == i) cout << j << " ";
cout << endl;
}
cout << endl;
return 0;
}

Free Basic

' Struktura zbiorów rozłącznych


' Data: 25.03.2014
' (C)2014 mgr Jerzy Wałaszek
'---------------------------
Const N = 10 ' Liczba elementów
Dim Shared As Integer R(0 To N - 1)
' Łączy dwa podzbiory w jeden
'----------------------------
Sub UnionSets(Byval x As Integer, Byval y As Integer)
Dim As Integer rx,ry,i
rx = R(x) ' Wyznaczamy reprezentanta zbioru zawierającego x
ry = R(y) ' i reprezentanta zbioru zawierającego y
If rx <> ry Then ' Łączenie wymaga różnych zbiorów
For i = 0 To N - 1 ' Przeglądamy kolejne elementy tablicy R
If R(i) = ry Then R(i) = rx ' i zastępujemy w nich reprezentanta ry przez rx
Next
End If
End Sub

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 288 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

' **********************
' *** PROGRAM GŁÓWNY ***
' **********************
Dim As Integer i,j,x,y,c
Randomize ' Inicjujemy generator pseudolosowy
For i = 0 To N - 1
R(i) = i ' Ustawiamy tablicę R
Next
For i = 0 To N - 1
x = Int(Rnd * N) ' Generujemy losowe x i y
y = Int(Rnd * N)
UnionSets(x,y) ' Łączymy zbiory
Next
c = 0 ' Licznik podzbiorów w R
' Wyświetlamy wyniki
For i = 0 To N - 1
If R(i) = i Then c += 1 ' Zliczamy reprezentantów
Print i;" is in set ";R(i)
Next
Print
Print "Number of sets =";c
Print
For i = 0 To N - 1
If R(i) = i Then
Print "Set";i;" :";
For j = 0 To N - 1
If R(j) = i Then Print j;
Next
Print
End If
Next
Print
End

Wynik
0 is in set 0
1 is in set 8
2 is in set 6
3 is in set 8
4 is in set 8
5 is in set 5
6 is in set 6
7 is in set 6
8 is in set 8
9 is in set 8
Number of sets = 4
Set 0 : 0
Set 5 : 5
Set 6 : 2 6 7
Set 8 : 1 3 4 8 9

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 289 / 519


Algorytmy i Struktury Danych - Zbiory rozłączne 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0042a.php 290 / 519


Algorytmy i Struktury Danych - Łańcuchy znakowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Łańcuchy znakowe
Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

Współczesne komputery oprócz liczb przetwarzają również teksty. Teksty zbudowane są z ciągów znakowych, które możemy traktować jako
tablice znaków – dostęp do poszczególnych liter odbywa się, podobnie jak u tablic, poprzez indeks, czyli numer znaku w ciągu. W
rzeczywistości znak przechowywany jest w pamięci komputera w postaci liczby – kodu znaku. Rozróżniamy dwa rodzaje takich kodów – 8
bitowe (najczęściej są to znormalizowane kody wg standardu ASCII – ang. American Standard Code for Information Interchange – Amerykański
Standardowy Kod do Wymiany Informacji) i 16 bitowe (standard Unicode). Znaki 16 bitowe wprowadzono w celu ominięcia ograniczeń kodów 8
bitowych, które mogą reprezentować jedynie do 256 różnych znaków, co jest niewystarczające do reprezentowania wszystkich znaków
narodowych oraz różnych symboli stosowanych w matematyce, fizyce i innych dziedzinach ludzkiej działalności.
Jednym z podstawowych problemów znakowych jest problem wyszukiwania wzorca (ang. pattern searching lub patterrn matching) – tzn. mając
dany pewien ciąg znaków szukamy w innym ciągu znakowym miejsca, w którym występuje ciąg pierwszy. Taki problem często występuje
podczas redagowania tekstów, gdy w większym tekście należy wyszukać określoną frazę. Informatycy poświęcili wiele pracy na rozwiązanie
tego podstawowego problemu. W efekcie wynaleziono bardzo efektywne algorytmy wyszukiwania wzorca, które znajdują zastosowania również
przy rozwiązywaniu innych problemów tekstowych.
W tym rozdziale zajmiemy się algorytmami przetwarzania danych tekstowych. Wiele z nich można z powodzeniem wykorzystywać na różnych
konkursach programowania oraz na olimpiadach informatycznych. Zapraszamy do lektury.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0043.php 291 / 519


Algorytmy i Struktury Danych - Łańcuchy znakowe 2014-10-03

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0043.php 292 / 519


Algorytmy i Struktury Danych - Podstawowe pojęcia tekstowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Pojęcia podstawowe przy przetwarzaniu tekstów


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

W algorytmach tekstowych będziemy stosowali następujące ustalenia i oznaczenia:

Alfabetem (ang. alphabet) będziemy nazywali skończony zbiór symboli (ang. the set of symbols) – zwykle alfabet jest zbiorem
liter, znaków, cyfr, jednakże w przypadku uogólnionym może to być dowolny zbiór obiektów, które można w jakiś sposób
sklasyfikować.

Skończone ciągi symboli alfabetu nazwiemy łańcuchami (ang. strings). Czasami w tym znaczeniu używa się terminów słowa
(ang. words) lub teksty (ang. texts).

Przez pusty łańcuch (ang. empty string) rozumiemy łańcuch nie zawierający ani jednego znaku.

s[i] – oznacza i-ty znak łańcucha s. Umówmy się, iż indeksy w łańcuchach rozpoczynają się od 0. W języku Pascal oraz Basic
indeksy startują od wartości 1. Należy to uwzględniać w programach. My wybieramy wartość 0, robiąc ukłon w stronę języka C++,
który jest o wiele bardziej popularny niż Pascal i Basic.

|s| – oznacza długość łańcucha (ang. string length), czyli liczbę przechowywanych w nim aktualnie znaków. Łańcuch pusty ma
długość 0.

s[i : j] – oznacza fragment łańcucha (ang substring) zawierający kolejne znaki s[i] s[i + 1] s[i + 2] ... s[j - 1]. Znak s[j] nie należy
do tej sekwencji. Na przykład, jeśli s = "ALA MA BOCIANA", to s[4 : 9] = "MA BO". Taki fragment łańcucha s będziemy nazywali
oknem (ang. string window).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0044.php 293 / 519


Algorytmy i Struktury Danych - Podstawowe pojęcia tekstowe 2014-10-03

Długość podłańcucha wyliczamy ze wzoru:

| s[i : j] | = j - i

Podłańcuch s[i : i] jest łańcuchem pustym – posiada długość 0, co wynika bezpośrednio z podanego powyżej wzoru.

s = t – równość dwóch łańcuchów oznacza, iż są one tej samej długości oraz posiadają identyczne znaki na tych samych
pozycjach.

s[i : i + | t | ] = t – oznacza to, iż fragment łańcucha s od pozycji i-tej do i + |t| - 1 zawiera dokładnie te same znaki, co łańcuch t.
Na przykład, jeśli s = "ALA MA BOCIANA" i t = "MA", to zachodzi s[4 : 6] = t (zwróć uwagę, iż znak s[6] nie jest częścią
podłańcucha s[4...6]).

Prefiks (ang. prefix) łańcucha s jest łańcuchem zbudowanym z k początkowych znaków:

Pref(s) = s[0 : k]

Sufiks (ang. suffix) łańcucha s jest łańcuchem zbudowanym z k końcowych znaków:

Suff(s) = s[n - k : n]

s[0 : n] s[0 : n]
Pref(s) Suff(s)
s[0 : k] s[n-k : n]

Prefiks i sufiks mogą być puste, tzn. mogą nie zawierać żadnego znaku.

Mówimy, że prefiks lub sufiks jest właściwy (ang. proper), jeśli nie obejmuje całego łańcucha s.

Maksymalny prefiks właściwy (ang. maximal proper prefix) obejmuje wszystkie znaki łańcucha s za wyjątkiem ostatniego.
Podobnie maksymalny sufiks właściwy (ang. maximal proper sufix) obejmuje wszystkie znaki łańcucha za wyjątkiem
pierwszego:

max Pref(s) = s[0 : |s| - 1]


max Suff(s) = s[1 : |s| ]

Jeśli istnieje prefiks s, który jest równy sufiksowi s, to mówimy, iż tworzą one tzw. prefikso-sufiks (ang. border):

s
Pref(s) Suff(s)
Pref(s) = Suff(s) = Border(s)

Uwaga: prefiks i sufiks w powyższym układzie mogą się wzajemnie częściowo pokrywać (a nie sugeruje tego rysunek). Na
przykład maksymalny prefikso-sufiks dla tekstu:

ababab
to

ababab
ababab
Dwie środkowe litery ab pokrywają się w prefiksie i sufiksie. Przez maksymalny prefikso-sufiks łańcucha s rozumiemy najdłuższy,
właściwy prefiks i sufiks s, które są sobie równe.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0044.php 294 / 519


Algorytmy i Struktury Danych - Podstawowe pojęcia tekstowe 2014-10-03

Jeśli dany łańcuch s posiada prefikso-sufiks o długości k, to okresem (ang. period) nazywamy taką liczbę całkowitą d, że
zachodzi warunek:

s[0 : k] = s[d : n]

Graficznie wygląda to tak:

Border(s) s Border(s)
Border(s) s Border(s)
←d→

Okres można bardzo prosto obliczyć wg wzoru:

d = |s| - | Border(s) |

Maksymalny prefikso-sufiks łańcucha s oznacza najdłuższy prefiks i sufiks tego łańcucha, które są sobie równe.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0044.php 295 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Podstawowe operacje na łańcuchach znakowych


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Deklaracja zmiennych znakowych
Podstawowe pojęcia dotyczące przetwarzania tekstów Kod znaku
Podstawowe operacje na łańcuchach znakowych Konkatencja – łączenie łańcuchów
Naiwne wyszukiwanie wzorca w tekście Wstawianie znaku do łańcucha
Wyszukiwanie maksymalnego prefikso-sufiksu Usuwanie znaku z łańcucha
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta Zastępowanie fragmentu łańcucha innym
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta Porównywanie łańcuchów
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a Pozostałe operacje tekstowe
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach

Deklaracja zmiennych znakowych i dostęp do przechowywanych znaków


We współczesnych językach programowania znaki są podstawowym typem danych. W pamięci komputera znak jest
przechowywany w postaci liczby, którą nazywamy kodem znaku (ang. character code). Każdy znak posiada swój własny kod.
Aby różne urządzenia systemu komputerowego mogły w ten sam sposób interpretować kody znaków, opracowano kilka
standardów kodowania liter. Poniżej przedstawiamy wybrane dwa:

ASCII – American Standard Code for Information Interchange – Amerykański Standardowy Kod do Wymiany Informacji.

Znaki są zapamiętywane w postaci 8 bitowych kodów (pierwotnie było to 7 bitów, lecz później standard ASCII został
poszerzony na 8 bitów, w których znalazły się różne znaki narodowe). Taki sposób reprezentacji znaków jest dzisiaj
bardzo wygodny, ponieważ podstawowa komórka pamięci komputera IBM przechowuje właśnie 8 bitów. Dzięki temu
znaki dobrze mieszczą się w pamięci.
8-bitowy kod pozwala przedstawić 256 różnych wartości i tylko tyle może być zdefiniowane znaków w kodzie ASCII.
Pierwsza połówka zbioru kodów – od 0 do 127 – jest zdefiniowana na stałe i raczej nigdy nie jest modyfikowana. Jest
to tzw. podstawowy zestaw znaków ASCII. Druga połówka – od 128 do 255 – zawiera znaki narodowe, które w różny
sposób mogą być przydzielane rozszerzonym kodom ASCII. Z tego właśnie powodu powstały różne strony kodowe.
Na przykład konsola znakowa stosuje kodowanie LATIN II. Natomiast system Windows stosuje Windows 1250.
Niestety, w obu systemach polskie literki posiadają różne kody. Dlatego wyświetlenie przygotowanego w Windows
polskiego tekstu w konsoli znakowej powoduje, iż polskie znaki Windows 1250 zostają źle zinterpretowane w konsoli
LATIN II.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 296 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

Poniższy program demonstruje niekompatybilność kodowania znaków w Windows ze znakami wyświetlanymi w


oknie konsoli znakowej.

Lazarus Code::Blocks Free Basic

program prg; #include <iostream> Print "ĄąĆćĘꣳŃńÓ󌜏źŻż"


End
begin using namespace std;
writeln('ĄąĆćĘꣳŃńÓ󌜏źŻż');
end. int main()
{
cout << "ĄąĆćĘꣳŃńÓ󌜏źŻż\n";
return 0;
}

ĄąĆćĘꣳŃńÓ󌜏źŻż ą╣ĂŠ╩ŕú│нˡîťĆč»┐ ą╣ĂŠ╩ŕú│нˡîťĆč»┐

Unicode
Znaki są zapamiętywane w postaci kodów 16-bitowych. Dzięki temu rozwiązaniu liczba możliwych do przedstawienia
znaków rośnie do 65536. Pierwsze 256 kodów jest zwykle kompatybilne z kodami ASCII. Kody powyżej 256 tworzą
banki znaków, w których znajdują się wszystkie znaki narodowe, arabskie, hebrajskie, matematyczne itp.

Poniższa tabelka prezentuje nazwy typów znakowych w wybranych przez nas językach programowania:

Lazarus Code::Blocks Free Basic

ASCII char unsigned char String * 1

widechar
Unicode wchar
wchar_t Wstring * 1

W języku Free Basic nie ma prostego typu znakowego. W tym charakterze używamy łańcucha znakowego o stałej długości 1, o czym piszemy dalej.

Zmienne znakowe deklarujemy w identyczny sposób jak zmienne innych typów:

Lazarus Code::Blocks Free Basic

... ... ...


Deklaracja var char c; Dim c As String * 1
zmiennej c : char; wchar_t wc; Dim wc As Wstring * 1
znakowej wc : wchar; ... ...
...

Tak zadeklarowana zmienna c może przechowywać jeden znak ASCII, a zmienna wc jeden znak Unicode.
Zmienne znakowe mogą również być zadeklarowane jako tablice znaków.

Lazarus Code::Blocks Free Basic

... ... ...


Deklaracja var char c[100]; Dim c As String * 100
tablicy c : array[0..99] of char; wchar_t wc[100]; Dim wc As Wstring * 100
znakowej wc : array[0..99] of wchar; ... ...
...

W przypadku tablicy znakowej mamy dostęp do poszczególnych znaków za pomocą indeksu w klamerkach kwadratowych.
Wyjątkiem jest język Basic, gdzie zmienna znakowa jest traktowana jako spójna całość i dostęp do poszczególnych znaków
uzyskujemy poprzez polecenie Mid (przy zapisie do zmiennej) oraz funkcję Mid (przy odczycie ze zmiennej). Pierwszy znak
posiada zawsze indeks równy 1.

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 297 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

Program tworzy trzyznakową tablicę i wpisuje do niej wyraz ALO. Następnie literki są wypisywane w kierunku
odwrotnym:

Lazarus Code::Blocks Free Basic

program prg; #include <iostream> Dim s As String * 3


var using namespace std; Mid(s,1) = "A"
s : array[0..2] of char; Mid(s,2) = "L"
int main() Mid(s,3) = "O"
begin { Print Mid(s,3,1);
s[0] := 'A'; unsigned char s[3]; Print Mid(s,2,1);
s[1] := 'L'; Print Mid(s,1,1)
s[2] := 'O'; s[0] = 'A'; End
writeln(s[2],s[1],s[0]); s[1] = 'L';
end. s[2] = 'O';
cout << s[2] << s[1] << s[0]
<< endl << endl;
return 0;
}

Wynik
OLA

Oprócz zwykłych tablic znakowych (ang. character tables), języki Pascal, C++ oraz Basic udostępniają tzw. łańcuchy
znakowe (ang. character strings). Są to tablice dynamiczne, które mogą przechowywać ciągi znaków o różnych długościach
(łańcuchy automatycznie dopasowują się do rozmiaru przechowywanego tekstu – tablice znakowe natomiast nie posiadają takich
cech, programista musi o to zadbać sam):

Lazarus Code::Blocks Free Basic

łańcuch ASCII ansistring string String

łańcuch Unicode widestring wstring Wstring Ptr

Koniec łańcucha znakowego znaczony jest kodem 0. Znak o tym kodzie nie jest wliczany do łańcucha. Również nie powinieneś
tego znaku umieszczać wewnątrz łańcucha, gdyż może to spowodować nieprawidłowe działanie wielu funkcji i procedur
tekstowych.
W języku Pascal i Basic indeksy znaków w łańcuchu rozpoczynają się od 1, a w języku C++ od 0. W algorytmach tekstowych
musimy wziąć na to poprawkę.
W języku Basic łańcuchy Wstring są wskaźnikami do obszaru pamięci przechowującego właściwe znaki. Dlatego do wskaźnika
musi być przypisywany adres zarezerwowanego obszaru, w którym będą umieszczane znaki Unicode. Dostęp do danych
następuje poprzez operator *, podobnie jak w języku C++.
Liczbę znaków przechowywanych w łańcuchu tekstowym otrzymamy przy pomocy następujących funkcji:

Lazarus Code::Blocks Free Basic

Długość łańcucha length(s) s.length() Len(s)


s.size()

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Programy demonstrują sposoby deklarowania zmiennej łańcuchowej oraz dostępu do znaków zawartych w łańcuchu.

Lazarus Code::Blocks Free Basic

program prg; #include <iostream> Dim s As String


#include <string> Dim i As Integer
var
s : ansistring; using namespace std; s = "Hello there!!!"
i : integer; For i = Len(s) To 1 Step - 1
int main() Print Mid(s,i,1);
begin { Next
s := 'Hello there!!!'; string s; Print: Print
for i := length(s) downto 1 do s = "Hello there!!!"; End
write(s[i]); for(int i = s.length()-1; i >= 0; i--)
writeln; writeln; cout << s[i];
end. cout << endl << endl;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 298 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03
end. cout << endl << endl;
return 0;
}

program prg; #include <iostream> Dim s As Wstring Ptr


#include <string> Dim i As Integer
var
s : widestring; using namespace std; s = Allocate(20 * Len(Wstring))
i : integer; *s = "Hello there!!!"
int main() For i = Len(*s) To 1 Step - 1
begin { Print Mid(*s,i,1);
s := 'Hello there!!!'; wstring s; Next
for i := length(s) downto 1 do s = L"Hello there!!!"; Print: Print
write(s[i]); for(int i = s.length()-1; i >= 0; i--) End
writeln; writeln; cout << (char)s[i];
end. cout << endl << endl;
return 0;
}

Wynik
!!!ereht olleH

Kod znaku
Przy przetwarzaniu tekstu często musimy odczytywać kody znaków zawartych w zmiennej znakowej lub zamieniać kody na
odpowiadające im znaki – na przykład w celu umieszczenia ich w tekście. W każdym z wybranych przez nas języków
programowania istnieją odpowiednie do tego zadania narzędzia.

Lazarus Code::Blocks Free Basic

dostęp do kodu znaku ord(znak) (int)znak Asc(znak)

zamiana kodu na znak chr(kod) (char) kod Chr(kod)

Język C++ traktuje znaki jak liczby całkowite (unsigned char – bez znaku, char – ze znakiem). Nie ma zatem zwykle potrzeby
dokonywać konwersji znakowych. Wyjątek stanowi przesyłanie znaków do strumieni – musimy dokonać konwersji kodu, aby w
strumieniu został zapisany znak, a nie jego kod jako liczba całkowita.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje z klawiatury łańcuch znaków do zmiennej łańcuchowej, a następnie wypisuje kolejne literki wraz
z ich kodami ASCII.

Lazarus

program prg;
var
s : ansistring;
i : integer;
begin
readln(s);
writeln;
for i := 1 to length(s) do
writeln(s[i],' : ',ord(s[i]):3);
writeln;
end.

Code::Blocks

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main()
{
string s;
getline(cin,s);
cout << endl;
for(unsigned i = 0; i < s.length(); i++)
cout << s[i] << " : " << setw(3) << (int)s[i] << endl;
cout << endl;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 299 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

return 0;
}

Free Basic

Dim s As String, i As Integer


Input s
Print
For i = 1 To Len(s)
Print Using "! : ###"; Mid(s,i,1); Asc(Mid(s,i,1))
Next
Print
End

Wynik
Aligator Arek
A : 65
l : 108
i : 105
g : 103
a : 97
t : 116
o : 111
r : 114
: 32
A : 65
r : 114
e : 101
k : 107

Konkatencja – łączenie łańcuchów


Często zdarza się, iż chcemy połączyć dwa lub więcej tekstów w jeden tekst. Operacja łączenia tekstu nosi nazwę konkatencji
(ang. concatenation). W przypadku łańcuchów jest to bardzo proste:

Lazarus Code::Blocks Free Basic


Łączymy łańcuch s1 z s2
i wynik połączenia s3 := s1 + s2; s3 = s1 + s2; s3 = s1 + s2
umieszczamy w s3

Wstawianie znaku/ciągu znaków do łańcucha


Podmiana znaku w łańcuchu jest operacją prostą. Po prostu odwołujemy się do wybranego elementu w zmiennej łańcuchowej –
może nią być również tablica znaków – i zapisujemy go nową zawartością:

Lazarus Code::Blocks Free Basic


Zamiana znaku s[i] := 'znak'; s[i] = 'znak'; Mid(s,i,1) = "znak"
na pozycji i-tej s[i] := char(kod); s[i] = kod Mid(s,i,1) = Chr(kod)
w łańcuchu s

Wstawienie znaku wymaga przesunięcia części znaków w zmiennej łańcuchowej, aby udostępnić miejsce na wstawiany znak.
Operacja wstawiania znaku lub łańcucha znaków jest obsługiwana przez funkcje biblioteczne:

Lazarus Code::Blocks Free Basic


Wstawiamy
łańcuch s1
na pozycję insert(s1,s2,i); s2.insert(i,s1); s2 = Left(s2,i-1) + s1 + Right(s2,Len(s2)-i+1)
i-tą
w łańcuchu
s2

Język FreeBasic nie posiada bezpośredniej funkcji wstawiania znaku lub łańcucha do innego łańcucha. Dlatego posiłkujemy się
dwoma funkcjami pomocniczymi:

Left(s,i) – zwraca i pierwszych znaków łańcucha s. Jeśli i = 0, to zwraca łańcuch pusty.


Right(s,i) – zwraca i ostatnich znaków łańcucha s. Jeśli i = 0, to zwraca łańcuch pusty.

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w łańcuchu tekstowym zdanie "Rudy lisek", a następnie wstawia łańcuch ", szybki" po słowie

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 300 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

"Rudy".

Lazarus

program prg;
var
s : ansistring;
begin
s := 'Rudy lisek';
writeln(s);
insert(', szybki',s,5);
writeln(s);
writeln;
end.

Code::Blocks

#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
s = "Rudy lisek";
cout << s << endl;
s.insert(4,", szybki");
cout << s << endl << endl;
return 0;
}

Free Basic

Dim s As String
s = "Rudy lisek"
Print s
s = Left(s,4) + ", szybki" + Right(s,6)
Print s
Print
End

Wynik
Rudy lisek
Rudy, szybki lisek

Usuwanie znaku z łańcucha


Usunięcie znaku z łańcucha/tablicy polega na przesunięciu wszystkich znaków następujących za znakiem usuwanym o jedną
pozycję w lewo. W ten sposób znak zostaje nadpisany znakiem sąsiadującym z prawej strony – w efekcie zniknie on z łańcucha.
Dla łańcuchów znakowych mamy w każdym z wybranych języków programowania gotowe funkcje usuwania znaku lub fragmentu
łańcucha.

Lazarus Code::Blocks Free Basic


Usuwamy z
łańcucha s
n znaków delete(s,i,n); s.erase(i,n); s = Left(s,i-1) + Right(s,Len(s)-i-n+1)
począwszy
od pozycji i-tej

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w łańcuchu tekstowym zdanie "Rakieta kosmiczna", a następnie usuwa z wyrazu "kosmiczna" literkę 's'.

Lazarus Code::Blocks Free Basic

program prg; #include <iostream> Dim s As String


#include <string>
var s = "Rakieta kosmiczna"
s : ansistring; using namespace std; Print s
begin s = Left(s,10) + Right(s,6)
s := 'Rakieta kosmiczna'; int main() Print s
writeln(s); { End
delete(s,11,1); string s;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 301 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

writeln(s);
writeln; s = "Rakieta kosmiczna";
end. cout << s << endl;
s.erase(10,1);
cout << s << endl << endl;
return 0;
}

Wynik
Rakieta kosmiczna
Rakieta komiczna

Zastępowanie fragmentu łańcucha innym łańcuchem tekstowym


Zastępując fragment łańcucha innym łańcuchem możemy wykorzystać funkcje usuwania i wstawiania tekstu – najpierw usuwamy
zastępowany fragment z łańcucha, a następnie wstawiamy na jego miejsce nowy fragment. W języku C++ możemy wykorzystać
funkcję składową replace() klasy string, która wykonuje dokładnie to samo zadanie. W języku FreeBasic wykorzystujemy funkcje
Left() i Right().

Lazarus Code::Blocks Free Basic


n znaków od
pozycji i-tej
w łańcuchu
delete(s2,i,n);
s2 insert(s1,s2,i);
s2.replace(i,n,s1); s2 = Left(s2,i-1) + s1 + Right(s2,Len(s2)-i-n+1)
zastępujemy
łańcuchem
s1

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program umieszcza w łańcuchu tekstowym zdanie "Zielone, stare drzewko", a następnie wymienia wyraz "stare" na
"wysokie".

Lazarus Code::Blocks Free Basic

program prg; #include <iostream> Dim s As String


#include <string>
var s = "Zielone, stare drzewko"
s : ansistring; using namespace std; Print s
begin s = Left(s,9) + "wysokie" + Right(s,8)
s := 'Zielone, stare drzewko'; int main() Print s
writeln(s); { End
delete(s,10,5); string s;
insert('wysokie',s,10);
writeln(s); s = "Zielone, stare drzewko";
writeln; cout << s << endl;
end. s.replace(9,5,"wysokie");
cout << s << endl << endl;
return 0;
}

Wynik
Zielone, stare drzewko
Zielone, wysokie drzewko

Porównywanie łańcuchów
Łańcuchy tekstowe możemy porównywać przy pomocy typowych operatorów porównań. Jednakże obowiązuje tutaj kilka zasad.
Dwa łańcuchy są równe, jeśli składają się z takiej samej liczby znaków oraz zgadzają się ze sobą na każdej pozycji znakowej.
Jeśli dwa łańcuchy mają różną długość, lecz krótszy łańcuch zawiera te same początkowe znaki c łańcuch dłuższy, to krótszy
jest mniejszy, a dłuższy jest większy.
W dwóch łańcuchach porównywane są znaki na odpowiadających sobie pozycjach znakowych aż do napotkania niezgodności
kodów. Wtedy mniejszy łańcuch jest tym, który posiada na porównywanej pozycji znak o mniejszym kodzie. Na przykład:
"ALA" > "AKACJA" – kod literki L jest większy od kodu literki K.
Taki sposób porównywania nosi nazwę leksykograficznego. Zwróć uwagę, iż w ten sposób nie można porównywać łańcuchów
zawierających polskie litery – poprawny będzie jedynie test na równość łańcuchów.

Pozostałe operacje tekstowe


W poniższej tabelce zebraliśmy często wykonywane, typowe operacje na tekstach. Z operacji tych będziemy intensywnie
korzystać w przykładach programowych do omawianych algorytmów tekstowych.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 302 / 519


Algorytmy i Struktury Danych - Podstawowe operacje na łańcuchach znakowych 2014-10-03

Lazarus Code::Blocks Free Basic


Odczyt
wiersza
znaków readln(s); getline(cin,s); Line Input s
ze write('opis'); readln(s) cout << "opis"; getline(cin,s) Line Input "opis";s
standardowego
wejścia
Zapis łańcucha
znaków
writeln(s); cout << s << endl; Print s
na write(s); cout << s; Print s;
standardowe
wyjście

Sprawdzenie, if(s == "") ...


czy łańcuch if s = '' then ... if(!s.length())... If s2 = "" Then ...
if length(s) = 0 then ... If Len(s2) = 0 Then ...
jest pusty if(!s.size())...

Kopiowanie n
znaków
łańcucha s1 s2 = copy(s1,i,n); s2 = s1.substr(i,n); s2 = Mid(s1,i,n)
od pozycji i-tej
do łańcucha
s2
Kopiowanie n
początkowych
znaków s2 = copy(s1,1,n); s2 = s1.substr(0,n); s2 = Left(s1,n)
łańcucha s1
do łańcucha
s2
Kopiowanie n
końcowych
znaków s2 = copy(s1,length(s1)-n+1,n); s2 = s1.substr(s1.length()-n); s2 = Right(s1,n)
łańcucha s1
do łańcucha
s2

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0045.php 303 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Naiwne wyszukiwanie wzorca w tekście


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe

Problem
W łańcuchu znakowym s znaleźć wszystkie wystąpienia wzorca p.

Rozwiązanie nr 1
Problem Wyszukiwania Wzorca – WW (ang. pattern matching) to jeden z podstawowych problemów tekstowych, który
intensywnie badali wybitni informatycy. Rozwiązaniem jest wskazanie w ciągu s wszystkich pozycji i takich, że zachodzi równość:

s[i : i + |p|] = p

Oznacza to, iż wzorzec p jest fragmentem łańcucha s występującym na pozycji i-tej.

Algorytm N – naiwny – ustawia okno o długości wzorca p na pierwszej pozycji w łańcuchu s. Następnie sprawdza,
czy zawartość tego okna jest równa wzorcowi p. Jeśli tak, pozycja okna jest zwracana jako wynik, po czym okno
przesuwa się o jedną pozycję w prawo i cała procedura powtarza się. Algorytm kończymy, gdy okno wyjdzie poza
koniec łańcucha. Klasa pesymistycznej złożoności obliczeniowej algorytmu N jest równa O(n × m), gdzie n oznacza

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 304 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

liczbę znaków tekstu, a m liczbę znaków wzorca. Jednakże w typowych warunkach algorytm pracuje w czasie O(n),
ponieważ zwykle wystarczy porównanie kilku początkowych znaków okna z wzorcem, aby stwierdzić, iż są one
niezgodne.

Algorytm naiwny wyszukiwania wzorca w łańcuchu tekstowym


Wejście
s – łańcuch znakowy
p – łańcuch wzorca
Wyjście:
Wszystkie pozycje wzorca p w łańcuchu s.
Elementy pomocnicze:
i – pozycja okna, i N
n – długość łańcucha s, n N
m – długość wzorca p, m N

Lista kroków:
K01: n ← |s| ; obliczamy długość łańcucha s
K02: m ← |p| ; obliczamy długość wzorca p
K03: Dla i = 0,1,... n - m wykonuj K04
K04: Jeśli p = s[i : i + m], to pisz i ; okno zawiera wzorzec?
K05: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A, B i C. Następnie losuje 3
literowy wzorzec z tych samych liter i algorytmem naiwnym wyszukuje wszystkie wystąpienia wzorca w łańcuchu.
Pozycję wzorca zaznacza znakiem ^.

Lazarus

// Algorytm WWN
// Data: 29.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s,p : ansistring;
i : integer;
begin
randomize;
// generujemy łańcuch
s := '';
for i := 1 to 80 do s := s + chr(65 + random(3));
// generujemy wzorzec
p := '';
for i := 1 to 3 do p := p + chr(65 + random(3));
// wypisujemy wzorzec
writeln(p);
// wypisujemy łańcuch
write(s);
// szukamy wzorca w łańcuchu
for i := 1 to 78 do
if p = copy(s,i,3) then write('^') else write(' ');

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 305 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

writeln;
writeln;
end.

Code::Blocks

// Algorytm WWN
// Data: 29.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s,p;
int i;
srand((unsigned)time(NULL));
// generujemy łańcuch
s = "";
for(i = 0; i < 80; i++) s += char(65 + (rand() % 3));
// generujemy wzorzec
p = "";
for(i = 0; i < 3; i++) p += char(65 + (rand() % 3));
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch
cout << s;
// szukamy wzorca w łańcuchu
for(i = 0; i < 78; i++)
cout << (p == s.substr(i,3) ? "^" : " ");
cout << endl << endl;
return 0;
}

Free Basic

' Algorytm WWN


' Data: 29.05.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,p
Dim As Integer i
Randomize
' generujemy łańcuch
s = ""
For i = 1 To 80: s += Chr(65 + Cint(Rnd * 2)): Next
' generujemy wzorzec
p = ""
For i = 1 To 3: p += Chr(65 + Cint(Rnd * 2)): Next
' wypisujemy wzorzec
Print p
' wypisujemy łańcuch
Print s;
' szukamy wzorca w łańcuchu
For i = 1 To 78

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 306 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

If p = Mid(s,i,3) Then Print "^";: Else Print " ";


Next
Print: Print
End

Wynik
CBC
AACBBAACCACBCCBABBBBABACAABAAACABCABBCBAAAAAAACCAAACBBAACABCBCCABCBCCBCBCAABACAC
^ ^ ^ ^ ^

Wyszukiwanie wzorca algorytmem N


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Algorytm N możemy nieco usprawnić wykorzystując ideę wartowników. Na końcu łańcucha oraz wzorca umieszczamy dwa różne
znaki. Zapobiegną one wyjściu poza obszar łańcucha przy testowaniu znaków w oknie i we wzorcu. Dzięki nim odpadnie
sprawdzanie zakresu indeksów w oknie – algorytm wykona mniej operacji.

Algorytm naiwny wyszukiwania wzorca z wartownikami


Wejście
s – łańcuch znakowy
p – łańcuch wzorca
Wyjście:
Wszystkie pozycje wzorca p w łańcuchu s.
Elementy pomocnicze:
i – pozycja okna w łańcuchu s, i N
j – pozycja wewnątrz okna, j N
n – długość łańcucha s, n N
m – długość wzorca p, m N

Lista kroków:
K01: n ← |s| ; obliczamy długość łańcucha s
K02: m ← |p| ; obliczamy długość wzorca p
K03: s ← s + 'X' ; na końcu łańcucha umieszczamy wartownika
K04: p ← p + 'Y' ; na końcu wzorca umieszczamy innego wartownika
K05: Dla i = 0,1,...,n - m wykonuj K06...K08 ; pozycje okna
K06: j ← 0 ; pozycja w oknie i we wzorcu
K07: Dopóki s[i + j] = p[j], wykonuj j ← j + 1 ; szukamy pierwszego różnego znaku okna i wzorca
K08: Jeśli j = m, to pisz i ; sprawdzamy, czy cały wzorzec wystąpił w oknie
K09: Usuń ostatni znak z s ; pozbywamy się wartownika z łańcucha
K10: Usuń ostatni znak z p ; pozbywamy się wartownika ze wzorca
K11: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A, B i C. Następnie losuje 3
literowy wzorzec z tych samych liter i algorytmem naiwnym wyszukuje wszystkie wystąpienia wzorca w łańcuchu.
Pozycję wzorca zaznacza znakiem ^.

Lazarus

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 307 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

// Algorytm WWN z wartownikami


// Data: 30.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s,p : ansistring;
i,j : integer;
begin
randomize;
// generujemy łańcuch
s := '';
for i := 1 to 80 do s := s + chr(65 + random(3));
// generujemy wzorzec
p := '';
for i := 1 to 3 do p := p + chr(65 + random(3));
// wypisujemy wzorzec
writeln(p);
// wypisujemy łańcuch
write(s);
// dodajemy wartowników
s := s + 'X'; p := p + 'Y';
// szukamy wzorca w łańcuchu
for i := 1 to 78 do
begin
j := 0;
while s[i + j] = p[j + 1] do inc(j);
if j = 3 then write('^') else write(' ');
end;
writeln;
writeln;
end.

Code::Blocks

// Algorytm WWN z wartownikami


// Data: 30.05.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s,p;
int i,j;
srand((unsigned)time(NULL));
// generujemy łańcuch
s = "";
for(i = 0; i < 80; i++) s += char(65 + (rand() % 3));
// generujemy wzorzec
p = "";
for(i = 0; i < 3; i++) p += char(65 + (rand() % 3));
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch
cout << s;
// dodajemy wartowników

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 308 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

s += "X"; p += "Y";
// szukamy wzorca w łańcuchu
for(i = 0; i < 78; i++)
{
for(j = 0; s[i + j] == p[j]; j++) ;
cout << (j == 3 ? "^" : " ");
}
cout << endl << endl;
return 0;
}

Free Basic

' Algorytm WWN z wartownikami


' Data: 30.05.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,p
Dim As Integer i,j
Randomize
' generujemy łańcuch
s = ""
For i = 1 To 80: s += Chr(65 + Cint(Rnd * 2)): Next
' generujemy wzorzec
p = ""
For i = 1 To 3: p += Chr(65 + Cint(Rnd * 2)): Next
' wypisujemy wzorzec
Print p
' wypisujemy łańcuch
Print s;
' dodajemy wartowników
s += "X": p += "Y"
' szukamy wzorca w łańcuchu
For i = 1 To 78
j = 0
While Mid(s,i + j,1) = Mid(p,j + 1,1): j += 1: Wend
If j = 3 Then Print "^";: Else Print " ";
Next
Print: Print
End

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 309 / 519


Algorytmy i Struktury Danych - Naiwne wyszukiwanie wzorca 2014-10-03

Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0046.php 310 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie maksymalnego prefikso-sufiksu


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Liczby Fibonacciego
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
Dla danego łańcucha s wyznaczyć maksymalny prefikso-sufiks.

Zadanie wyszukania w łańcuchu s maksymalnego prefikso-sufiksu sprowadza się do znalezienia najdłuższego prefiksu, który
jednocześnie jest sufiksem łańcucha s.

Rozwiązanie nr 1
Pierwsze rozwiązanie uzyskamy wykorzystując bezpośrednio definicję prefikso-sufiksu. Rozpoczynamy od maksymalnego
prefiksu właściwego. Następnie porównujemy kolejne, coraz mniejsze prefiksy z sufiksami aż do uzyskania zgodności lub do
otrzymania pustego prefiksu. W obu przypadkach zwracamy długość prefiksu, na którym zakończyliśmy porównywanie.
Algorytm posiada złożoność O(n2), jednakże dla typowych tekstów pracuje w czasie prawie liniowym O(n).

Algorytm naiwny wyszukiwania maksymalnego prefikso-sufiksu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 311 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

Wejście
s – łańcuch znakowy
Wyjście:
długość maksymalnego prefikso-sufiksu łańcucha s.
Elementy pomocnicze:
i – długość prefiksu łańcucha s, i N
n – długość łańcucha s, n N

Lista kroków:
K01: n ← |s| ; obliczamy długość łańcucha s
K02: i ← n - 1 ; rozpoczynamy od maksymalnego prefiksu
K03: Dopóki i > 0 wykonuj K04...K05 ; szukamy maksymalnego prefikso-sufiksu
K04: Jeśli s[0 : i] = s[n - i : n], to idź do K06 ; sprawdzamy, czy prefiks jest równy sufiksowi
K05: i ← i - 1 ; następny, mniejszy prefiks
K06: Zakończ z wynikiem i

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 40 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B. Następnie wyznacza w
tym łańcuchu maksymalny prefikso-sufiks i podaje jego długość.

Lazarus

// Wyznaczanie maksymalnego PS
// Data: 1.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : ansistring;
i,n : integer;
begin
randomize;
// generujemy łańcuch
s := '';
for i := 1 to 40 do s := s + chr(65 + random(2));
// wypisujemy łańcuch
writeln(s);
// szukamy prefikso-sufiksu
n := length(s);
i := n - 1;
while i > 0 do
begin
if copy(s,1,i) = copy(s,n - i + 1,i) then break;
dec(i);
end;
writeln(i);
writeln;
end.

Code::Blocks

// Wyznaczanie maksymalnego PS
// Data: 1.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 312 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s;
int i,n;
srand((unsigned)time(NULL));
// generujemy łańcuch
s = "";
for(i = 0; i < 40; i++) s += 65 + rand() % 2;
// wypisujemy łańcuch
cout << s << endl;
// szukamy prefikso-sufiksu
n = s.length();
for(i = n - 1; i > 0; i--) if(s.substr(0,i) == s.substr(n - i,i)) break;
cout << i << endl << endl;
return 0;
}

Free Basic

' Wyznaczanie maksymalnego PS


' Data: 1.06.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim s As String
Dim As Integer i,n
Randomize
' generujemy łańcuch
s = ""
For i = 1 To 40: s += Chr(65 + Cint(Rnd * 1)): Next
' wypisujemy łańcuch
Print s
' szukamy prefikso-sufiksu
n = Len(s)
i = n - 1
While i > 0
If Mid(s,1,i) = Mid(s,n - i + 1,i) Then Exit While
i -= 1
Wend
Print i
Print
End

Wynik
ABBAAABBBAAAAABBABAABAABABBBABABBABBABBA
4

Wyszukiwanie maksymalnego prefikso-sufiksu


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Podany w rozwiązaniu nr 1 algorytm można znacząco ulepszyć wykorzystując programowanie dynamiczne oraz proste własności
prefikso-sufiksów. Postaraj się dokładnie zrozumieć podane poniżej informacje – najlepiej tę partię rozdziału przeczytaj
kilkakrotnie. Przyda ci się to przy algorytmach MP i KMP.
Własność 1 – rozszerzenie prefikso-sufiksu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 313 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

Jeśli maksymalny prefiks właściwy łańcucha tekstowego s posiada prefikso-sufiks o długości k znaków:

to powiemy, iż prefikso-sufiks prefiksu jest rozszerzalny do prefikso-sufiksu całego łańcucha s, jeśli znak s[k] (czyli
znak leżący tuż za prefiksem należącym do prefikso-sufiksu) jest równy znakowi s[n-1] (czyli znakowi leżącemu tuż
za sufiksem należącym do prefikso-sufiksu). Wtedy długość prefikso-sufiksu zwiększa się do k + 1 i staje się on
prefikso-sufiksem łańcucha s.

Własność 2 – redukcja prefikso-sufiksu


Jeśli łańcuch tekstowy s posiada prefikso-sufiks, a ten prefikso-sufiks również posiada swój własny prefikso-sufiks,
to jest on także prefikso-sufiksem łańcucha s. Brzmi zawile, lecz uzasadnienie jest bardzo proste:

Łańcuch tekstowy s posiada prefikso-sufiks b:

Prefikso-sufiks b posiada swój własny prefikso-sufiks bb:

Ponieważ b jest jednocześnie prefiksem i sufiksem łańcucha s:

to jego prefikso-sufiks bb jest również prefikso-sufiksem łańcucha s:

Co więcej, jeśli prefikso-sufiks b był maksymalnym prefikso-sufiksem łańcucha s, a prefikso-sufiks bb był


maksymalnym prefikso-sufiksem prefikso-sufiksu b, to nowy prefikso-sufiks bb jest poprzednim prefikso-sufiksem
łańcucha s. Pomiędzy prefikso-sufiksem b i bb nie istnieje żaden inny prefikso-sufiks łańcucha s.

Z programowaniem dynamicznym (ang. dynamic programming) zetknęliśmy się już przy okazji wyliczania kolejnych liczb ciągu
Fibonacciego. Polega ono na rozwiązywaniu problemu głównego startując od problemów najprostszych. Rozwiązania

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 314 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

zapamiętujemy i z nich tworzymy rozwiązania problemów na wyższym poziomie. Proces ten kontynuujemy aż do osiągnięcia
rozwiązania problemu docelowego.
Podane powyżej dwie własności pozwalają wyznaczyć pomocniczą tablicę maksymalnych prefikso-sufiksów dla kolejnych
prefiksów łańcucha tekstowego s (pierwsza własność umożliwia rozszerzenie prefikso-sufiksu dla kolejnego prefiksu, jeśli znamy
prefikso-sufiks poprzedniego prefiksu, a druga własność pozwala szukać prefikso-sufiksów, które mogą być rozszerzane, jeśli
bieżącego prefikso-sufiksu nie można rozszerzyć). Tablica ta nosi zwykle nazwę Π (niekiedy spotyka się nazwę MPNext – od
nazwisk twórców algorytmu – Morrisa i Pratta). Indeks w tablicy Π oznacza długość prefiksu łańcucha s, natomiast element o tym
indeksie ma wartość długości maksymalnego prefikso-sufiksu dla danego prefiksu. Na przykład, jeśli Π[6] = 2, to prefiks 6-cio
znakowy łańcucha s posiada prefikso-sufiks maksymalny o długości dwóch znaków.
Jeśli łańcuch s składa się z m znaków, to indeksy tablicy Π mają wartości od 0 do m, Zerowy element tablicy ma zawsze wartość
równą -1 i pełni funkcję wartownika. Nie pozwala on wyjść poza prefiks pusty przy przeglądaniu wstecznym tablicy Π.

Przykład:
Wyznaczymy tablicę Π dla łańcucha ABACABAB.
Lp. Tworzona tablica Π Opis
s : A B A C A B A B Element Π[0] jest zawsze równy -1. Pełni on rolę wartownika, dzięki
1. i : 0 1 2 3 4 5 6 7 8 któremu upraszcza się algorytm wyznaczania kolejnych elementów tablicy.
Π[]:-1 ? ? ? ? ? ? ? ?
s : A B A C A B A B Prefiks jednoznakowy A posiada zawsze prefikso-sufiks pusty, ponieważ
2. i : 0 1 2 3 4 5 6 7 8 prefikso-sufiks musi się składać z prefiksu i sufiksu właściwego. Zatem
Π[]:-1 0 ? ? ? ? ? ? ? Π[1] = 0.
s : A B A C A B A B Prefikso-sufiks prefiksu dwuznakowego AB ma szerokość 0. Π[2] = 0.
3. i : 0 1 2 3 4 5 6 7 8
Π[]:-1 0 0 ? ? ? ? ? ?
s : A B A C A B A B Prefiks trójznakowy ABA ma prefikso-sufiks maksymalny o szerokości 1,
4. i : 0 1 2 3 4 5 6 7 8 który obejmuje pierwszą i ostatnią literkę A. Π[3] = 1.
Π[]:-1 0 0 1 ? ? ? ? ?
s : A B A C A B A B Prefiks czteroznakowy ABAC znów posiada prefikso-sufiks pusty o
5. i : 0 1 2 3 4 5 6 7 8 szerokości 0. Π[4] = 0.
Π[]:-1 0 0 1 0 ? ? ? ?
s : A B A C A B A B Prefiks pięcioznakowy ABACA posiada maksymalny prefikso-sufiks o
6. i : 0 1 2 3 4 5 6 7 8 szerokości 1, obejmujący pierwszą i ostatnią literkę A. Π[5] = 1.
Π[]:-1 0 0 1 0 1 ? ? ?
s : A B A C A B A B Prefikso-sufiks prefiksu sześcioznakowego ABACAB jest rozszerzeniem
7. i : 0 1 2 3 4 5 6 7 8 prefikso-sufiksu poprzedniego prefiksu. Zatem Π[6] = 2.
Π[]:-1 0 0 1 0 1 2 ? ?
s : A B A C A B A B Prefikso-sufiks prefiksu siedmioznakowego ABACABA również jest
8. i : 0 1 2 3 4 5 6 7 8 rozszerzeniem prefikso-sufiksu prefiksu poprzedniego, czyli Π[7] = 3.
Π[]:-1 0 0 1 0 1 2 3 ?
s : A B A C A B A B W tym przypadku prefikso-sufiks niewłaściwego prefiksu wzorca
i : 0 1 2 3 4 5 6 7 8 ABACABAB nie jest rozszerzeniem prefikso-sufiksu prefiksu poprzedniego.
Π[]:-1 0 0 1 0 1 2 3 2 Sprawdzamy zatem, czy nie można rozszerzyć wewnętrznego prefikso-
sufiksu tego prefikso-sufiksu - zaznaczyliśmy go niebieskim kolorem.
Szerokość prefikso-sufiksu wewnętrznego otrzymamy zawsze ze wzoru:

9. szerokość prefikso-sufiksu wewnętrznego = Π[szerokość prefikso-sufiksu


poprzedniego]

Jeśli poprzedni prefiks miał prefikso-sufiks o szerokości 3, to wewnętrzny


prefikso-sufiks ma szerokość: Π[3] = 1. Jak widzimy, ten wewnętrzny
prefikso-sufiks jest rozszerzalny do prefikso-sufiksu o szerokości 2. Zatem
Π[8] = 2.

Ostatni element tablicy Π jest maksymalnym prefikso-sufiksem łańcucha s. Wyznaczenie zawartości tablicy Π dla
łańcucha s zbudowanego z m znaków ma liniową klasę złożoności obliczeniowej równą O(m).

Algorytm Morrisa-Pratta wyszukiwania maksymalnego prefikso-sufiksu


Wejście
s – łańcuch znakowy
Wyjście:
długość maksymalnego prefikso-sufiksu łańcucha s.
Elementy pomocnicze:
Π – tablica długości prefikso-sufiksów kolejnych prefiksów łańcucha s. Indeksy od 0 do |s|.Π C
b – długość prefikso-sufiksu, b C
i – indeks, i N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 315 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

Lista kroków:
K01: Π[0] ← -1 ; na początku tablicy Π wstawiamy wartownika
K02: b ← -1 ; początkowo długość prefikso-sufiksu ustawiamy na -1
; wyznaczamy długości prefikso-sufiksów dla prefiksów
K03: Dla i = 1,2,...,|s| wykonuj K04...K06 łańcucha s
K04: Dopóki b > -1 s[b] ≠ s[i - 1], ; pętla K04 przerywa się w dwóch przypadkach:
wykonuj b ← Π[b] ; - szerokość prefikso-sufiksu b osiągnęła wartownika
; - prefikso-sufiks poprzedniego prefiksu jest rozszerzalny
; w przeciwnym razie prefikso-sufiks jest redukowany do
swojego
; prefikso-sufiksu i pętla się powtarza
K05: b ← b + 1 ; W tym kroku znajdujemy się z wartością b = -1, gdy
prefikso-sufiksu
; nie da się rozszerzyć, lub z b = długości rozszerzalnego
; prefikso-sufiksu poprzedniego prefiksu.
; W obu przypadkach zwiększenie b o 1 daje poprawną
wartość
; wpisu do tablicy Π.
K06: Π[i] ← b ; szerokość prefiksosufiksu prefiksu zapamiętujemy w
tablicy
K07: Zakończ z wynikiem b

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 40 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B. Następnie wyznacza w
tym łańcuchu maksymalny prefikso-sufiks i podaje jego długość.

Lazarus

// Wyznaczanie maksymalnego PS
// Data: 1.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : ansistring;
PI : array[0..40] of integer;
i,b : integer;
begin
randomize;
// generujemy łańcuch
s := '';
for i := 1 to 40 do s := s + chr(65 + random(2));
// wypisujemy łańcuch
writeln(s);
// szukamy prefikso-sufiksu
PI[0] := -1; b := -1;
for i := 1 to 40 do
begin
while (b > -1) and (s[b + 1] <> s[i]) do b := PI[b];
inc(b); PI[i] := b;
end;
writeln(b);
writeln;
end.

Code::Blocks

// Wyznaczanie maksymalnego PS
// Data: 1.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 316 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s;
int PI[41],i,b;
srand((unsigned)time(NULL));
// generujemy łańcuch
s = "";
for(i = 0; i < 40; i++) s += 65 + rand() % 2;
// wypisujemy łańcuch
cout << s << endl;
// szukamy prefikso-sufiksu
PI[0] = b = -1;
for(i = 1; i <= 40; i++)
{
while((b > -1) && (s[b] != s[i - 1])) b = PI[b];
PI[i] = ++b;
}
cout << b << endl << endl;
return 0;
}

Free Basic

' Wyznaczanie maksymalnego PS


' Data: 1.06.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim s As String
Dim As Integer PI(40),i,b
Randomize
' generujemy łańcuch
s = ""
For i = 1 To 40: s += Chr(65 + Cint(Rnd)): Next
' wypisujemy łańcuch
Print s
' szukamy prefikso-sufiksu
PI(0) = -1: b = -1
For i = 1 To 40
While (b > -1) And (Mid(s,b + 1,1) <> Mid(s,i,1)): b = PI(b): Wend
b += 1: PI(i) = b
Next
Print b
Print
End

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 317 / 519


Algorytmy i Struktury Danych - Wyszukiwanie maksymalnego prefikso-sufiksu 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0047.php 318 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.

Zadanie znajdowania wzorca w łańcuchu rozwiązuje algorytm opracowany przez J. H. Morrisa i V. R. Pratta w 1977 roku. Algorytm
działa w czasie liniowym O(n + m), gdzie n jest długością przeszukiwanego łańcucha, a m jest długością poszukiwanego wzorca.
W algorytmie Morrisa-Pratta (w skrócie algorytm MP) wykorzystuje się tablicę Π, której tworzenie opisane jest w rozdziale o
wyszukiwaniu maksymalnego prefikso-sufiksu. Korzysta się przy tym z następującej własności łańcuchów tekstowych:

Załóżmy, iż poszukujemy pierwszego wystąpienie wzorca p w danym łańcuchu tekstowym s. W trakcie


porównywania kolejnych znaków łańcucha ze znakami wzorca natrafiliśmy na sytuację, w której pewien prefiks
wzorca p, o długości b znaków, pasuje do prefiksu okna w łańcuchu s przed pozycją i-tą, jednakże znak s[i]
(oznaczony na rysunku symbolem A) różni się od znaku p[b] (oznaczony symbolem B), który znajduje się we
wzorcu tuż za pasującym prefiksem:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 319 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

Algorytm N w takiej sytuacji przesuwa okno wzorca o jedną pozycję w prawo względem przeszukiwanego tekstu i
rozpoczyna od początku porównywanie znaków wzorca p ze znakami okna nie korzystając zupełnie z faktu
zgodności części znaków. To marnotrawstwo prowadzi w efekcie do klasy złożoności obliczeniowej O(n2).
Tymczasem okazuje się, iż wykorzystując fakt istnienia pasującego prefiksu, możemy pominąć pewne porównania
znaków bez żadnej szkody dla wyniku poszukiwań. Otóż po stwierdzeniu niezgodności okno wzorca przesuwamy
tak, aby przed znakiem s[i] znalazł się maksymalny prefikso-sufiks prefiksu wzorca p:

Dzięki temu podejściu pomijamy niepotrzebne porównania znaków oraz unikamy cofania się indeksu i (przesuwa się
jedynie okno wzorca).
Dla każdego prefiksu wzorca szerokość maksymalnego prefikso-sufiksu można wyznaczyć przed rozpoczęciem
wyszukiwania – do tego właśnie celu generujemy tablicę Π algorytmem MP podanym w poprzednim rozdziale. Dla
danej długości prefiksu b możemy z tej tablicy odczytać szerokość maksymalnego prefikso-sufiksu tego prefiksu. W
naszym przypadku będzie to:

bb = Π[b]

Teraz porównujemy znak wzorca p[bb] (oznaczony na rysunku symbolem C) ze znakiem s[i] (symbol A). Jeśli wciąż
mamy niezgodność, to procedurę powtarzamy, aż do wyczerpania się prefikso-sufiksów – w takim przypadku okno
wzorca oraz indeks i przesuwamy o jedną pozycję w prawo.
Jeśli otrzymamy zgodność, to pasujący prefiks zwiększa swoją długość o 1 znak. Przesuwamy również indeks i o 1,
ponieważ znak na tej pozycji został już całkowicie wykorzystany przez algorytm MP.
Jeśli prefiks obejmie cały wzorzec (b = |s|), to znaleziona zostanie pozycja wzorca w s i będzie ona równa i - b + 1.
W przeciwnym razie poszukiwania kontynuujemy.
Zwróć uwagę, iż algorytm MP nigdy nie cofa indeksu i. Dzięki tej własności algorytm umożliwia przetwarzanie
danych sekwencyjnych – np. wyszukiwanie położenia wzorca w dużym pliku, który fizycznie może nie mieścić się w
pamięci operacyjnej komputera – każdy znak pliku jest odczytywany tylko jeden raz.

Algorytm Morrisa-Pratta wyszukiwania wzorca


Wejście
s – łańcuch znakowy
p – poszukiwany wzorzec
Wyjście:
Kolejne pozycje wystąpienia wzorca w łańcuchu. Wartość -1 nie wskazuje żadnej pozycji wzorca i
oznacza, iż wzorzec nie występuje w przeszukiwanym łańcuchu.
Elementy pomocnicze:
pp – zawiera wyznaczane pozycje wzorca, pp C
i – pozycja porównywanego znaku w łańcuchu tekstowym s, i N, 0 ≤ i ≤ |s|
b – długość prefiksu wzorca p pasującego do prefiksu okna wzorca w łańcuchu s, b N, 0 ≤ b ≤ |p|.
Π tablica o indeksach od 0 do |p|, przechowująca długości maksymalnych prefikso-sufiksów

kolejnych prefiksów wzorca p, Π C.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 320 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

Lista kroków:

K01: Wyznacz tablicę Π ; wyznaczamy tablicę maksymalnych prefikso-


sufiksów
K02: pp ← -1 ; wstępna pozycja wzorca ustawiona na -1
; wstępną długość prefikso-sufiksu ustawiamy na
K03: b ← 0
0
K04: Dla i = 0,1,... |s| - 1, wykonuj K05...K10 ; pętla wyznacza znaki łańcucha do porównania ze
znakami wzorca
K05: Dopóki (b > -1) (p[b] ≠ s[i]), ; Szukamy we wzorcu p rozszerzalnego prefiksu
wykonuj b ← Π[b] pasującego do prefiksu
; okna wzorca. Pętlę K05 przerywamy, jeśli
znajdziemy rozszerzalny
; prefiks lub napotkamy wartownika -1
K06: b ← b + 1 ; Rozszerzamy prefiks o 1 znak. Jeśli prefiks był
wartownikiem,
; to otrzymuje wartość 0.
K07: Jeśli b < |p|, to następny obieg pętli K04 ; Jeśli prefiks nie obejmuje całego wzorca, to
kontynuujemy pętlę K04,
; czyli porównujemy znak p[b] z kolejnym znakiem
łańcucha s.
K08: pp ← i - b + 1 ; wyznaczamy pozycję wzorca p w łańcuchu s
K09: Pisz pp ; wyprowadzamy tę pozycję
K10: b ← Π[b] ; redukujemy b do długości prefikso-sufiksu
wzorca
K11: Jeśli pp = -1, pisz -1 ; jeśli wzorzec p nie występuje w s, wyprowadzamy
-1
K12: Zakończ

Uwaga: Porównaj algorytm MP wyszukiwania wzorca z algorytmem wyznaczania tablicy Π[0] i wyciągnij wnioski.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B oraz 5 znakowy wzorzec
wg tego samego schematu. Następnie program wyznacza tablicę długości maksymalnych prefikso-sufiksów
kolejnych prefiksów wzorca i wykorzystuje ją do znalezienia wszystkich wystąpień wzorca w łańcuchu.

Lazarus

// Wyszukiwanie wzorca algorytmem MP


// Data: 3.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const
N = 80; // długość łańcucha s
M = 5; // długość wzorca p
var
s,p : ansistring;
PI : array[0..M] of integer;
i,b,pp : integer;
begin
randomize;
// generujemy łańcuch s
s := '';
for i := 1 to N do s := s + chr(65 + random(2));
// generujemy wzorzec
p := '';
for i := 1 to M do p := p + chr(65 + random(2));
// dla wzorca obliczamy tablicę PI[]
PI[0] := -1;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 321 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

b := -1;
for i := 1 to M do
begin
while (b > -1) and (p[b + 1] <> p[i]) do b := PI[b];
inc(b); PI[i] := b;
end;
// wypisujemy wzorzec p
writeln(p);
// wypisujemy łańcuch s
write(s);
// poszukujemy pozycji wzorca w łańcuchu
pp := 0; b := 0;
for i := 1 to N do
begin
while (b > -1) and (p[b + 1] <> s[i]) do b := PI[b];
inc(b);
if b = M then
begin
while pp < i - b do
begin
write(' '); inc(pp);
end;
write('^'); inc(pp);
b := PI[b];
end
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie wzorca algorytmem MP


// Data: 3.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80; // długość łańcucha s
const int M = 5; // długość wzorca p
int main()
{
string s,p;
int PI[M + 1],i,b,pp;
srand((unsigned)time(NULL));
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++) s += 65 + rand() % 2;
// generujemy wzorzec
p = "";
for(i = 0; i < M; i++) p += 65 + rand() % 2;
// dla wzorca obliczamy tablicę PI[]
PI[0] = b = -1;
for(i = 1; i <= M; i++)
{
while((b > -1) && (p[b] != p[i - 1])) b = PI[b];
PI[i] = ++b;
}
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch s
cout << s;
// poszukujemy pozycji wzorca w łańcuchu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 322 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

pp = b = 0;
for(i = 0; i < N; i++)
{
while((b > -1) && (p[b] != s[i])) b = PI[b];
if(++b == M)
{
while(pp < i - b + 1)
{
cout << " "; pp++;
}
cout << "^"; pp++;
b = PI[b];
}
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie wzorca algorytmem MP


' Data: 3.06.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 80 ' długość łańcucha s
Const M = 5 ' długość wzorca p
Dim As String s,p
Dim As Integer PI(M),i,b,pp
Randomize
' generujemy łańcuch s
s = ""
For i = 1 To N: s += Chr(65 + Cint(Rnd)): Next
' generujemy wzorzec
p = ""
For i = 1 To M: p += Chr(65 + Cint(Rnd)): Next
' dla wzorca obliczamy tablicę PI[]
PI(0) = -1: b = -1
For i = 1 To M
While (b > -1) And (Mid(p,b + 1,1) <> Mid(p,i,1)): b = PI(b): Wend
b += 1: PI(i) = b
Next
' wypisujemy wzorzec p
Print p
' wypisujemy łańcuch s
Print s;
' poszukujemy pozycji wzorca w łańcuchu
pp = 0: b = 0
For i = 1 To N
While (b > -1) And (Mid(p,b + 1,1) <> Mid(s,i,1)): b = PI(b): Wend
b += 1
If b = M Then
While pp < i - b
Print " ";: pp += 1
Wend
Print "^";: pp += 1
b = PI(b)
End If
Next
Print
End

Wynik
BABBB
AABABBBBABBBBABABAAABBBAABBBABABBABABBAABABBBBBAABAAAAAAABABBBABBBABAABBBBAAAABB
^ ^ ^ ^ ^

Wyszukiwanie wzorca algorytmem MP


(C)2012 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 323 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem MP 2014-10-03

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0048.php 324 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.

Profesor Donald Knuth przeanalizował dokładnie algorytm Morrisa-Pratta i zauważył, iż można go jeszcze ulepszyć. Ulepszenie
t o polega na nieco innym sposobie wyznaczania tablicy Π w stosunku do algorytmu MP. Otóż oryginalnie tablica ta zawiera
maksymalne szerokości prefikso-sufiksów kolejnych prefiksów wzorca. Załóżmy, iż w trakcie porównania dochodzi do
niezgodności na pozycji znaku A w łańcuchu przeszukiwanym ze znakiem B we wzorcu (A, B i C oznaczają nie konkretne literki,
ale różne znaki łańcucha i wzorca) :

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 325 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

W celu uniknięcia po przesunięciu okna wzorca natychmiastowej niezgodności musimy dodatkowo zażądać, aby znak C leżący
tuż za prefikso-sufiksem prefiksu we wzorcu był różny od znaku B, który poprzednio wywołał niezgodność. Algorytm MP nie
sprawdzał tej cechy.
Tablicę szerokości prefikso-sufiksów uwzględniającą tę cechę będziemy nazywali tablicą KMPNext. Kolejne jej elementy są
maksymalnymi szerokościami prefikso-sufiksów prefiksu wzorca. Jeśli dany prefikso-sufiks nie istnieje (nawet prefikso-sufiks
pusty), to element tablicy ma wartość -1 (w poprzedniej wersji algorytmu wartownik występował tylko w elemencie o indeksie 0).

Przykład:
Wyznaczymy tablicę KMPNext dla wzorca ABACABAB – identyczny wzorzec jak w rozdziale o tworzeniu tablicy Π.

Lp. Tworzona tablica Next Opis


s : A B A C A B A B Element KMPNext[0] jest zawsze równy -1. Pełni on rolę
1. i : 0 1 2 3 4 5 6 7 8 wartownika, dzięki któremu upraszcza się algorytm wyznaczania
KMPNext[]:-1 ? ? ? ? ? ? ? ? kolejnych elementów tablicy.
s : A B A C A B A B Prefiks jednoznakowy A posiada zawsze prefikso-sufiks pusty.
2. i : 0 1 2 3 4 5 6 7 8 Dodatkowo znak A leżący tuż za prefikso-sufiksem oraz znak B
KMPNext[]:-1 0 ? ? ? ? ? ? ? leżący za prefiksem są różne. Zatem istnieje prefikso-sufiks pusty
o szerokości 0 i to wpisujemy do tablicy.
s : A B A C A B A B Prefikso-sufiks prefiksu dwuznakowego AB ma szerokość 0.
i : 0 1 2 3 4 5 6 7 8 Jednakże znak A leżący tuż za prefikso-sufiksem pustym oraz
3. KMPNext[]:-1 0-1 ? ? ? ? ? ? znak A leżący tuż za prefiksem jest tym samym znakiem. Skoro
tak, to prefikso-sufiks nie istnieje - wpisujemy -1.
s : A B A C A B A B Prefikso-sufiks prefiksu ABA ma szerokość 1 (obejmuje pierwszą
i : 0 1 2 3 4 5 6 7 8 i ostatnią literkę A). Znak B leżący tuż za prefikso-sufiksem oraz
4. KMPNext[]:-1 0-1 1 ? ? ? ? ? z nak C leżący za prefiksem są różne. Zatem do tablicy
wprowadzamy 1.
s : A B A C A B A B Prefiks ABAC ma prefikso-sufiks pusty o szerokości 0. Jednakże
5. i : 0 1 2 3 4 5 6 7 8 za prefikso-sufiksem i za prefiksem występuje ten sam znak.
KMPNext[]:-1 0-1 1-1 ? ? ? ? Zatem w tablicy umieszczamy -1.
s : A B A C A B A B Prefiks ABACA posiada prefikso-sufiks o szerokości 1 (pierwsza i
i : 0 1 2 3 4 5 6 7 8 ostatnia litera A). Jednakże za prefikso-sufiksem i prefiksem
KMPNext[]:-1 0-1 1-1 0 ? ? ? występuje ten sam znak B. Z tablicy odczytujemy szerokość
6. następnego prefikso-sufiksu - KMPNext[1] = 0. Jest to prefikso-
sufiks pusty. W tym przypadku za prefikso-sufiksem wystąpi
litera A, która różni się od litery B za prefiksem. Jest to zatem
poszukiwany prefikso-sufiks i szerokość 0 wpisujemy do tablicy.
s : A B A C A B A B Prefiks ABACAB posiada prefikso-sufiks o szerokości 2 (AB z
i : 0 1 2 3 4 5 6 7 8 przodu i z tyłu). Jednakże za prefikso-sufiksem i za prefiksem
7. KMPNext[]:-1 0-1 1-1 0-1 ? ? występuje ten sam znak A. Odczytujemy z tablicy szerokość
następnego prefikso-sufiksu KMPNext[2] = -1. Prefikso-sufiks nie
istnieje, zatem w tablicy umieszczamy -1.
s : A B A C A B A B Prefiks ABACABA posiada prefikso-sufiks o szerokości 3 (ABA).
8. i : 0 1 2 3 4 5 6 7 8 Znak C za prefikso-sufiksem jest różny od znaku B za prefiksem,
KMPNext[]:-1 0-1 1-1 0-1 3 ? zatem w tablicy umieszczamy 3.
s : A B A C A B A B Cały wzorzec ABACABAB posiada prefikso-sufiks o szerokości 2
i : 0 1 2 3 4 5 6 7 8 (AB) . Ponieważ nie ma już możliwości sprawdzenia znaku A
9. KMPNext[]:-1 0-1 1-1 0-1 3 2 leżącego tuż za prefikso-sufiksem ze znakiem leżącym za
wzorcem, to w tablicy umieszczamy 2. Tablica KMPNext jest
gotowa.

Porównajmy tablicę Π z tablicą KMPNext:


wzorzec A B A C A B A B
indeks 0 1 2 3 4 5 6 7 8
Π -1 0 0 1 0 1 2 3 2
KMPNext -1 0 -1 1 -1 0 -1 3 2

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 326 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

Algorytm KMP wyszukiwania wzorca wykorzystujący tablicę KMPNext jest identyczny z algorytmem MP wykorzystującym tablicę
Π. W algorytmie KMP zastępujemy jedynie odwołania do tablicy Π odwołaniami do tablicy KMPNext[], która pozwala bardziej
efektywnie wyszukiwać wystąpienia wzorców, ponieważ pomijane są puste przebiegi. Klasa złożoności obliczeniowej jest równa
O(m + n), gdzie m jest długością wzorca p, a n jest długością przeszukiwanego łańcucha s.

Algorytm Knutha-Morrisa-Pratta tworzenia tablicy KMPNext


Wejście
s – łańcuch znakowy
Wyjście:
Tablica KMPNext[] z długościami prefikso-sufiksów kolejnych prefiksów łańcucha s. KMPNext C.
Elementy pomocnicze:
KMPNext – tablica długości prefikso-sufiksów kolejnych prefiksów łańcucha s, KMPNext C
b – długość prefikso-sufiksu, b C
i – indeks, i N
Lista kroków:
K01: KMPNext[0] ← -1 ; na początku tablicy KMPNext wstawiamy wartownika
K02: b ← -1 ; początkowo długość prefikso-sufiksu ustawiamy na -1
K03: Dla i = 1,2,...,|s| wykonuj K04...K06 ; wyznaczamy długości prefikso-sufiksów dla prefiksów
łańcucha s
K04: Dopóki b > -1 s[b] ≠ s[i - 1], ; w pętli K04 szukamy prefikso-sufiksu b prefiksu
wykonuj b ← KMPNext[b] wzorca, który da
; się rozszerzyć. Z pętli wychodzimy, jeśli natrafimy na
wartownika
; lub prefikso-sufiks jest rozszerzalny
K05: b ← b + 1 ; rozszerzamy prefikso-sufiks
K06: Jeśli i = |s| s[i] ≠ s[b], ; Jeśli następny znak wzorca różni się od znaku tuż za
to KMPNext[i] ← b, prefikso-sufiksem,
inaczej KMPNext[i] ← KMPNext[b] ; to w tablicy KMPNext umieszczamy szerokość
prefikso-sufiksu.
; Inaczej w KMPNext umieszczamy zredukowaną
szerokość
; prefikso-sufiksu
K07: Zakończ

Algorytm Knutha-Morrisa-Pratta wyszukiwania wzorca


Wejście
s – łańcuch znakowy
p – poszukiwany wzorzec
Wyjście:
Kolejne pozycje wystąpień wzorca w łańcuchu. Wartość -1 nie wskazuje żadnej pozycji wzorca i
oznacza, iż wzorzec nie pojawia się w przeszukiwanym łańcuchu.
Elementy pomocnicze:
pp – zawiera wyznaczane pozycje wzorca, pp C
i – pozycja porównywanego znaku w łańcuchu tekstowym s, i N, 0 ≤ i ≤ |s|
b
– długość prefiksu wzorca p pasującego do prefiksu okna wzorca w łańcuchu s, b C, 0 ≤ b
≤ |p|.
KMPNext tablica o indeksach od 0 do |p|, przechowująca długości maksymalnych prefikso-sufiksów

kolejnych prefiksów wzorca p, KMPNext C
Lista kroków:

K01: Wyznacz tablicę KMPNext ; wyznaczamy tablicę maksymalnych prefikso-


sufiksów algorytmem KMP
K02: pp ← -1 ; wstępna pozycja wzorca ustawiona na -1
K03: b ← 0 ; wstępną długość prefikso-sufiksu ustawiamy na
0
K04: Dla i = 0,1,... |s| - 1, wykonuj K05...K10 ; pętla wyznacza znaki łańcucha do porównania ze
znakami wzorca
K05: Dopóki (b > -1) (p[b] ≠ s[i]), ; Szukamy we wzorcu p rozszerzalnego prefiksu
wykonuj b ← KMPNext[b] pasującego do prefiksu
; okna wzorca. Pętlę K05 przerywamy, jeśli
znajdziemy rozszerzalny
; prefiks lub napotkamy wartownika -1
K06: b ←b + 1 ; Rozszerzamy prefiks o 1 znak. Jeśli prefiks był

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 327 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

wartownikiem,
; to otrzymuje wartość 0.
K07: Jeśli b < |p|, to następny obieg pętli K04 ; Jeśli prefiks nie obejmuje całego wzorca, to
kontynuujemy pętlę K04,
; czyli porównujemy znak p[b] z kolejnym znakiem
łańcucha s.
K08: pp ← i - b + 1 ; wyznaczamy pozycję wzorca p w łańcuchu s
K09: Pisz pp ; wyprowadzamy tę pozycję
K10: b ← KMPNext[b] ; redukujemy b do długości prefikso-sufiksu
wzorca
K11: Jeśli pp = -1, pisz -1 ; jeśli wzorzec p nie występuje w s, wyprowadzamy
-1
K12: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B oraz 5 znakowy wzorzec
wg tego samego schematu. Następnie program wyznacza tablicę długości maksymalnych prefikso-sufiksów
kolejnych prefiksów wzorca i wykorzystuje ją do znalezienia wszystkich wystąpień wzorca w łańcuchu.

Lazarus

// Wyszukiwanie wzorca algorytmem KMP


// Data: 4.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const
N = 80; // długość łańcucha s
M = 5; // długość wzorca p
var
s,p : ansistring;
KMPNext : array[0..M] of integer;
i,b,pp : integer;
begin
randomize;
// generujemy łańcuch s
s := '';
for i := 1 to N do s := s + chr(65 + random(2));
// generujemy wzorzec
p := '';
for i := 1 to M do p := p + chr(65 + random(2));
// dla wzorca obliczamy tablicę KMPNext[]
KMPNext[0] := -1;
b := -1;
for i := 1 to M do
begin
while (b > -1) and (p[b + 1] <> p[i]) do b := KMPNext[b];
inc(b);
if (i = M) or (p[i + 1] <> p[b + 1]) then KMPNext[i] := b
else KMPNext[i] := KMPNext[b];
end;
// wypisujemy wzorzec p
writeln(p);
// wypisujemy łańcuch s
write(s);
// poszukujemy pozycji wzorca w łańcuchu
pp := 0; b := 0;
for i := 1 to N do

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 328 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

begin
while (b > -1) and (p[b + 1] <> s[i]) do b := KMPNext[b];
inc(b);
if b = M then
begin
while pp < i - b do
begin
write(' '); inc(pp);
end;
write('^'); inc(pp);
b := KMPNext[b];
end
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie wzorca algorytmem KMP


// Data: 4.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80; // długość łańcucha s
const int M = 5; // długość wzorca p
int main()
{
string s,p;
int KMPNext[M + 1],i,b,pp;
srand((unsigned)time(NULL));
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++) s += 65 + rand() % 2;
// generujemy wzorzec
p = "";
for(i = 0; i < M; i++) p += 65 + rand() % 2;
// dla wzorca obliczamy tablicę Next[]
KMPNext[0] = b = -1;
for(i = 1; i <= M; i++)
{
while((b > -1) && (p[b] != p[i - 1])) b = KMPNext[b];
++b;
if((i == M) || (p[i] != p[b])) KMPNext[i] = b;
else KMPNext[i] = KMPNext[b];
}
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch s
cout << s;
// poszukujemy pozycji wzorca w łańcuchu
pp = b = 0;
for(i = 0; i < N; i++)
{
while((b > -1) && (p[b] != s[i])) b = KMPNext[b];
if(++b == M)
{
while(pp < i - b + 1)
{
cout << " "; pp++;
}
cout << "^"; pp++;
b = KMPNext[b];
}
}
cout << endl;
return 0;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 329 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

Free Basic

' Wyszukiwanie wzorca algorytmem KMP


' Data: 4.06.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 80 ' długość łańcucha s
Const M = 5 ' długość wzorca p
Dim As String s,p
Dim As Integer KMPNext(M),i,b,pp
Randomize
' generujemy łańcuch s
s = ""
For i = 1 To N: s += Chr(65 + Cint(Rnd)): Next
' generujemy wzorzec
p = ""
For i = 1 To M: p += Chr(65 + Cint(Rnd)): Next
' dla wzorca obliczamy tablicę PI[]
KMPNext(0) = -1: b = -1
For i = 1 To M
While (b > -1) And (Mid(p,b + 1,1) <> Mid(p,i,1)): b = KMPNext(b): Wend
b += 1:
If (i = M) Or (Mid(p,i + 1,1) <> Mid(p,b + 1,1)) Then
KMPNext(i) = b
Else
KMPNext(i) = KMPNext(b)
End If
Next
' wypisujemy wzorzec p
Print p
' wypisujemy łańcuch s
Print s;
' poszukujemy pozycji wzorca w łańcuchu
pp = 0: b = 0
For i = 1 To N
While (b > -1) And (Mid(p,b + 1,1) <> Mid(s,i,1)): b = KMPNext(b): Wend
b += 1
If b = M Then
While pp < i - b
Print " ";: pp += 1
Wend
Print "^";: pp += 1
b = KMPNext(b)
End If
Next
Print
End

Wynik
BABBB
AABABBBBABBBBABABAAABBBAABBBABABBABABBAABABBBBBAABAAAAAAABABBBABBBABAABBBBAAAABB
^ ^ ^ ^ ^

Wyszukiwanie wzorca algorytmem KMP


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 330 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KMP 2014-10-03

Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0049.php 331 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.

Opisany w poprzednim rozdziale algorytm Knutha-Morrisa-Pratta, chociaż bardzo sprytny, jednak wciąż wymaga przeglądnięcia
kolejnych znaków przeszukiwanego łańcucha tekstowego. W stosunku do algorytmu naiwnego zaletą algorytmu KMP jest to, iż
nie cofamy się w przeszukiwanym tekście w przypadku stwierdzenia niezgodności ze wzorcem.
W roku 1975 Robert S. Boyer i J. Strother Moore wynaleźli znacznie lepszy algorytm, który uważa się za najszybszy, praktyczny
algorytm wyszukiwania wzorca, stosowany obecnie w prawie każdym edytorze tekstu przy wyszukiwaniu tekstów. Jako
ciekawostkę możemy podać, iż algorytm BM zaimplementowano w języku FreeBasic. Najpierw opiszemy uproszczoną wersję
tego algorytmu.
Algorytm Boyera-Moore'a (w skrócie algorytm BM) rozpoczyna porównywanie od ostatniego znaku wzorca, czyli odwrotnie niż
opisane poprzednio algorytmy. Co nam to daje? Bardzo wiele. Na przykład jeśli ostatni znak wzorca nie zgadza się ze znakiem w
przeszukiwanym tekście i dodatkowo wiemy, iż znak z przeszukiwanego tekstu nie występuje dalej we wzorcu, to okno wzorca
możemy od razu przesunąć o tyle pozycji, ile znaków zawiera wzorzec. W przeciwnym razie wzorzec pozycjonujemy tak, aby
zgrać pozycje znaku występującego jednocześnie w przeszukiwanym tekście i we wzorcu. Algorytmy naiwny i KMP nie pomijają
w ten sposób znaków w przeszukiwanym tekście. Dzięki temu algorytm BM zwykle dużo szybciej dochodzi do rozwiązania –
mówimy, iż dla typowych danych posiada on podliniową klasę złożoności obliczeniowej. Dla oddania sprawiedliwości należy
jednakże zauważyć, iż algorytm KMP nie wymaga buforowania przeglądanego tekstu (jest to bardzo korzystne w przypadku

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 332 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

jednakże zauważyć, iż algorytm KMP nie wymaga buforowania przeglądanego tekstu (jest to bardzo korzystne w przypadku
wykonywania poszukiwań np. w dużych plikach przechowywanych na zewnętrznym nośniku danych – tekst wczytujemy znak po
znaku i przetwarzamy), tymczasem w algorytmie BM takie buforowanie jest konieczne. Jeśli przeszukiwany tekst znajduje się w
całości w pamięci operacyjnej komputera, to ograniczenie powyższe nie jest kłopotliwe.

Przykład:
Dla przykładu wyszukajmy wzorzec ABCAB w tekście ACBADBABCABD.

Lp. Wyszukiwanie wzorca Opis

1. A C B A D B A B C A B D Okno wzorca umieszczamy na początku przeszukiwanego tekstu.


A B C A B
A C B A D B A B C A B D Porównanie rozpoczynamy od ostatniego znaku wzorca. Znaki są różne.
2. A B C A B Dodatkowo znak D z tekstu nie występuje we wzorcu.
A C B A D B A B C A B D Dlatego okno wzorca możemy od razu przesunąć o 5 pozycji (z tylu
3. → → → → → A B C A B znaków składa się cały wzorzec).
A C B A D B A B C A B D Znów porównujemy ostatni znak wzorca ze znakiem tekstu. Jest
4. A B C A B niezgodność. Lecz w tym przypadku litera A tekstu występuje we
wzorcu.
A C B A D B A B C A B D Okno wzorca przesuwamy tak, aby litera A z tekstu i ostatnia litera A ze
5. → A B C A B wzorca zrównały się pozycjami.
A C B A D B A B C A B D Teraz porównanie daje zgodność wszystkich znaków wzorca – zadanie
6. A B C A B zostało wykonane.

Do wykonywania przesunięcia okna wzorca względem przeszukiwanego tekstu wykorzystamy tablicę Last. W tablicy tej indeksy
poszczególnych elementów odpowiadają kodom znaków alfabetu. Elementy natomiast określają ostatnie położenie danej litery we
wzorcu. Jeśli litery nie ma we wzorcu, to reprezentujący ją element w tablicy Last ma wartość -1.

Przykład:
Załóżmy, iż alfabet składa się z 4 znaków ABCD, Tablica Last będzie zawierała cztery elementy. Dla podanego we
wcześniejszym przykładzie wzorca ABCAB tablica ta przyjmie następującą postać:

Last[A:(3), B:(4), C:(2), D:(-1)]

W trakcie wyszukiwania może zajść jeden z trzech poniższych przypadków:

Niech i oznacza pozycję początku okna wzorca p w przeszukiwanym tekście s, a j pozycję we wzorcu, na której
występuje niezgodność ze znakiem tekstu.

Lp. Możliwe przypadki Opis


i: 0 1 2 3 4 5 6 7 8 . Litera D nie występuje we wzorcu p. Okno wzorca możemy przesunąć na
s: ? ? D A B ? ? ? ? ? pozycję:
1. p: A B C A B i = i + j - Last[D] = 0 + 2 - (-1) = 3
j: 0 1 2 3 4
→ → → A B C A B
i: 0 1 2 3 4 5 6 7 8 . Litera C jest we wzorcu na pozycji 2. Okno możemy przesunąć na pozycję:
s: ? ? ? ? C ? ? ? ? ? i = i + j - Last[C] = 0 + 4 - 2 = 2
2. p: A B C A B
j: 0 1 2 3 4
→ → A B C A B
i: 0 1 2 3 4 5 6 7 8 . W tym przypadku zastosowanie powyższej reguły daje ujemny przesuw
s: ? A C A B ? ? ? ? ? okna wzorca - okno cofa się w lewo, zamiast przesunąć się w prawo
p: A B C A B i = i + j - Last[A] = 0 + 1 - 3 = -2
j: 0 1 2 3 4
A B C A B ← ← Rozwiązaniem jest zastosowanie reguły, iż w przypadku niezgodności okno
3. → A B C A B wzorca przesuwamy zawsze o większą z dwóch liczb:
1 oraz j - Last[X], czyli max(1, j - Last[X])
Wtedy:
i = i + max(1, j - Last[A])
i = 0 + max(1, 1 - 3) = 0 + max(1, -2) = 0 + 1 = 1

Algorytm tworzenia tablicy Last[] dla algorytmu Boyera-Moore'a


Tablica Last jest dodatkową strukturą danych wykorzystywaną w uproszczonym algorytmie Boyera-Moore'a. Przechowuje
pozycje ostatnich wystąpień we wzorcu p wszystkich możliwych znaków alfabetu. Jeśli danego znaku nie ma we wzorcu, to
pozycja ustawiana jest na -1.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 333 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

Wejście:
p – wzorzec
zp – kod pierwszego znaku alfabetu.
zk – kod ostatniego znaku alfabetu

Wyjście:
Last – tablica o indeksach od 0 do zk - zp. Każdy element Last[zk - zi] C i określa ostatnie położenie
znaku o kodzie zi we wzorcu. Takie podejście jest konieczne dla języka C++, który wymaga, aby
indeksy tablic rozpoczynały się od 0.
Elementy pomocnicze:
i – zmienna licznikowa pętli, i N
Lista kroków:
K01: Dla i = 0,1,..., zk - zp, wykonuj Last[i] ← -1 ; wypełniamy całą tablicę Last wartościami
-1
K02: Dla i = 0,1,...,|p| - 1, wykonuj Last[kod(p[i]) - zp] ← i ; przeglądamy wzorzec wprowadzając do
tablicy Last położenia
; kolejno napotkanych liter. Ponieważ
poprzednie wpisy są nadpisywane,
; w efekcie otrzymamy ostatnie położenia
znaków występujących
; we wzorcu. Komórki nie zapisane
przechowują wartość -1, która
; pochodzi z pętli K01.
K03: Zakończ

Uproszczony algorytm Boyera-Moore'a wyszukiwania wzorca


Wejście:
s – łańcuch znakowy do przeszukania
p – wzorzec, którego wystąpień poszukujemy w łańcuchu s
zp – kod pierwszego znaku alfabetu.
zk – kod ostatniego znaku alfabetu

Wyjście:
Kolejne pozycje wystąpień wzorca w łańcuchu. Wartość -1 nie wskazuje żadnej pozycji wzorca i
oznacza, iż wzorzec nie pojawia się w przeszukiwanym łańcuchu.
Elementy pomocnicze:
Last – tablica ostatnich pozycji wszystkich znaków alfabetu we wzorcu p, Last C
i – pozycja okna wzorca w łańcuchu s, i N
j – pozycja znaku we wzorcu p, który porównujemy ze znakiem łańcucha s, j N
max(a,b) – funkcja zwracająca większą z liczb a i b.
pp – zawiera pozycję wzorca p w łańcuchu s, pp N
m – długość wzorca p, m N
n – długość łańcucha s, n N

Lista kroków:
K01: Dla zp i zk oblicz tablicę Last ; przygotowujemy tablicę ostatnich pozycji znaków we
wzorcu
K02: m ← | p | ; zapamiętujemy w m długość wzorca
K03: n ← | s | ; a w n długość łańcucha
K04: pp ← -1 ; pozycję łańcucha p w s wstępnie ustawiamy na -1
K05: i ← 0 ; okno wzorca umieszczamy na początku łańcucha s
K06: Dopóki i ≤ n - m, wykonuj K07...K14 ; pętlę wykonujemy dopóki okno wzorca mieści się w
łańcuchu
K07: j ←m-1 ; w j ostatnia pozycja znaku we wzorcu p
K08: Dopóki (j > -1) (p[j] = s[i+j]), ; sprawdzamy od końca zgodność znaków wzorca z
wykonuj j ← j - 1 oknem
K09: Jeśli j > -1, to idź do K14 ; wzorzec pasuje do okna?
K10: pp ← i ; zapamiętujemy pozycję okna
K11: Pisz pp ; wyprowadzamy znalezioną pozycję
K12: i ←i + 1 ; przesuwamy pozycję okna
K13: Kontynuuj pętlę K06 ; i szukamy dalej

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 334 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

K14: i ← i + max(1,j - Last[kod(s[i + j]) - zp]) - przesuwamy okno wzorca wykorzystując tablicę Last
K15: Jeśli pp = -1, to pisz -1 ; w razie nie znalezienia pozycji wzorca wypisujemy -1
K16: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B oraz 5 znakowy wzorzec
wg tego samego schematu. Następnie program wyznacza tablicę długości maksymalnych prefikso-sufiksów
kolejnych prefiksów wzorca i wykorzystuje ją do znalezienia wszystkich wystąpień wzorca w łańcuchu.

Lazarus

// Wyszukiwanie wzorca algorytmem BM


// Data: 6.06.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------------
program prg;
uses Math;
const
N = 80; // długość łańcucha s
M = 5; // długość wzorca p
zp = 65; // kod pierwszego znaku alfabetu
zk = 66; // kod ostatniego znaku alfabetu
var
s,p : ansistring;
Last : array[0..zk-zp] of integer;
i,j,pp : integer;
begin
randomize;
// generujemy łańcuch s
s := '';
for i := 1 to N do s := s + chr(zp + random(zk - zp + 1));
// generujemy wzorzec
p := '';
for i := 1 to M do p := p + chr(zp + random(zk - zp + 1));
// wypisujemy wzorzec
writeln(p);
// wypisujemy łańcuch
write(s);
// dla wzorca obliczamy tablicę Last[]
for i := 0 to zk - zp do Last[i] := 0;
for i := 1 to M do Last[ord(p[i]) - zp] := i;
// szukamy pozycji wzorca w łańcuchu
pp := 1; i := 1;
while i <= N - M + 1 do
begin
j := M;
while (j > 0) and (p[j] = s[i + j - 1]) do dec(j);
if j = 0 then
begin
while pp < i do
begin
write(' '); inc(pp);
end;
write('^'); inc(pp);
inc(i);
end
else inc(i,max(1,j - Last[ord(s[i+j-1]) - zp]));
end;
writeln;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 335 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

end.

Code::Blocks

// Wyszukiwanie wzorca algorytmem BM


// Data: 6.06.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80; // długość łańcucha s
const int M = 5; // długość wzorca p
const int zp = 65; // kod pierwszego znaku alfabetu
const int zk = 66; // kod ostatniego znaku alfabetu
int main()
{
string s,p;
int Last[zk - zp + 1],i,j,pp;
srand((unsigned)time(NULL));
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++) s += zp + rand() % (zk - zp + 1);
// generujemy wzorzec
p = "";
for(i = 0; i < M; i++) p += zp + rand() % (zk - zp + 1);
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch
cout << s;
// dla wzorca obliczamy tablicę Last[]
for(i = 0; i <= zk - zp; i++) Last[i] = -1;
for(i = 0; i < M; i++) Last[p[i] - zp] = i;
// szukamy pozycji wzorca w łańcuchu
pp = i = 0;
while(i <= N - M)
{
j = M - 1;
while((j > -1) && (p[j] == s[i + j])) j--;
if(j == -1)
{
while(pp < i)
{
cout << " "; pp++;
}
cout << "^"; pp++;
i++;
}
else i += max(1,j - Last[s[i + j] - zp]);
}
cout << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie wzorca algorytmem BM


' Data: 6.06.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------------
Const N = 80 ' długość łańcucha s
Const M = 5 ' długość wzorca p
Const zp = 65 ' kod pierwszego znaku alfabetu
Const zk = 66 ' kod ostatniego znaku alfabetu
' Funkcja wyznacza większą z dwóch liczb

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 336 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

'---------------------------------------
Function max(Byval a As Integer, Byval b As Integer) As Integer
If a > b Then max = a Else max = b
End Function
Dim As String s,p
Dim As Integer Last(zk-zp),i,j,pp
Randomize
' generujemy łańcuch s
s = ""
For i = 1 To N: s += Chr(zp + Cint(Rnd * (zk - zp))): Next
' generujemy wzorzec
p = ""
For i = 1 To M: p += Chr(zp + Cint(Rnd * (zk - zp))): Next
' wypisujemy wzorzec
Print p
' wypisujemy łańcuch
Print s;
' dla wzorca obliczamy tablicę Last[]
For i = 0 To zk - zp: Last(i) = 0: Next
For i = 1 To M: Last(Asc(Mid(p,i,1)) - zp) = i: Next
' szukamy pozycji wzorca w łańcuchu
pp = 1: i = 1
While i <= N - M + 1
j = M
While (j > 0) And (Mid(p,j,1) = Mid(s,i + j - 1,1)): j -= 1: Wend
If j = 0 Then
While pp < i: Print " ";: pp += 1: Wend
Print "^";: pp += 1
i += 1
Else
i += max(1,j - Last(Asc(Mid(s,i + j - 1,1)) - zp))
End If
Wend
Print
End

Wynik
AAABB
ABABAAAAABBBAAAAABABAABABAAABBBABAAAABABBABBAABABBBBAAAABBAABBBAAAAABBBBAAAAABAA
^ ^ ^ ^

Wyszukiwanie wzorca uproszczonym algorytmem BM


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 337 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca uproszczonym algorytmem BM 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0050.php 338 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Heurystyki algorytmu Boyera-Moore'a
Podstawowe pojęcia dotyczące przetwarzania tekstów Tworzenie tablicy przesunięć BMNext
Podstawowe operacje na łańcuchach znakowych Algorytm Boyera-More'a wyszukiwania wzorca
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.

Tutaj znajdziesz oryginalny tekst R.S.Boyera oraz J.S.Moore'a z roku 1977.

Heurystyki algorytmu Boyera-Moore'a


Uproszczony algorytm Boyera-Moore'a reaguje tylko na niezgodność znaku wykorzystując wyliczoną wcześniej tablicę Last do
znalezienia przesunięcia okna wzorca p w obrębie przeszukiwanego tekstu s. Postępowanie to nazywamy heurystyką
niepasującego znaku (ang. bad character heuristics). Zupełnie nie korzystamy tutaj z informacji, iż pewien sufiks b wzorca p
pasował do przeszukiwanego tekstu:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 339 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

Pełna wersja algorytmu nie wyrzuca do kosza tej informacji. Rozważmy możliwe dwa przypadki:

1. Pasujący sufiks b powtarza się wewnątrz wzorca. Dodatkowo żądamy, aby znak C bezpośrednio poprzedzający ten
fragment tekstu był różny od znaku B (B jest oznaczeniem symbolicznym, a nie konkretną literą!), gdyż w przeciwnym razie
otrzymamy natychmiastową niezgodność z przeszukiwanym tekstem. Okno wzorca przesuwamy tak, aby uzyskać
pokrycie pasującego fragmentu b z pasującym do niego fragmentem b przeszukiwanego tekstu.

2. Pasujący sufiks b nie powtarza się wewnątrz wzorca. Jednakże istnieje prefikso-sufiks bb wzorca zawarty w pasującym
sufiksie b (porusz algorytm KMP). W takim przypadku okno wzorca przesuwamy tak, aby uzyskać pokrycie prefikso-
sufiksów we wzorcu i w przeszukiwanym tekście:

Jeśli prefikso-sufiks jest pusty, to okno wzorca można przesunąć o całą długość wzorca beż żadnej straty dla
wyników poszukiwań. W efekcie uzyskujemy bardzo ciekawą własność algorytmu Boyera-Moore'a – im wzorzec p
jest dłuższy, tym szybciej przebiega wyszukiwanie, ponieważ algorytm przeskakuje duże partie tekstu.

Opisana w tych dwóch punktach strategia postępowania nosi nazwę heurystyki pasującego sufiksu (ang. good suffix heuristics).
W wyniku jej zastosowania otrzymujemy przesunięcie okna wzorca bez pomijania możliwych wystąpień w przeszukiwanym
tekście.
Pełny algorytm Boyera-Moore'a wykorzystuje obie heurystyki – nie pasującego znaku (opisana w poprzednim rozdziale) oraz
pasującego sufiksu, a wynikowe przesunięcie okna wzorca jest większym z tych dwóch wyliczonych przesunięć.

Tworzenie tablicy przesunięć BMNext


Do wyznaczenia przesunięcia w przypadku heurystyki pasującego sufiksu będzie nam potrzebna tablica BMNext zawierająca
wartości przesunięć dla poszczególnych sufiksów wzorca (tablica ta jest niejako odwróceniem tablicy KMPNext obecnej w
algorytmie KMP). Wyznaczamy ją w dwóch etapach.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 340 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

Etap 1
Ten etap jest bardzo podobny do przetwarzania wzorca w algorytmie KMP. Pasujący sufiks bb jest prefikso-sufiksem
pewnego sufiksu b wzorca p:

Musimy ustalić prefikso-sufiksy sufiksów wzorca, jednakże w porównaniu z algorytmem KMP będzie obowiązywało
odwrotne odwzorowanie pomiędzy prefikso-sufiksem, a najkrótszym sufiksem zawierającym ten prefikso-sufiks.
ednocześnie musimy zagwarantować, iż dany prefikso-sufiks nie może być rozszerzalny w lewo (patrz znaki B i C
we wzorcu) przez znak B, który spowodował niezgodność z przeszukiwanym tekstem. W przeciwnym razie po
przesunięciu okna wzorca otrzymalibyśmy natychmiastową niezgodność, ponieważ w tym samym miejscu znów
znalazłby się niezgodny znak B.
W etapie 1 obliczana jest pomocnicza tablica П o elementach indeksowanych od 0 do m (m = |p|). Element П[i]
zawiera początkową pozycję maksymalnego prefikso-sufiksu sufiksu wzorca rozpoczynającego się na pozycji i-tej.

Przykład:
Dla wzorca ABBABBBA tablicę П wyznaczymy następująco:

Tworzenie tablicy П Opis


i: 0 1 2 3 4 5 6 7 8 Na pozycji m = 8 jest sufiks pusty, Ponieważ nie posiada on prefikso-sufiksu, do
p: A B B A B B B A
П: . . . . . . . . 9 П[8] wpisujemy 9

i: 0 1 2 3 4 5 6 7 8 Na pozycji 7 jest sufiks A. Posiada on prefikso-sufiks pusty rozpoczynający się


p: A B B A B B B A
П: . . . . . . . 8 9 na pozycji 8.

i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BA również posiada prefikso-sufiks pusty.
П: . . . . . . 8 8 9
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BBA posiada prefikso-sufiks pusty
П: . . . . . 8 8 8 9
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BBBA posiada prefikso-sufiks pusty
П: . . . . 8 8 8 8 9
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks ABBBA posiada prefikso-sufiks A rozpoczynający się na pozycji 7.
П: . . . 7 8 8 8 8 9
i: 0 1 2 3 4 5 6 7 8 Sufiks BABBBA rozszerza prefikso-sufiks poprzedniego sufiksu do prefikso-
p: A B B A B B B A
П: . . 6 7 8 8 8 8 9 sufiksu BA rozpoczynającego się na pozycji 6.

i: 0 1 2 3 4 5 6 7 8 Sufiks BBABBBA znów rozszerza prefikso-sufiks poprzedniego sufiksu do


p: A B B A B B B A prefikso-sufiksu BBA rozpoczynającego się na pozycji 5.
П: . 5 6 7 8 8 8 8 9
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks ABBABBBA redukuje prefikso-sufiks do A na pozycji 7.
П: 7 5 6 7 8 8 8 8 9

Tablica П służy do wstępnej generacji właściwej tablicy BMNext. Na początku zerujemy wszystkie komórki tej
tablicy. Następnie wyznaczamy kolejne wartości tablicy П. Jeśli prefikso-sufiks poprzedniego sufiksu wzorca nie
może być rozszerzony w lewo na prefikso-sufiks sufiksu rozpoczynającego się na pozycji i-tej, to odnotowujemy ten
fakt w elemencie BMNext[pozycja prefikso-sufiksu] o ile nie zawiera on już jakiejś wartości różnej od 0 (dotyczy to
prefikso-sufiksu mniejszego sufiksu, który ma preferencje). Do elementu tablicy BMNext wpisujemy różnicę pozycji
nie rozszerzalnego prefikso-sufiksu oraz i, czyli:

BMNext[pozycja prefikso-sufiksu] = pozycja prefikso-sufiksu – pozycja sufiksu

W efekcie otrzymujemy wartość przesunięcie okna wzorca p w łańcuchu s, po którym zrównują się pozycje

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 341 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

pasującego prefikso-sufiksu we wzorcu i w oknie. Prześledźmy teraz tworzenie obu tablic dla wzorca ABBABBBA:

Tworzenie tablic П i BMNext Opis


i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A
П: . . . . . . . . 9 Inicjujemy П[8] = 9, zerujemy BMNext
BMNext: 0 0 0 0 0 0 0 0 0
i: 0 1 2 3 4 5 6 7 8 Na pozycji 7 jest sufiks A. Posiada on prefikso-sufiks pusty
p: A B B A B B B A rozpoczynający się na pozycji 8. Prefikso-sufiks tego sufiksu nie daje się
П: . . . . . . . 8 9 rozszerzyć w lewo.
BMNext: 0 0 0 0 0 0 0 0 1 W BMNext[8] wpisujemy 8 - 7 = 1
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BA również posiada prefikso-sufiks pusty. Prefikso-sufiks nie jest
П: . . . . . . 8 8 9 rozszerzalny, ale w BMNext[8] już mamy wpis 1.
BMNext: 0 0 0 0 0 0 0 0 1
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BBA posiada prefikso-sufiks pusty i nie jest rozszerzalny.
П: . . . . . 8 8 8 9 Tablicy BMNext nie modyfikujemy.
BMNext: 0 0 0 0 0 0 0 0 1
i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks BBBA posiada prefikso-sufiks pusty i jest rozszerzalny.
П: . . . . 8 8 8 8 9 Tablicy BMNext nie modyfikujemy.
BMNext: 0 0 0 0 0 0 0 0 1
i: 0 1 2 3 4 5 6 7 8 Sufiks ABBBA posiada prefikso-sufiks A rozpoczynający się na pozycji 7 i
p: A B B A B B B A jest rozszerzalny.
П: . . . 7 8 8 8 8 9
BMNext: 0 0 0 0 0 0 0 0 1 Tablicy BMNext nie modyfikujemy.

i: 0 1 2 3 4 5 6 7 8 Sufiks BABBBA posiada prefikso-sufiks BA rozpoczynającego się na


p: A B B A B B B A pozycji 6 i jest rozszerzalny.
П: . . 6 7 8 8 8 8 9
BMNext: 0 0 0 0 0 0 0 0 1 Tablicy BMNext nie modyfikujemy.

i: 0 1 2 3 4 5 6 7 8 Sufiks BBABBBA posiada prefikso-sufiks BBA rozpoczynającego się na


p: A B B A B B B A pozycji 5 i nie jest rozszerzalny.
П: . 5 6 7 8 8 8 8 9
BMNext: 0 0 0 0 0 4 0 0 1 W BMNext[5] wpisujemy 5 - 1 = 4

i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A Sufiks ABBABBBA redukuje prefikso-sufiks do A na pozycji 7.
П: 7 5 6 7 8 8 8 8 9
BMNext: 0 0 0 0 0 4 0 0 1

Algorytm etapu nr 1 tworzenia tablicy BMNext dla pełnego algorytmu Boyera-Moore'a


Wejście:
p – wzorzec
Wyjście:

BMNext – tablica m + 1 elementowa przesunięć okna wzorca, częściowo zapełniona, BMNext C


П – pomocnicza tablica m + 1 elementowa położeń prefikso-sufiksów sufiksów wzorca, П C

Elementy pomocnicze:
i – indeksowanie elementów tablic, i N
m – długość wzorca p, m N
b – położenie prefikso-sufiksu aktualnego sufiksu, b N
Lista kroków:
K01: m ← |p| ; wyznaczamy długość wzorca
K02: Dla i = 0,1,...,m wykonuj BMNext[i] ← 0 ; zerujemy elementy tablicy BMNext{ }
K03: i ← m ; ustawiamy indeks elementów dla
tablicy П
K04: b ← m + 1 ; wstępne położenie prefikso-sufiksu
poza pustym sufiksem
K05: П[i] ← b ; inicjujemy ostatni element tablicy
K06: Dopóki i > 0, wykonuj K07...K12 ; pętla wypełniająca komórki tablicy П
K07: Dopóki (b ≤ m) (p[i-1] ≠ p[b-1]) wykonuj K08...K09 ; pętla obsługuje sytuację, gdy bieżący
prefikso-sufiks istnieje
; i nie daje się rozszerzyć w lewo.
K08: Jeśli BMNext[b] = 0, to BMNext[b] ← b - i ; odnotowujemy taki prefikso-sufiks w
tablicy BMNext
; o ile nie został już odnotowany przez

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 342 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

wcześniejszy sufiks.
K09: b ← П[b] ; w b umieszczamy położenie prefikso-
sufiksu wewnętrznego
; i kontynuujemy pętlę K07
K10: b ←b -1 ; prefikso-sufiks jest rozszerzalny lub
nie istnieje
K11: i ←i -1 ; przesuwamy się na kolejną pozycję w
lewo.
K12: П[i] ← b ; położenie prefikso-sufiksu
zapamiętujemy w tablicy П'
K13: Zakończ

Etap 2
Po zakończeniu etapu 1 otrzymujemy wypełnioną tablicę П, zawierającą położenia prefikso-sufiksów kolejnych
sufiksów wzorca oraz częściowo wypełnioną tablicę BMNext zawierającą przesunięcia okna wzorca dla kolejnych
sufiksów. W etapie 2 sprawdzamy, czy istnieje największy prefikso-sufiks wzorca zawarty w całości w pasującym
sufiksie. Wzorzec można wtedy przesunąć tak daleko, jak pozwoli na to pasujący prefikso-sufiks:

Dlatego wyznaczymy teraz dla każdego sufiksu najdłuższy prefikso-sufiks wzorca, który jest zawarty w tym sufiksie.
Pozycja startowa najszerszego prefikso-sufiksu wzorca jest zawarta w П[0]. Dla naszego wzorca ABBABBBA jest to
7, ponieważ prefikso-sufiks A rozpoczyna się od pozycji 7:

i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A
П: 7 5 6 7 8 8 8 8 9
BMNext: 0 0 0 0 0 4 0 0 1

Wartość ta zostaje umieszczona w kolejnych, wolnych elementach tablicy BMNext. Jednakże gdy sufiks wzorca
stanie się krótszy od prefikso-sufiksu wzorca (czyli gdy go już nie może zawierać), to kolejnym prefikso-sufiksem
stanie się jego prefikso-sufiks wewnętrzny. W efekcie otrzymamy pełną tablicę BMNext:

i: 0 1 2 3 4 5 6 7 8
p: A B B A B B B A
П: 7 5 6 7 8 8 8 8 9
BMNext: 7 7 7 7 7 4 7 7 1

Algorytm etapu nr 2 tworzenia tablicy BMNext dla pełnego algorytmu Boyera-Moore'a


Wejście:

p – wzorzec
m – długość wzorca (z poprzedniego etapu), m N
BMNext – tablica m + 1 elementowa przesunięć okna wzorca częściowo zapełniona w etapie nr 1.
BMNext C
П – pomocnicza tablica m + 1 elementowa położeń prefikso-sufiksów sufiksów wzorca, utworzona w
etapie nr 1, П C

Wyjście:
BMNext – kompletna tablica m + 1 elementowa przesunięć okna wzorca

Elementy pomocnicze:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 343 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

i – indeksowanie elementów tablic, i N


b – położenie prefikso-sufiksu aktualnego sufiksu, b N
Lista kroków:
K01: b ← П[0] ; najdłuższy prefikso-sufiks
K02: Dla i = 0,1,...,m wykonuj K03...K04 ; indeksujemy kolejne komórki BMNext
K03: Jeśli BMNext[i] = 0, to BMNext[i] ← b ; w wolnych elementach umieszczamy położenie
prefikso-sufiksu
K04: Jeśli i = b, to b ← П[b] ; jeśli sufiks nie mieści prefikso-sufiksu, to bierzemy
kolejny prefikso-sufiks
K05: Zakończ

Algorytm Boyera-Moore'a wyszukiwania wzorca


Algorytm BM składa się z etapu przetwarzania wzorca, w którym wyznacza się dwie tablice:

1. Last zawierającą ostatnie położenia znaków alfabetu we wzorcu


2. BMNext zawierającą przesunięcia dla pasujących sufiksów wzorca.

Po wyznaczeniu tych tablic następuje właściwy etap wyszukiwania.

Pełny algorytm Boyera-Moore'a wyszukiwania wzorca


Wejście:
s – przeszukiwany łańcuch
p – poszukiwany wzorzec
zp – kod pierwszego znaku alfabetu.
zk – kod ostatniego znaku alfabetu

Wyjście:
Kolejne pozycje wystąpień wzorca w łańcuchu. Wartość -1 nie wskazuje żadnej pozycji wzorca i
oznacza, iż wzorzec nie pojawia się w przeszukiwanym łańcuchu.
Elementy pomocnicze:
i – położenie okna wzorca p w przeszukiwanym łańcuchu s, i N
j – położenie porównywanego znaku we wzorcu p, j N
m – długość wzorca p - wyznaczane w trakcie wyliczania tablic Last i BMNext, m N
n – długość łańcucha s – wyznaczane w trakcie wyliczania tablic Last i BMNext, n N
Last – tablica ostatnich wystąpień znaków alfabetu we wzorcu p. Indeksy od 0 do zk - zp. Last
C
BMNext – tablica przesunięć okna wzorca dla pasujących sufiksów. Indeksy od 0 do m. BMNext
C
pp – znalezione pozycje wzorca p w łańcuchu s, pp N
max(a,b) – zwraca większą z liczb a lub b

Lista kroków:
K01: Wyznacz tablicę Last dla p, zp i zk ; wyznaczamy ostatnie położenia znaków
alfabetu we wzorcu
K02: Wyznacz tablicę BMNext dla p ; wyznaczamy przesunięcia okna wzorca
dla pasujących sufiksów
K03: pp ← -1 ; ustawiamy pozycję wzorca na "nie
znaleziono"
K04: i ← 0 ; okno wzorca umieszczamy na
początku łańcucha s
K05: Dopóki i ≤ n - m wykonuj K06...K13 ; dopóki okno wzorca mieści się
wewnątrz łańcucha
K06: j ← m - 1 ; porównywanie znaków wzorca z
łańcuchem rozpoczynamy od końca
K07: Dopóki (j > -1) (p[j] = s[i+j]), wykonuj j ← j - 1 ; pętla wykonuje się, gdy pozycja j jest w
obrębie wzorca oraz znak
; wzorca pasuje do znaku okna wzorca w
łańcuchu s.
K08: Jeśli j = -1, to idź do K11 ; cały wzorzec pasuje do okna?
K09: i ← i + max(BMNext[j + 1], j - Last[kod(s[i + j]) - zp]) ; jeśli nie, to pozycję okna wzorca
zwiększamy o większe z przesunięć
; pasującego sufiksu lub do ostatniej
pozycji nie pasującego znaku
K10: Następny obieg pętli K05

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 344 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

K11: pp ← i ; tutaj jesteśmy, gdy wzorzec pasuje do


okna
K12: Pisz pp ; wyprowadzamy pozycję wzorca p w
łańcuchu s
K13: i ← i + BMNext[0] ; przesuwamy okno na następną możliwą
pozycję i kontynuujemy
K14: Jeśli pp = -1, to pisz -1 ; sprawdzamy, czy znaleziono pozycję
wzorca. Jeśli nie, to wynik -1
K15: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A i B oraz 5 znakowy wzorzec
wg tego samego schematu. Następnie program wyznacza tablicę długości maksymalnych prefikso-sufiksów
kolejnych prefiksów wzorca i wykorzystuje ją do znalezienia wszystkich wystąpień wzorca w łańcuchu.

Lazarus

// Wyszukiwanie wzorca pełnym algorytmem BM


// Data: 10.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------------------
program prg;
uses Math;
const
N = 80; // długość łańcucha s
M = 5; // długość wzorca p
zp = 65; // kod pierwszego znaku alfabetu
zk = 66; // kod ostatniego znaku alfabetu
var
s,p : ansistring;
Last : array[0..zk-zp] of integer;
BMNext,Pi : array[0..M] of integer;
b,i,j,pp : integer;
begin
randomize;
// generujemy łańcuch s
s := '';
for i := 1 to N do
s := s + chr(zp + random(zk - zp + 1));
// generujemy wzorzec p
p := '';
for i := 1 to M do
p := p + chr(zp + random(zk - zp + 1));
// wypisujemy wzorzec
writeln(p);
// wypisujemy łańcuch
write(s);
// dla wzorca obliczamy tablicę Last[]
for i := 0 to zk - zp do Last[i] := 0;
for i := 1 to M do Last[ord(p[i]) - zp] := i;
// Etap I obliczania tablicy BMNext[]
for i := 0 to M do BMNext[i] := 0;
i := M; b := M + 1; Pi[i] := b;
while i > 0 do
begin
while (b <= M) and (p[i] <> p[b]) do
begin
if BMNext[b] = 0 then BMNext[b] := b - i;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 345 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

b := Pi[b];
end;
dec(b); dec(i); Pi[i] := b;
end;
// Etap II obliczania tablicy BMNext[]
b := Pi[0];
for i := 0 to M do
begin
if BMNext[i] = 0 then BMNext[i] := b;
if i = b then b := Pi[b];
end;
// szukamy pozycji wzorca w łańcuchu
pp := 1; i := 1;
while i <= N - M + 1 do
begin
j := M;
while (j > 0) and (p[j] = s[i + j - 1]) do dec(j);
if j = 0 then
begin
while pp < i do
begin
write(' '); inc(pp);
end;
write('^'); inc(pp);
inc(i,BMNext[0]);
end
else
inc(i,max(BMNext[j],j-Last[ord(s[i+j-1])-zp]));
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie wzorca pełnym algorytmem BM


// Data: 10.06.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80; // długość łańcucha s
const int M = 5; // długość wzorca p
const int zp = 65; // kod pierwszego znaku alfabetu
const int zk = 66; // kod ostatniego znaku alfabetu
int main()
{
string s,p;
int Last[zk-zp + 1],BMNext[M+1],Pi[M+1],b,i,j,pp;
srand((unsigned)time(NULL));
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++)
s += zp + rand() % (zk - zp + 1);
// generujemy wzorzec p
p = "";
for(i = 0; i < M; i++)
p += zp + rand() % (zk - zp + 1);
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch
cout << s;
// dla wzorca obliczamy tablicę Last[]
for(i = 0; i <= zk - zp; i++) Last[i] = -1;
for(i = 0; i < M; i++) Last[p[i] - zp] = i;
// Etap I obliczania tablicy BMNext[]

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 346 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

for(i = 0; i <= M; i++) BMNext[i] = 0;


i = M; b = M + 1; Pi[i] = b;
while(i > 0)
{
while((b <= M) && (p[i - 1] != p[b - 1]))
{
if(BMNext[b] == 0) BMNext[b] = b - i;
b = Pi[b];
}
Pi[--i] = --b;
}
// Etap II obliczania tablicy BMNext[]
b = Pi[0];
for(i = 0; i <= M; i++)
{
if(BMNext[i] == 0) BMNext[i] = b;
if(i == b) b = Pi[b];
}
// szukamy pozycji wzorca w łańcuchu
pp = i = 0;
while(i <= N - M)
{
j = M - 1;
while((j > -1) && (p[j] == s[i + j])) j--;
if(j == -1)
{
while(pp < i)
{
cout << " "; pp++;
}
cout << "^"; pp++;
i += BMNext[0];
}
else i += max(BMNext[j+1],j-Last[s[i+j]-zp]);
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie wzorca pełnym algorytmem BM


' Data: 10.06.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------------------
Const N = 80 ' długość łańcucha s
Const M = 5 ' długość wzorca p
Const zp = 65 ' kod pierwszego znaku alfabetu
Const zk = 66 ' kod ostatniego znaku alfabetu
' Funkcja wyznacza większą z dwóch liczb
'---------------------------------------
Function max(Byval a As Integer, Byval b As Integer) As Integer
If a > b Then max = a Else max = b
End Function
Dim As String s,p
Dim As Integer Last(zk - zp),BMNext(M),Pi(M),b,i,j,pp
Randomize
' generujemy łańcuch s
s = ""
For i = 1 To N: s += Chr(zp + Cint(Rnd * (zk - zp))): Next
' generujemy wzorzec p
p = ""
For i = 1 To M: p += Chr(zp + Cint(Rnd * (zk - zp))): Next
' wypisujemy wzorzec
Print p
' wypisujemy łańcuch
Print s;
' dla wzorca obliczamy tablicę Last[]
For i = 0 To zk - zp: Last(i) = 0: Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 347 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

For i = 1 To M: Last(Asc(Mid(p,i,1)) - zp) = i: Next


' Etap I obliczania tablicy BMNext[]
For i = 0 To M: BMNext(i) = 0: Next
i = M: b = M + 1: Pi(i) = b
While i > 0
While (b <= M) And (Mid(p,i,1) <> Mid(p,b,1))
If BMNext(b) = 0 Then BMNext(b) = b - i
b = Pi(b)
Wend
b -= 1: i -= 1: Pi(i) = b
Wend
' Etap II obliczania tablicy BMNext[]
b = Pi(0)
For i = 0 To M
If BMNext(i) = 0 Then BMNext(i) = b
If i = b Then b = Pi(b)
Next
' szukamy pozycji wzorca w łańcuchu
pp = 1: i = 1
While i <= N - M + 1
j = M
While (j > 0) And (Mid(p,j,1) = Mid(s,i + j - 1,1)): j -= 1: Wend
If j = 0 Then
While pp < i
Print " ";: pp += 1
Wend
Print "^";: pp += 1
i += BMNext(0)
Else
i += max(BMNext(j),j - Last(Asc(Mid(s,i + j - 1,1)) - zp))
End If
Wend
Print
End

Wynik
ABBBA
BBAABBBAABBBAABAAABBBABBBAAABABBAAABBAAAABBABAAABBAABAABBBBBBBBAABABBAAABAAAABBB
^ ^ ^ ^

Wyszukiwanie wzorca pełnym algorytmem BM


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 348 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca pełnym algorytmem BM 2014-10-03

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0051.php 349 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie wzorca algorytmem Karpa-Rabina


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.

W celu znalezienia wzorca p w łańcuchu s algorytm naiwny porównuje każdorazowo zawartość okna wzorca ze wzorcem.
Prowadzi to do kwadratowej klasy złożoności obliczeniowej O(n2). W algorytmie Karpa-Rabina postępujemy nieco inaczej.
Najpierw odwzorowujemy poszukiwany wzorzec p w liczbę całkowitą Hp za pomocą tzw. funkcji haszującej (ang. hash function).
Funkcja haszująca posiada taką własność, iż identyczne łańcuchy znakowe odwzorowuje w identyczne liczby – hasze. Następnie
ustawiamy okno wzorca na początku tekstu i haszujemy je przy pomocy tej samej funkcji w liczbę całkowitą Hs. Porównujemy ze
sobą liczby Hp i Hs. Jeśli są równe, to oznacza to, iż wzorzec i jego okno są do siebie podobne z dokładnością do haszów. Aby
mieć pewność, iż są identyczne, sprawdzamy je znak po znaku, podobnie jak w algorytmie naiwnym, lecz teraz nie wykonujemy
tego każdorazowo, tylko wtedy, gdy jest zgodność haszy. Jeśli hasze nie są zgodne (lub chcemy znaleźć następne wystąpienia
wzorca), to okno wzorca przesuwamy o jedną pozycję w obrębie łańcucha s, ponownie haszujemy zawartość okna i otrzymaną
nową liczbę Hs porównujemy z haszem wzorca Hp. Zatem cała procedura się powtarza. Gdy okno wzorca wyjdzie poza łańcuch,
przeszukiwanie przerywamy.
Funkcja haszująca najczęściej traktuje przetwarzany łańcuch tekstu jak liczbę, zapisaną przy wybranej podstawie (zwykle
podstawa jest równa rozmiarowi alfabetu). Kolejne znaki tekstu są traktowane jak cyfry tej liczby. Dodatkowo w celu uniknięcia
zbyt dużych wartości haszu stosowana jest arytmetyka modularna – wyniki wszystkich działań są sprowadzane do reszty z

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 350 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

dzielenia przez wybrany moduł, który jest liczbą pierwszą. Aby zrozumieć zasadę wyznaczania haszu, przyjrzyjmy się prostemu
przykładowi.

Przykład:
Alfabet składa się z trzech liter: A, B i C.
Wyznaczyć hasz dla tekstu: "CBBAB".
Jako bazę systemu liczbowego przyjmiemy 3, ponieważ z tylu liter składa się alfabet. Cyfry posiadają wartości: A =
0, B = 1 i C = 2. Jako moduł przyjmiemy liczbę pierwszą 23. Wtedy:

H[CBBAB] = (2 × 34 + 1 × 33 + 1 × 32 + 0 × 31 + 1 × 30) mod 23


H[CBBAB] = (2 × 81 + 1 × 27 + 1 × 9 + 0 × 3 + 1 × 1) mod 23
H[CBBAB] = (162 + 27 + 9 + 0 + 1) mod 23
H[CBBAB] = 199 mod 23
H[CBBAB] = 15

Funkcja haszująca powinna być tak dobrana, aby w prosty sposób można było wyznaczyć hasz okna wzorca po przesunięciu o
jedną pozycję w obrębie przeszukiwanego łańcucha s. Podana w poprzednim przykładzie funkcja spełnia ten warunek.

Przykład:
Tekst ma postać "CBBABB". Wyznaczyliśmy hasz pierwszych pięciu liter, czyli H[CBBAB] = 15. Teraz przesuwamy
okno o jedną pozycję w prawo. Okno zawiera tekst "BBABB". Wyznaczymy H[BBABC] na podstawie poprzednio
wyznaczonego haszu H[CBBAB].

Najpierw pozbywamy się z haszu najstarszej cyfry, czyli C = 2. W tym celu wyznaczamy mnożnik d = 34 mod 23 =
12. Od haszu H[CBBAB] odejmujemy modularnie iloczyn d i cyfry C:

H = (H[CBBAB] - d × 2) mod 23
H = (15 - 12 × 2) mod 23
H = (-9) mod 23
H = 14

Teraz otrzymany hasz H mnożymy modularnie przez 3 – spowoduje to przesunięcie w nim wszystkich cyfr o jedną
pozycję w lewo:

H = (H × 3) mod 23
H = (14 × 3) mod 23
H = 42 mod 23
H = 19

Na koniec do haszu H dodajemy modularnie nową cyfrę B:

H = (H + 1) mod 23
H = (19 + 1) mod 23
H = 20 mod 23
H = 20

Sprawdźmy, czy otrzymaliśmy H[BBABB]:

H[BBABB] = (1 × 34 + 1 × 33 + 0 × 32 + 1 × 31 + 1 × 30) mod 23


H[BBABB] = (81 + 27 + 0 + 3 + 1) mod 23
H[BBABB] = 112 mod 23
H[BBABB] = 20 = H

Zwróć uwagę, iż wyznaczenie haszu okna po przesunięciu wymaga stałej liczby operacji, zatem jest wykonywane w czasie stałym
O(1). Co więcej, operacje dodawania i odejmowania można z powodzeniem zastąpić operacją logiczną xor, mnożenie
przesunięciami bitów, a operację modulo obcinaniem bitowym wyniku do długości słowa maszynowego – np do 32 bitów.
Rozstrzygnięcie tych kwestii pozostawiamy już konkretnej implementacji algorytmu Karpa-Rabina.
Moduł powinien być względnie dużą liczbą, aby ograniczyć liczbę fałszywych zgodności haszów wzorca i jego okna.
Typowo algorytm Karpa-Rabina pracuje w liniowej klasie złożoności obliczeniowej O(m+n), zatem dla długich tekstów ma on

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 351 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

zdecydowaną przewagę nad algorytmem naiwnym.

Algorytm Karpa-Rabina wyszukiwania wzorca


Wejście:
s – przeszukiwany łańcuch
p – poszukiwany wzorzec

Wyjście:
Kolejne pozycje wystąpień wzorca w łańcuchu.
Elementy pomocnicze:
i – położenie okna wzorca p w przeszukiwanym łańcuchu s, i N
m – długość wzorca p, m N
n – długość łańcucha s, n N
Hp – hasz wzorca
Hs – hasz okna wzorca
h(x) – wybrana funkcja haszująca
Lista kroków:
K01: m ← |p| ; obliczamy liczbę znaków wzorca
K02: n ← |s| ; oraz liczbę znaków łańcucha
K03: Hp ← h(p) ; obliczamy hasz wzorca
K04: Hs ← h(s[0:m]) ; obliczamy hasz okna
K05: i ← 0 ; ustawiamy pozycję okna
K06: Jeśli Hs ≠ Hp, to idź do K08 ; sprawdzamy równość haszy
K07: Jeśli p = s[i:i+m], przetwarzaj i ; znaleziono wzorzec p w s na pozycji i-tej
K08: i ← i + 1 ; przesuń okno wzorca o jedną pozycję w prawo w łańcuchu s
K09: Jeśli i = n - m, to zakończ ; koniec łańcucha?
K10: Oblicz nowe Hs dla s[i:i+m] ; wyznaczamy hasz przesuniętego okna, wykorzystując
poprzedni hasz
K11: Idź do K06 ; kontynuujemy pętlę

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 80 znakowy łańcuch zbudowany z pseudolosowych kombinacji liter A, B i C oraz 4 znakowy
wzorzec wg tego samego schematu. Funkcja haszująca ma postać:

h(s) = c × 33 + c × 32 + c × 31 + c × 30,
gdzie c oznacza cyfrę trójkową uzyskaną z liter łańcucha przez odjęcie od ich kodu liczby 65.

Ponieważ 4 cyfrowa liczba trójkowa posiada największą wartość 2222(3) = 80(10) i mieści się bez problemu w typie
int, zrezygnowaliśmy z arytmetyki modulo.

Lazarus

// Wyszukiwanie wzorca algorytmem KR


// Data: 4.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const
N = 80; // długość łańcucha s
M = 4; // długość wzorca p
zp = 65; // kod pierwszego znaku alfabetu
zk = 67; // kod ostatniego znaku alfabetu
// Funkcja obliczająca hasz dla łańcucha x
//----------------------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 352 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

function h(x : ansistring) : integer;


var
i,hx : integer;
begin
hx := 0;
for i := 1 to M do
hx := 3 * hx + (ord(x[i]) - 65);
h := hx;
end;
var
s,p : ansistring;
pp,i,Hp,Hs : integer;
begin
randomize;
// generujemy łańcuch s
s := '';
for i := 1 to N do
s := s + chr(zp + random(zk - zp + 1));
// generujemy wzorzec
p := '';
for i := 1 to M do
p := p + chr(zp + random(zk - zp + 1));
// wypisujemy wzorzec
writeln(p);
// wypisujemy łańcuch
write(s);
// obliczamy hasz wzorca
Hp := h(p);
// obliczamy hasz okna wzorca
Hs := h(s);
// szukamy pozycji wzorca w łańcuchu
pp := 1; i := 1;
while true do
begin
if (Hp = Hs) and (p = copy(s,i,M)) then
begin
while pp < i do
begin
write(' '); inc(pp);
end;
write('^'); inc(pp);
end;
inc(i);
if i = N - M + 2 then break;
Hs := (Hs-(ord(s[i-1])-65)*27)*3+ord(s[i+M-1])-65;
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie wzorca algorytmem KR


// Data: 4.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 80; // długość łańcucha s
const int M = 4; // długość wzorca p
const int zp = 65; // kod pierwszego znaku alfabetu
const int zk = 67; // kod ostatniego znaku alfabetu
// Funkcja obliczająca hasz dla łańcucha x
//----------------------------------------
int h(string & x)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 353 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

{
int i, hx;
hx = 0;
for(i = 0; i < M; i++)
hx = 3 * hx + (x[i] - 65);
return hx;
}
int main()
{
string s,p;
int pp,i,Hp,Hs;
srand((unsigned)time(NULL));
// generujemy łańcuch s
s = "";
for(i = 0; i < N; i++)
s += zp + rand() % (zk - zp + 1);
// generujemy wzorzec p
p = "";
for(i = 0; i < M; i++)
p += zp + rand() % (zk - zp + 1);
// wypisujemy wzorzec
cout << p << endl;
// wypisujemy łańcuch
cout << s;
// obliczamy hasz wzorca
Hp = h(p);
// obliczamy hasz okna wzorca
Hs = h(s);
// szukamy pozycji wzorca w łańcuchu
pp = i = 0;
while(true)
{
if((Hp == Hs) && (p == s.substr(i,M)))
{
while(pp < i)
{
cout << " "; pp++;
}
cout << "^"; pp++;
}
i++;
if(i == N - M) break;
Hs = (Hs-(s[i-1]-65)*27)*3+s[i+M-1]-65;
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie wzorca algorytmem KR


' Data: 4.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 80 ' długość łańcucha s
Const M = 4 ' długość wzorca p
Const zp = 65 ' kod pierwszego znaku alfabetu
Const zk = 67 ' kod ostatniego znaku alfabetu
' Funkcja obliczająca hasz dla łańcucha x
'----------------------------------------
Function h(Byref x As String) As Integer
Dim As Integer i,hx
hx = 0
For i = 1 To M
hx = 3 * hx + Asc(Mid(x,i,1)) - 65

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 354 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

Next
h = hx
End Function

Dim As String s,p


Dim As Integer pp,i,Hp,Hs
Randomize
' generujemy łańcuch s
s = ""
For i = 1 To N: s += Chr(zp + Cint(Rnd * (zk - zp))): Next
' generujemy wzorzec p
p = ""
For i = 1 To M: p += Chr(zp + Cint(Rnd * (zk - zp))): Next
' wypisujemy wzorzec
Print p
' wypisujemy łańcuch
Print s;
' obliczamy hasz wzorca
Hp = h(p)
' obliczamy hasz okna wzorca
Hs = h(s)
' szukamy pozycji wzorca w łańcuchu
pp = 1: i = 1
Do
If (Hp = Hs) And (p = Mid(s,i,M)) Then
While pp < i
Print " ";: pp += 1
Wend
Print "^";: pp += 1
End If
i += 1
If i = N - M + 2 Then Exit Do
Hs = (Hs-(Asc(Mid(s,i-1,1))-65)*27)*3+Asc(Mid(s,i+M-1,1))-65
Loop
Print
End

Wynik
BABC
CCABABABCBBBCAABCBCCAAAAACBABAABCABBBCCCCBCABACBBBBAABCBABCBACCACBBBABCBAAABBBAB
^ ^ ^

Wyszukiwanie wzorca algorytmem KR


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 355 / 519


Algorytmy i Struktury Danych - Wyszukiwanie wzorca algorytmem KR 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0052.php 356 / 519


Algorytmy i Struktury Danych - Zliczanie słów 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Zliczanie słów w łańcuchu tekstowym


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie liniowe

Problem
W łańcuchu s wyznaczyć liczbę wszystkich słów.

Zadanie zliczenia słów (ang. words counting) sprowadza się do wyszukiwania liniowego znaków. Na początku pracy algorytmu
ustawiamy znacznik słów t na false. Wartość true tego znacznika oznacza przetwarzanie znaków słowa. Licznik słów ls zerujemy.
Teraz w pętli przeglądamy kolejne znaki łańcucha s. Jeśli napotkanym znakiem jest znak litery lub cyfry, to sprawdzamy stan
znacznika t. Jeśli jest on ustawiony na false, to znaczy, iż napotkaliśmy w tekście początek słowa. W takim przypadku
ustawiamy t na true i zwiększamy o 1 licznik ls. Jeśli znacznik t jest już ustawiony na true, to napotkaliśmy kolejną literę już
zliczonego słowa – nic nie robimy. Jeśli napotkamy inny znak, to traktujemy go jako separator i znacznik t zawsze zerujemy. Po
przeglądnięciu wszystkich znaków łańcucha s w zmiennej ls mamy liczbę słów.

Algorytm zliczania wyrazów


Wejście:

s – łańcuch tekstowy.

Wyjście:
Liczba słów zawartych w łańcuchu s.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0053.php 357 / 519


Algorytmy i Struktury Danych - Zliczanie słów 2014-10-03

Elementy pomocnicze:
i – indeks znaków w łańcuchu s, i N
ls – licznik słów, ls N
t – znacznik słowa

Lista kroków:
K01: ls ← 0 ; zerujemy licznik słów
K02: t ← false ; zerujemy znacznik słowa
K03: Dla i = 0,1,...,|s| - 1 wykonuj K04...K09 ; przeglądamy znaki łańcucha s
K04: Jeśli s[i] = cyfra_lub_litera, idź do K07
K05: t ← false ; zerujemy znacznik słowa
K06: Następny obieg pętli K03
K07: Jeśli t = true, następny obieg pętli K03 ; słowo już zliczone
K08: t ← true ; ustawiamy znacznik słowa
K09: ls ← ls + 1 ; zliczamy słowo
K10: Zakończ z wynikiem ls

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje wiersz znaków, a następnie zlicza występujące w nim wyrazy i wypisuje ich ilość.

Lazarus

// Zliczanie wyrazów w łańcuchu tekstowym


// Data: 5.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : ansistring;
i,ls : integer;
t : boolean;
begin
readln(s);
t := false; ls := 0;
for i := 1 to length(s) do
if s[i] in ['0'..'9','_','-',
'A'..'Z','a'..'z',
'Ą','ą','Ć','ć','Ę','ę',
'Ł','ł','Ń','ń','Ó','ó',
'Ś','ś','Ź','ź','Ż','ż'] then
begin
if not t then
begin
t := true; inc(ls);
end
end
else t := false;
writeln(ls);
writeln;
end.

Code::Blocks

// Zliczanie wyrazów w łańcuchu tekstowym


// Data: 5.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i,ls,n;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0053.php 358 / 519


Algorytmy i Struktury Danych - Zliczanie słów 2014-10-03

unsigned char c;
bool t;
getline(cin,s);
n = s.length(); t = false; ls = 0;
for(i = 0; i < n; i++)
{
c = s[i];
if(((c >= '0') && (c <= '9')) || (c == '_') || (c == '-') ||
((c >= 'A') && (c <= 'Z')) ||((c >= 'a') && (c <= 'z')) ||
(c == 164) || (c == 165) || (c == 143) || (c == 134) ||
(c == 168) || (c == 169) || (c == 157) || (c == 136) ||
(c == 227) || (c == 228) || (c == 224) || (c == 162) ||
(c == 151) || (c == 152) || (c == 141) || (c == 171) ||
(c == 189) || (c == 190))
{
if(!t)
{
t = true; ls++;
}
}
else t = false;
}
cout << ls << endl << endl;
return 0;
}

Free Basic

' Zliczanie wyrazów w łańcuchu tekstowym


' Data: 5.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Uinteger i,ls,n,t,c
Line Input s
n = Len(s): t = 0: ls = 0
For i = 1 To n
c = Asc(Mid(s,i,1))
If(((c >= Asc("0")) And (c <= Asc("9"))) Or (c = Asc("_")) Or (c = Asc("-")) Or _
((c >= Asc("A")) And (c <= Asc("Z"))) Or((c >= Asc("a")) And (c <= Asc("z"))) Or _
(c = 164) Or (c = 165) Or (c = 143) Or (c = 134) Or (c = 168) Or (c = 169) Or _
(c = 157) Or (c = 136) Or (c = 227) Or (c = 228) Or (c = 224) Or (c = 162) Or _
(c = 151) Or (c = 152) Or (c = 141) Or (c = 171) Or (c = 189) Or (c = 190)) Then
If t = 0 Then
t = 1: ls += 1
End If
Else
t = 0
End If
Next
Print ls
Print
End

Wynik
Ala ma krokodyla z plastiku i gumy, ale co to nas obchodzi.
12

Zliczanie wyrazów w łańcuchu tekstowym


(C)2012 mgr Jerzy Wałaszek

Wprowadź tekst:
Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0053.php 359 / 519


Algorytmy i Struktury Danych - Zliczanie słów 2014-10-03

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0053.php 360 / 519


Algorytmy i Struktury Danych - Dzielenie łańcucha na słowa 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Dzielenie łańcucha tekstowego na słowa


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem

Problem
Podzielić dany łańcuch tekstowy s na zawarte w nim słowa.

Operacja podziału łańcucha znaków na zawarte w nim słowa (ang. splitting into words) jest często wykonywana jako wstęp do
różnych algorytmów tekstowych.
W naszym prostym algorytmie w pętli będą wydobywane kolejne słowa z łańcucha s. Słowa te mogą być następnie odpowiednio
przetworzone przez inny algorytm. Zasada pracy jest następująca:

Tworzymy pusty łańcuch ss, w którym będą gromadzone znaki wydobywanego słowa. Na końcu łańcucha s
umieszczamy wartownika – dowolny znak nie będący literą ani cyfrą – może to być np. spacja. Wartownik
zagwarantuje nam przetworzenie wszystkich słów łańcucha s. Następnie przeglądamy kolejne znaki łańcucha s.
Jeśli przeglądany znak jest literą lub cyfrą, to dołączamy go do łańcucha ss. W przeciwnym razie, jeśli łańcuch ss
zawiera znaki, przetwarzamy je jako słowo, po czym łańcuch ss zerujemy – będzie on gotowy na przyjęcie nowych
znaków dla kolejnego słowa.

Algorytm podziału łańcucha na słowa


Wejście:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0054.php 361 / 519


Algorytmy i Struktury Danych - Dzielenie łańcucha na słowa 2014-10-03

s – łańcuch tekstowy.

Wyjście:
kolejne słowa zawarte w łańcuchu s.
Elementy pomocnicze:
i – indeks znaków w łańcuchu s, i N
ss – łańcuch zawierający kolejne słowa z łańcucha s

Lista kroków:
K01: ss ← "" ; zerujemy łańcuch słowa
K02: s ← s + wartownik ; na końcu łańcucha s umieszczamy wartownika
K03: Dla i = 0,1,...,|s| - 1 wykonuj K04...K09 ; przeglądamy kolejne znaki łańcucha s
K04: Jeśli s[i] = cyfra_lub_litera, to idź do K09 ; litery i cyfry dołączamy do łańcucha ss
K05: Jeśli ss = "", to następny obieg pętli K03 ; sprawdzamy, czy ss zawiera jakieś słowo
K06: Przetwarzaj ss ; przetwarzamy wydobyte słowo
K07: ss ← "" ; zerujemy ss dla następnego słowa
K08: Następny obieg pętli K03
K09: ss ← ss + s[i] ; dołączamy znak s[i] do łańcucha ss
K10: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje wiersz znaków i wypisuje zawarte w nim słowa w klamerkach.

Lazarus

// Podział łańcucha tekstowego na słowa


// Data: 6.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s,ss : ansistring;
i : integer;
begin
readln(s);
ss := '';
s := s + ' '; // dodajemy wartownika
for i := 1 to length(s) do
if s[i] in ['0'..'9','_','-',
'A'..'Z','a'..'z',
'Ą','ą','Ć','ć','Ę','ę',
'Ł','ł','Ń','ń','Ó','ó',
'Ś','ś','Ź','ź','Ż','ż'] then
ss := ss + s[i]
else if ss <> '' then
begin
writeln('[',ss,']'); ss := '';
end;
writeln;
end.

Code::Blocks

// Podział łańcucha tekstowego na słowa


// Data: 6.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0054.php 362 / 519


Algorytmy i Struktury Danych - Dzielenie łańcucha na słowa 2014-10-03

string s,ss;
int i,n;
unsigned char c;
getline(cin,s);
ss = "";
s += " "; // dodajemy wartownika
n = s.length();
for(i = 0; i < n; i++)
{
c = s[i];
if(((c >= '0') && (c <= '9')) || (c == '_') || (c == '-') ||
((c >= 'A') && (c <= 'Z')) ||((c >= 'a') && (c <= 'z')) ||
(c == 164) || (c == 165) || (c == 143) || (c == 134) ||
(c == 168) || (c == 169) || (c == 157) || (c == 136) ||
(c == 227) || (c == 228) || (c == 224) || (c == 162) ||
(c == 151) || (c == 152) || (c == 141) || (c == 171) ||
(c == 189) || (c == 190))
ss += c;
else if(ss != "")
{
cout << "[" << ss << "]\n";
ss = "";
}
}
return 0;
}

Free Basic

' Podział łańcucha tekstowego na słowa


' Data: 6.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,ss
Dim As Uinteger i,n,c
Line Input s
ss = ""
s += " " 'dodajemy wartownika
n = Len(s)
For i = 1 To n
c = Asc(Mid(s,i,1))
If(((c >= Asc("0")) And (c <= Asc("9"))) Or (c = Asc("_")) Or (c = Asc("-")) Or _
((c >= Asc("A")) And (c <= Asc("Z"))) Or((c >= Asc("a")) And (c <= Asc("z"))) Or _
(c = 164) Or (c = 165) Or (c = 143) Or (c = 134) Or (c = 168) Or (c = 169) Or _
(c = 157) Or (c = 136) Or (c = 227) Or (c = 228) Or (c = 224) Or (c = 162) Or _
(c = 151) Or (c = 152) Or (c = 141) Or (c = 171) Or (c = 189) Or (c = 190)) Then
ss += Chr(c)
Elseif ss <> "" Then
Print "[";ss;"]"
ss = ""
End If
Next
Print
End

Wynik
Człowiek nie lubi pracować, bo i po co miałby to robić?
[Człowiek]
[nie]
[lubi]
[pracować]
[bo]
[i]
[po]
[co]
[miałby]
[to]
[robić]

Podział łańcucha tekstowego na słowa


(C)2012 mgr Jerzy Wałaszek
Wprowadź tekst:
Wykonaj

...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0054.php 363 / 519


Algorytmy i Struktury Danych - Dzielenie łańcucha na słowa 2014-10-03

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0054.php 364 / 519


Algorytmy i Struktury Danych - Najdłuższe słowo 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie najdłuższego słowa w łańcuchu


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Wyszukiwanie max lub min

Problem
W łańcuchu s znaleźć najdłuższe słowo.

Najdłuższe słowo łańcucha (ang. the longest word of s string) s możemy znaleźć w czasie liniowym O(n) wykorzystując nieco
zmodyfikowany algorytm podziału łańcucha na słowa działający zgodnie z algorytmem znajdowania wartości maksymalnej w
zbiorze. Zasada jest następująca:

Przeglądamy łańcuch s wydobywając z niego słowa. W trakcie tego procesu obliczamy liczbę znaków w każdym z
wydobytych słów. Liczby te tworzą zbiór, w którym wyszukujemy element maksymalny. Zapamiętujemy pozycję
pierwszego znaku słowa raz jego długość.

Algorytm wyszukiwania najdłuższego słowa w łańcuchu


Wejście:

s – łańcuch tekstowy.

Wyjście:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0055.php 365 / 519


Algorytmy i Struktury Danych - Najdłuższe słowo 2014-10-03

Pozycja pierwszego znaku najdłuższego słowa pmax w łańcuchu s oraz liczba znaków lmax w tym
słowie. Jeśli pmax jest równe -1, to łańcuch s nie zawiera żadnego słowa.

Elementy pomocnicze:
i – indeks znaków w łańcuchu s, i N.
pm – pozycja początku słowa, pm C.
lm – liczba znaków w słowie, lm C.

Lista kroków:
K01: s ← s + wartownik ; dołączamy wartownika
K02: pmax ← -1 ; inicjujemy zmienne
K03: lmax ← 0
K04: lm ← 0
K05: Dla i = 0, 1,...,|s| - 1, wykonuj K06...K14 ; przeglądamy kolejne znaki łańcucha
K06: Jeśli s[i] = litera_lub_cyfra, to idź do K12 ; znak należący do słowa?
K07: Jeśli lm ≤ lmax, to idź do K10 ; dłuższe słowo niż dotychczas zapamiętane?
K08: lmax ← lm ; jeśli tak, to zapamiętujemy go
K09: pmax ← pm
K10: lm ← 0 ; po zapamiętaniu, zerujemy długość słowa
K11: Następny obieg pętli K05
K12: Jeśli lm > 0, to idź do K15 ; początek słowa?
K13: pm ← i ; jeśli tak, to zapamiętujemy pozycję pierwszego
znaku
K14: lm ← lm + 1 ; a długość ustawiamy na 1
K15 Zakończ z wynikiem pmax i lmax

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje wiersz znaków i wypisuje najdłuższe, zawarte w nim słowo oraz liczbę znaków w tym słowie.
Jeśli łańcuch nie zawiera żadnego słowa, pojawia się napis BRAK.

Lazarus

// Wyszukiwanie najdłuższego słowa


// Data: 9.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : ansistring;
i,pmax,lmax,pm,lm: integer;
begin
readln(s);
s := s + ' '; // dodajemy wartownika
pmax := -1; lmax := 0; lm := 0;
for i := 1 to length(s) do
if s[i] in ['0'..'9','_','-',
'A'..'Z','a'..'z',
'Ą','ą','Ć','ć','Ę','ę',
'Ł','ł','Ń','ń','Ó','ó',
'Ś','ś','Ź','ź','Ż','ż'] then
begin
if lm = 0 then pm := i;
inc(lm);
end
else
begin
if lm > lmax then
begin
lmax := lm; pmax := pm;
end;
lm := 0;
end;
if pmax = -1 then writeln('BRAK')

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0055.php 366 / 519


Algorytmy i Struktury Danych - Najdłuższe słowo 2014-10-03

else writeln('[',copy(s,pmax,lmax),'] : ',lmax);


writeln;
end.

Code::Blocks

// Wyszukiwanie najdłuższego słowa


// Data: 9.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i,pmax,lmax,pm,lm,n;
unsigned char c;
getline(cin,s);
s += " "; // dodajemy wartownika
n = s.length();
pmax = -1; lmax = lm = 0;
for(i = 0; i < n; i++)
{
c = s[i];
if(((c >= '0') && (c <= '9')) ||
(c == '_') || (c == '-') ||
((c >= 'A') && (c <= 'Z')) ||
((c >= 'a') && (c <= 'z')) ||
(c == 164) || (c == 165) ||
(c == 143) || (c == 134) ||
(c == 168) || (c == 169) ||
(c == 157) || (c == 136) ||
(c == 227) || (c == 228) ||
(c == 224) || (c == 162) ||
(c == 151) || (c == 152) ||
(c == 141) || (c == 171) ||
(c == 189) || (c == 190))
{
if(!lm) pm = i;
lm++;
}
else
{
if(lm > lmax)
{
lmax = lm; pmax = pm;
}
lm = 0;
}
}
if(pmax == -1) cout << "BRAK\n";
else cout << "[" << s.substr(pmax,lmax) << "] : " << lmax << endl;
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie najdłuższego słowa


' Data: 9.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Integer i,pmax,lmax,pm,lm,n,c
Line Input s
s += " " 'dodajemy wartownika
n = Len(s)
pmax = -1: lmax = 0: lm = 0
For i = 1 To n
c = Asc(Mid(s,i,1))
If(((c >= Asc("0")) And (c <= Asc("9"))) Or (c = Asc("_")) Or (c = Asc("-")) Or _
((c >= Asc("A")) And (c <= Asc("Z"))) Or((c >= Asc("a")) And (c <= Asc("z"))) Or _
(c = 164) Or (c = 165) Or (c = 143) Or (c = 134) Or (c = 168) Or (c = 169) Or _
(c = 157) Or (c = 136) Or (c = 227) Or (c = 228) Or (c = 224) Or (c = 162) Or _
(c = 151) Or (c = 152) Or (c = 141) Or (c = 171) Or (c = 189) Or (c = 190)) Then
If lm = 0 Then pm = i
lm += 1
Else
If lm > lmax Then
lmax = lm: pmax = pm

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0055.php 367 / 519


Algorytmy i Struktury Danych - Najdłuższe słowo 2014-10-03

End If
lm = 0
End If
Next
If pmax = -1 Then
Print "BRAK"
Else
Print "[";Mid(s,pmax,lmax);"] : ";lmax
End If
Print
End

Wynik
Wielki mały cały śmiały człowiek szybki i gibki
[człowiek] : 8

Wyszukiwanie najdłuższego słowa


(C)2012 mgr Jerzy Wałaszek

Wprowadź tekst:
Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0055.php 368 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie najdłuższego wspólnego podłańcucha


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie liniowe
Wyszukiwanie liniowe z wartownikiem
Wyszukiwanie max lub min

Problem
Dla danych, niepustych łańcuchów tekstowych s1 i s2 znaleźć najdłuższy wspólny podłańcuch s.

W porównaniu z poprzednimi zadaniami ten problem jest dosyć trudny, ponieważ nie wiemy, czego szukamy. Nie możemy zatem
w normalny sposób sprawdzić, czy to coś występuje jednocześnie w łańcuchu s1 i s2. Problem wyszukiwania wspólnych
sekwencji symboli jest szeroko wykorzystywany w badaniach genetycznych łańcuchów DNA.

Rozwiązanie nr 1
Pierwsze rozwiązanie problemu wyszukiwania najdłuższego wspólnego podłańcucha (ang. the longest common substring –
LCS) jest rozwiązaniem nieoptymalnym. Nasz algorytm będzie się starał dopasowywać jak największe fragmenty łańcucha s1 do
fragmentów łańcucha s2 zapamiętując najdłuższy, wyznaczony w ten sposób podłańcuch. Zasada jego pracy będzie następująca:

Wybieramy kolejne znaki łańcucha s1. Wyszukujemy w s2 wszystkie wystąpienia wybranego znaku. Po znalezieniu
zgodności staramy się maksymalnie rozszerzyć pasujący fragment w prawo. Długości wyznaczonych w ten sposób
wspólnych podciągów tworzą ciąg liczbowy, w którym wyszukujemy wartość maksymalną. Zapamiętujemy położenie
podciągu oraz jego długość. Po przetworzeniu wszystkich znaków łańcucha s1 mamy wyznaczony najdłuższy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 369 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

wspólny podłańcuch.

Tak określony algorytm znajdowania najdłuższego wspólnego podciągu posiada sześcienną klasę złożoności obliczeniowej
O(m2n), gdzie m jest długością łańcucha s1, a n jest długością łańcucha s2.

Przykład:
Wyszukać najdłuższy wspólny podłańcuch dla:

s1 = AAABBA
s2 = ABAABBAAA

Lp. Kolejne dopasowania s2 do s1 Najdłuższy wspólny podłańcuch

A A A B B A
A B A A B B A A A
A B A A B B A A A
1. A B A A B B A A A AAA
A B A A B B A A A
A B A A B B A A A
A B A A B B A A A

A A A B B A
A B A A B B A A A
A B A A B B A A A
2. A B A A B B A A A AABBA
A B A A B B A A A
A B A A B B A A A
A B A A B B A A A

A A A B B A
A B A A B B A A A
A B A A B B A A A
3. A B A A B B A A A ABBA
A B A A B B A A A
A B A A B B A A A
A B A A B B A A A

A A A B B A
A B A A B B A A A
4. A B A A B B A A A BBA
A B A A B B A A A

A A A B B A
A B A A B B A A A
5. A B A A B B A A A BA
A B A A B B A A A

A A A B B A
A B A A B B A A A
A B A A B B A A A
6. A B A A B B A A A A
A B A A B B A A A
A B A A B B A A A
A B A A B B A A A

Algorytm wyszukiwania najdłuższego wspólnego podłańcucha


Wejście:

s1, s2 – łańcuchy tekstowe, w których szukamy najdłuższego wspólnego podłańcucha

Wyjście:
lmax – długość najdłuższego wspólnego podłańcucha. Jeśli lmax jest równe 0, to brak wspólnego
podłańcucha. lmax N.
p1 – pozycja najdłuższego wspólnego podłańcucha w s1, p1 N.
p2 – pozycja najdłuższego wspólnego podłańcucha w s2, p2 N.

Elementy pomocnicze:
i – indeks znaków w łańcuchu s1, i N
j – indeks znaków w łańcuchu s2, j N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 370 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

m – długość łańcucha s1, m N


n – długość łańcucha s2, n N
lm – liczba znaków w podłańcuchu, lm N

Lista kroków:
K01: m ← |s1| ; obliczamy długość łańcucha s1
K02: n ← |s2| ; obliczamy długość łańcucha s2
K03: s1 ← wartownik1 + s1 + wartownik1 ; dodajemy wartownika do s1
K04: s2 ← wartownik2 + s2 + wartownik2 ; dodajemy innego wartownika do s2
K05: lmax ← 0 ; zerujemy długość podłańcucha
K06: Dla i = 1,2,...,m wykonuj K07...K14
K07: Dla j = 1,2,...,n wykonuj K08...K14
K08: Jeśli s1[i] ≠ s2[j], to następny obieg pętli K07 ; szukamy zgodnego znaku w s1 i s2
K09: lm ← 1 ; ustawiamy wstępną długość
podłańcucha
K10: Dopóki s1[i + lm] = s2[j + lm], wykonuj lm ← lm + 1 ; rozszerzamy podciąg w prawo
K11: Jeśli lm ≤ lmax, to następny obieg pętli K07 ; sprawdzamy, czy podłańcuch jest
dłuższy od zapamiętanego
K12 lmax ← lm ; jeśli tak, zapamiętujemy go
K13: p1 ← i - 1 ; pozycja skorygowana z uwagi na
strażnika
K14: p2 ← j - 1 ; pozycja skorygowana z uwagi na
strażnika
K15: Usuń wartowników z s1 i s2 ; przywracamy stan s1 i s2
K16: Zakończ z wynikiem lmax,p1,p2

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje dwa losowe łańcuchy po 40 znaków zbudowane z liter A i B. Wyszukuje w nich najdłuższy
wspólny podłańcuch i wypisuje go odpowiednio pozycjonując łańcuchy względem siebie i wyznaczonego
podłańcucha. Jeśli łańcuchy nie posiadają wspólnego podłańcucha, wypisywane jest słowo BRAK.

Lazarus

// Wyszukiwanie najdłuższego podłańcucha


// Data: 10.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s1,s2 : ansistring;
i,j,p1,p2,lm,lmax : integer;
begin
randomize;
// generujemy łańcuchy s1 i s2
s1 := ''; s2 := '';
for i := 1 to 40 do
begin
s1 := s1 + char(65 + random(2));
s2 := s2 + char(65 + random(2));
end;
// wypisujemy łańcuchy s1 i s2
writeln('s1 = ',s1);
writeln('s2 = ',s2);
// szukamy najdłuższego wspólnego podłańcucha
s1 := '$' + s1 + '$'; // dodajemy wartowników do s1
s2 := '#' + s2 + '#'; // dodajemy wartowników do s2
lmax := 0;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 371 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

for i := 2 to 41 do
for j := 2 to 41 do
if s1[i] = s2[j] then
begin
lm := 1;
while s1[i + lm] = s2[j + lm] do inc(lm);
if lm > lmax then
begin
lmax := lm; p1 := i - 1; p2 := j - 1;
end;
end;
s1 := copy(s1,2,40); // usuwamy wartowników z s1
s2 := copy(s2,2,40); // usuwamy wartowników z s2
// prezentujemy wyniki
writeln;
if lmax = 0 then writeln('BRAK')
else
begin
repeat
if p1 > p2 then
begin
s2 := ' ' + s2; inc(p2);
end
else if p2 > p1 then
begin
s1 := ' ' + s1; inc(p1);
end;
until p1 = p2;
writeln(s1);
for i := 1 to p1 - 1 do write(' ');
writeln(copy(s1,p1,lmax),' : ',lmax);
writeln(s2);
end;
end.

Code::Blocks

// Wyszukiwanie najdłuższego podłańcucha


// Data: 10.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s1,s2;
int i,j,p1,p2,lm,lmax;
srand((unsigned)time(NULL));
// generujemy łańcuchy s1 i s2
s1 = ""; s2 = "";
for(i = 0; i < 40; i++)
{
s1 += 65 + rand() % 2;
s2 += 65 + rand() % 2;
}
// wypisujemy łańcuchy s1 i s2
cout << "s1 = " << s1 << endl
<< "s2 = " << s2 << endl;
// szukamy najdłuższego wspólnego podłańcucha
s1 = "$" + s1 + "$"; // dodajemy wartowników do s1
s2 = "#" + s2 + "#"; // dodajemy wartowników do s2
lmax = 0;
for(i = 1; i <= 40; i++)
for(j = 1; j <= 40; j++)
if(s1[i] == s2[j])
{
lm = 1;
while(s1[i + lm] == s2[j + lm]) lm++;
if(lm > lmax)
{

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 372 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

lmax = lm; p1 = i - 1; p2 = j - 1;
}
}
s1 = s1.substr(1,40); // usuwamy wartowników z s1
s2 = s2.substr(1,40); // usuwamy wartowników z s2
// prezentujemy wyniki
cout << endl;
if(lmax == 0) cout << "BRAK\n";
else
{
do
{
if(p1 > p2)
{
s2 = " " + s2; p2++;
}
else if(p2 > p1)
{
s1 = " " + s1; p1++;
}
} while(p1 != p2);
cout << s1 << endl;
for(i = 0; i < p1; i++) cout << " ";
cout << s1.substr(p1,lmax) << " : " << lmax << endl
<< s2 << endl;
}
return 0;
}

Free Basic

' Wyszukiwanie najdłuższego podłańcucha


' Data: 10.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s1,s2
Dim As Integer i,j,p1,p2,lm,lmax
Randomize
' generujemy łańcuchy s1 i s2
s1 = "": s2 = ""
For i = 1 To 40
s1 += Chr(65 + Cint(Rnd))
s2 += Chr(65 + Cint(Rnd))
Next
' wypisujemy łańcuchy s1 i s2
Print "s1 = ";s1
Print "s2 = ";s2
' szukamy najdłuższego wspólnego podłańcucha
s1 = "$" + s1 + "$" ' dodajemy wartowników do s1
s2 = "#" + s2 + "#" ' dodajemy wartowników do s2
lmax = 0
For i = 2 To 41
For j = 2 To 41
If Mid(s1,i,1) = Mid(s2,j,1) Then
lm = 1
While Mid(s1,i + lm,1) = Mid(s2,j + lm,1): lm += 1: Wend
If lm > lmax Then
lmax = lm
p1 = i - 1
p2 = j - 1
End If
End If
Next
Next
s1 = Mid(s1,2,40) ' usuwamy wartowników z s1
s2 = Mid(s2,2,40) ' usuwamy wartowników z s2
' prezentujemy wyniki
Print
If lmax = 0 Then
Print "BRAK"
Else
Do
If p1 > p2 Then
s2 = " " + s2: p2 += 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 373 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

Elseif p2 > p1 Then


s1 = " " + s1: p1 += 1
End If
Loop Until p1 = p2
Print s1
For i = 1 To p1 - 1: Print " ";: Next
Print Mid(s1,p1,lmax);" : ";lmax
Print s2
End If
End

Wynik
s1 = AAAAAABBAAABBBBBAABBAAAAABBBABAABBBBABBA
s2 = AAAABBBBBABBBAAAAAAABAABBAABBAAABBBAABBA
AAAAAABBAAABBBBBAABBAAAAABBBABAABBBBABBA
AABBAAABBB : 10
AAAABBBBBABBBAAAAAAABAABBAABBAAABBBAABBA

Wyszukiwanie najdłuższego wspólnego podłańcucha


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Drugie rozwiązanie wykorzystuje programowanie dynamiczne, gdzie zapamiętujemy w osobnej tablicy długości pasujących
podłańcuchów w obu łańcuchach s1 i s2. Dzięki temu klasa złożoności obliczeniowej algorytmu spada do O(mn). Algorytm
dynamiczny wymaga dodatkowej pamięci rozmiaru O(mn) na zapamiętywanie długości podłańcuchów. Zasada działania jest
następująca:
Oznaczmy przez L[i+1,j+1] największą długość (ang. length) wspólnego podłańcucha kończącego się na pozycjach
s1[i] oraz s2[j]. Długość tę wyznaczamy z długości L[i,j] w następujący sposób:

Jeśli s1[i] ≠ s2[j], to L[i+1,j+1] ← 0


Jeśli s1[i] = s2[j], to L[i+1,j+1] ← 1 + L[i,j]

Wynika z tego, iż długość wspólnego podłańcucha rośnie o 1, jeśli znaki na pozycjach i oraz j w obu łańcuchach są
zgodne. W przeciwnym razie długość zeruje się. Wyliczone długości są zapamiętywane w tablicy L, dzięki czemu w
kolejnych obiegach pętli nie musimy ich wyznaczać.

Algorytm dynamiczny wyszukiwania najdłuższego wspólnego podłańcucha


Wejście:

s1, s2 – łańcuchy tekstowe, w których szukamy najdłuższego wspólnego podłańcucha

Wyjście:
lmax – długość najdłuższego wspólnego podłańcucha. Jeśli lmax jest równe 0, to brak wspólnego
podłańcucha. lmax N.
p1 – pozycja najdłuższego wspólnego podłańcucha w s1, p1 N.
p2 – pozycja najdłuższego wspólnego podłańcucha w s2, p2 N.

Elementy pomocnicze:
i – indeks znaków w łańcuchu s1, i N.
j – indeks znaków w łańcuchu s2, j N.
m – długość łańcucha s1, m N
n – długość łańcucha s2, n N
L[] – dwuwymiarowa tablica zapamiętująca kolejne długości podłańcuchów. Wymiary L przebiegają
od 0 do m oraz od 0 do n. L N.

Lista kroków:
K01: m ← |s1| ; obliczamy długość łańcucha s1
K02: n ← |s2| ; obliczamy długość łańcucha s2

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 374 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

K03: Dla i = 0,1,...,m: L[i,0] ← 0 ; zerujemy pierwszą kolumnę tablicy L


K04: Dla j = 0,1,...,n: L[0,j] ← 0 ; zerujemy pierwszy wiersz tablicy L
K05: lmax ← 0 ; zerujemy największą długość wspólnego
podłańcucha
K06: Dla i = 0,1,...,m - 1: wykonuj K07...K15 ; przebiegamy przez kolejne znaki s1
K07: Dla j = 0,1,...,n - 1: wykonuj K08...K15 ; przebiegamy przez kolejne znaki s2
K08: Jeśli s1[i] = s2[j], idź do K11 ; sprawdzamy równość znaków s1[i] z s2[j]
K09: L[i+1,j+1] ← 0 ; znaki są różne, długość zerujemy
K10: Następny obieg pętli K06
K11: L[i+1,j+1] ← 1 + L[i,j] ; obliczamy nową długość wspólnego podłańcucha
K12: Jeśli L[i+1,j+1] ≤ lmax, to idź do K10 ; sprawdzamy, czy jest to najdłuższy podłańcuch
K13: lmax ← L[i+1,j+1] ; zapamiętujemy długość wspólnego podłańcucha
K14: p1 ← i - lmax + 1 ; oraz jego pozycję w s1
K15 p2 ← j - lmax + 1 ; i w s2
K16: Zakończ z wynikiem lmax,p1,p2

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów oraz
sposób korzystania z nich.

Program generuje dwa losowe łańcuchy po 40 znaków zbudowane z liter A i B. Wyszukuje w nich najdłuższy
wspólny podłańcuch i wypisuje go odpowiednio pozycjonując łańcuchy względem siebie i wyznaczonego
podłańcucha. Jeśli łańcuchy nie posiadają wspólnego podłańcucha, wypisywane jest słowo BRAK.

Lazarus

// Wyszukiwanie najdłuższego podłańcucha


// Data: 16.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s1,s2 : ansistring;
i,j,p1,p2,lmax : integer;
L : array[0..40,0..40] of integer;
begin
randomize;
// generujemy łańcuchy s1 i s2
s1 := ''; s2 := '';
for i := 1 to 40 do
begin
s1 := s1 + char(65 + random(2));
s2 := s2 + char(65 + random(2));
end;
// wypisujemy łańcuchy s1 i s2
writeln('s1 = ',s1);
writeln('s2 = ',s2);
// szukamy najdłuższego wspólnego podłańcucha
for i := 0 to 40 do
begin
L[i,0] := 0;
L[0,i] := 0;
end;
lmax := 0;
for i := 1 to 40 do
for j := 1 to 40 do
if s1[i] <> s2[j] then L[i,j] := 0
else
begin
L[i,j] := 1 + L[i-1,j-1];
if L[i,j] > lmax then
begin
lmax := L[i,j];

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 375 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

p1 := i - lmax + 1;
p2 := j - lmax + 1;
end;
end;
// prezentujemy wyniki
writeln;
if lmax = 0 then writeln('BRAK')
else
begin
repeat
if p1 > p2 then
begin
s2 := ' ' + s2; inc(p2);
end
else if p2 > p1 then
begin
s1 := ' ' + s1; inc(p1);
end;
until p1 = p2;
writeln(s1);
for i := 1 to p1 - 1 do write(' ');
writeln(copy(s1,p1,lmax),' : ',lmax);
writeln(s2);
end;
end.

Code::Blocks

// Wyszukiwanie najdłuższego podłańcucha


// Data: 16.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string s1,s2;
int i,j,p1,p2,lmax,L[41][41];
srand((unsigned)time(NULL));
// generujemy łańcuchy s1 i s2
s1 = ""; s2 = "";
for(i = 0; i < 40; i++)
{
s1 += 65 + rand() % 2;
s2 += 65 + rand() % 2;
}
// wypisujemy łańcuchy s1 i s2
cout << "s1 = " << s1 << endl
<< "s2 = " << s2 << endl;
// szukamy najdłuższego wspólnego podłańcucha
for(i = 0; i <= 40; i++) L[i][0] = L[0][i] = 0;
lmax = 0;
for(i = 0; i < 40; i++)
for(j = 0; j < 40; j++)
if(s1[i] != s2[j]) L[i+1][j+1] = 0;
else
{
L[i+1][j+1] = 1 + L[i][j];
if(L[i+1][j+1] > lmax)
{
lmax = L[i+1][j+1];
p1 = i - lmax + 1;
p2 = j - lmax + 1;
}
}
// prezentujemy wyniki
cout << endl;
if(lmax == 0) cout << "BRAK\n";
else

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 376 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

{
do
{
if(p1 > p2)
{
s2 = " " + s2; p2++;
}
else if(p2 > p1)
{
s1 = " " + s1; p1++;
}
} while(p1 != p2);
cout << s1 << endl;
for(i = 0; i < p1; i++) cout << " ";
cout << s1.substr(p1,lmax) << " : " << lmax << endl
<< s2 << endl;
}
return 0;
}

Free Basic

' Wyszukiwanie najdłuższego podłańcucha


' Data: 16.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s1,s2
Dim As Integer i,j,p1,p2,lmax,L(40,40)
Randomize
' generujemy łańcuchy s1 i s2
s1 = "": s2 = ""
For i = 1 To 40
s1 += Chr(65 + Cint(Rnd))
s2 += Chr(65 + Cint(Rnd))
Next
' wypisujemy łańcuchy s1 i s2
Print "s1 = ";s1
Print "s2 = ";s2
' szukamy najdłuższego wspólnego podłańcucha
For i = 0 To 40
L(i,0) = 0
L(0,i) = 0
Next
lmax = 0
For i = 1 To 40
For j = 1 To 40
If Mid(s1,i,1) <> Mid(s2,j,1) Then
L(i,j) = 0
Else
L(i,j) = 1 + L(i-1,j-1)
If L(i,j) > lmax Then
lmax = L(i,j)
p1 = i - lmax + 1
p2 = j - lmax + 1
End If
End If
Next
Next
' prezentujemy wyniki
Print
If lmax = 0 Then
Print "BRAK"
Else
Do
If p1 > p2 Then
s2 = " " + s2: p2 += 1
Elseif p2 > p1 Then
s1 = " " + s1: p1 += 1
End If
Loop Until p1 = p2
Print s1
For i = 1 To p1 - 1: Print " ";: Next
Print Mid(s1,p1,lmax);" : ";lmax
Print s2
End If
End

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 377 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

Zwróć uwagę, iż w prezentowanym powyżej algorytmie stosujemy dwuwymiarową tablicę L[m × n]. Tymczasem w
tablicy tej wykorzystywane są naraz tylko dwa wiersze – i-ty oraz (i-1)-szy. Algorytm możemy tak zmodyfikować,
aby wymiar tablicy spadł do L[2 × n]. Zmniejszy to zapotrzebowanie na pamięć do rozmiaru O(n), zamiast O(mn).

Algorytm dynamiczny wyszukiwania najdłuższego wspólnego podłańcucha


Wejście:

s1, s2 – łańcuchy tekstowe, w których szukamy najdłuższego wspólnego podłańcucha

Wyjście:
lmax – długość najdłuższego wspólnego podłańcucha. Jeśli lmax jest równe 0, to brak wspólnego
podłańcucha. lmax N.
p1 – pozycja najdłuższego wspólnego podłańcucha w s1, p1 N.
p2 – pozycja najdłuższego wspólnego podłańcucha w s2, p2 N.

Elementy pomocnicze:
i – indeks znaków w łańcuchu s1, i N.
j – indeks znaków w łańcuchu s2, j N.
m – długość łańcucha s1, m N
n – długość łańcucha s2, n N
L[] – dwuwymiarowa tablica zapamiętująca kolejne długości podłańcuchów. Wymiary L przebiegają
od 0 do 1 oraz od 0 do n. L N.

Lista kroków:
K01: m ← |s1| ; obliczamy długość łańcucha s1
K02: n ← |s2| ; obliczamy długość łańcucha s2
K03: Dla j = 0,1,...,n: L[0,j] ← 0 ; zerujemy pierwszy wiersz tablicy L
K04: lmax ← 0 ; zerujemy największą długość wspólnego
podłańcucha
K05: Dla i = 0,1,...,m - 1: wykonuj K06...K15 ; przebiegamy przez kolejne znaki s1
K06: Dla j = 0,1,...,n - 1: wykonuj K07...K14 ; przebiegamy przez kolejne znaki s2
K07: Jeśli s1[i] = s2[j], idź do K10 ; sprawdzamy równość znaków s1[i] z s2[j]
K08: L[1,j+1] ← 0 ; znaki są różne, długość zerujemy
K09: Następny obieg pętli K05
K10: L[1,j+1] ← 1 + L[0,j] ; obliczamy nową długość wspólnego podłańcucha
K11: Jeśli L[1,j+1] ≤ lmax, to idź do K09 ; sprawdzamy, czy jest to najdłuższy podłańcuch
K12: lmax ← L[1,j+1] ; zapamiętujemy długość wspólnego podłańcucha
K13: p1 ← i - lmax + 1 ; oraz jego pozycję w s1
K14 p2 ← j - lmax + 1 ; i w s2
K15: Dla j = 0,1,...,n: L[0,j] ← L[1,j] ; przepisujemy wiersz 1 do wiersza 0 tablicy L
K16: Zakończ z wynikiem lmax,p1,p2

Zmodyfikowanie przykładowych programów wg nowego algorytmu pozostawiam czytelnikowi jako proste ćwiczenie w
programowaniu. A może wymyślisz sposób przełączania wierszy tablicy L bez konieczności ich przepisywania - zastanów się nad
dwoma dodatkowymi zmiennymi indeksującymi wiersze tej tablicy.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 378 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podłańcuch 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0056.php 379 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie najdłuższego wspólnego podciągu


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach

Problem
Dla danych dwóch niepustych łańcuchów tekstowych s1 i s2 znaleźć najdłuższy wspólny podciąg znakowy.

Na pierwszy rzut oka problem wydaje się taki sam jak w poprzednim rozdziale. Jest jednak bardzo istotna różnica. Przy
znajdowaniu najdłuższego wspólnego podłańcucha chodziło o znalezienie najdłuższego, spójnego fragmentu tekstu, który
występuje w obu łańcuchach. W bieżącym problemie znajdowania najdłuższego wspólnego podciągu (ang. longest common
subsequence) chodzi o wyznaczenie najdłuższego ciągu znaków, które występują w tej samej kolejności w obu łańcuchach. Znaki
te mogą być rozdzielone w każdym z łańcuchów innymi znakami.

Przykład:
Najdłuższy wspólny podłańcuch Najdłuższy wspólny podciąg
s1 = ALIBABA s1 = ALIBABA
s2 = KALIMALBA s2 = KALIMALBA
ALI ALIABA

Problem znajdowania najdłuższego wspólnego podciągu występuje we wielu dziedzinach nauki:

Biologia molekularna – badania ciągów genów w łańcuchach DNA, które można sprowadzić do długich łańcuchów
tekstowych zbudowanych z czterech znaków ACGT reprezentujących składniki DNA. Gdy zostaną znalezione nowe ciągi

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 380 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

aminokwasów, szuka się podobieństw do nich. Do tego celu właśnie służy znajdowanie najdłuższych wspólnych
podciągów.
Porównywanie plików – można sprawdzić w jakim stopniu dwa różne pliki są do siebie podobne wydzielając w nich
najdłuższy wspólny podciąg.

Rozwiązanie nr 1
W pierwszym podejściu zastosujemy nieefektywne rozwiązanie rekurencyjne. Zaobserwujmy kilka prostych faktów związanych z
problemem najdłuższego wspólnego podciągu – nazwijmy go dla ułatwienia LCS (ang. longest common subsequence). Załóżmy, iż
mamy dwa łańcuchy znaków takie jak w powyższym przykładzie. Zapiszmy je jeden nad drugim i połączmy strzałkami znaki
należące do LCS:

Zwróć uwagę, iż strzałki nie mogą się krzyżować, gdyż wtedy nie byłaby zachowana kolejność znaków. Wynika stąd kilka bardzo
istotnych wniosków:

Jeśli dwa łańcuchy rozpoczynają się od tego samego znaku, to znak ten na pewno należy do LCS.
Jeśli początkowe znaki w obu łańcuchach są różne, to nie mogą oba jednocześnie należeć do LCS, gdyż
wymagałoby to skrzyżowania strzałek. Jeden z nich (a może oba) będzie trzeba usunąć. Gdy zdecydujemy, co z
nimi zrobić, to problem zredukuje się do znalezienia najdłuższego wspólnego podciągu dla łańcuchów
pomniejszonych o te pierwsze znaki. Pozwala to na rozwiązanie rekurencyjne. Na początek określimy algorytm
znajdowania długości LCS, a następnie samego LCS.

Algorytm rekurencyjnego znajdowania długości najdłuższego wspólnego podciągu


Wejście:
s1, s2 – łańcuchy tekstowe, w których szukamy LCS. Na końcu łańcuchów dodajemy wartownika.
i – indeks w s1, od którego rozpoczynamy przeglądanie łańcucha, i N
j – indeks w s2, od którego rozpoczynamy przeglądanie łańcucha, j N

Wyjście:
Długość LCS N
Lista kroków LCS(i,j):
K01: Jeśli s1[i] = wartownik, to zakończ z wynikiem 0 ; napotkany koniec łańcucha s1
K02: Jeśli s2[j] = wartownik, to zakończ z wynikiem 0 ; napotkany koniec łańcucha s2
K03: Jeśli s1[i] = s2[j], to zakończ z wynikiem 1 + LCS(i+1,j+1) ; wywołanie rekurencyjne
K04: Zakończ z wynikiem max(LCS(i+1,j),LCS(i,j+1)) ; wywołanie rekurencyjne

Mając algorytm znajdowania długości LCS możemy skonstruować algorytm wyznaczania samego LCS. Opieramy się na
następujących przesłankach:

Jeśli znaki s1[i] oraz s2[j] są równe, to należą do LCS.


Jeśli znaki s1[i] oraz s2[j] są różne, to wyliczamy LCS(i+1,j) oraz LCS(i,j+1), a następnie sprawdzamy:

1. LCS(i+1,j) ≤ LCS(i,j+1) – odrzucamy s2[j], gdyż bez niego mamy dłuższy LCS
2. Inaczej odrzucamy s1[i] z tego samego powodu co wyżej

Algorytm znajdowania najdłuższego wspólnego podciągu


Wejście:
s1, s2 – łańcuchy tekstowe, w których szukamy LCS. Na końcu łańcuchów dodajemy wartownika o
kodzie 0 (w C++ już jest!).
i – indeks w s1, od którego rozpoczynamy przeglądanie łańcucha, i N.
j – indeks w s2, od którego rozpoczynamy przeglądanie łańcucha, j N.

Wyjście:
Zawartość sLCS

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 381 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

Elementy pomocnicze:
sLCS – łańcuch zawierający LCS

Lista kroków:
K01: sLCS ← "" ; zerujemy LCS
K02: i ← 0 ; ustawiamy indeksy na pierwszych znakach
s1 i s2
K03: j ← 0
K04: Dopóki (s1[i] ≠ 0) (s2[j] ≠ 0) wykonuj K05...K11
K05: Jeśli s1[i] ≠ s2[j], idź do K10 ; sprawdzamy równość znaków w obu
łańcuchach
K06: sLCS ← sLCS + s1[i] ; dodajemy znak do LCS
K07: i ← i + 1 ; przesuwamy się na następną pozycję w obu
łańcuchach
K08: j ← j + 1
K09: Następny obieg pętli K04 ; i kontynuujemy pętlę
K10: Jeśli LCS(i+1,j) ≤ LCS(i,j+1), to idź do K08 ; odrzucamy znak s2[j]
K11: i ← i + 1 ; odrzucamy znak s1[i]
K12: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwa wiersze znaków (niezbyt długie z uwagi na wykładniczą klasę złożoności algorytmu),
znajduje dla nich najdłuższy wspólny podciąg, wypisuje go wraz z jego długością.

Lazarus

// Wyszukiwanie najdłuższego wspólnego podciągu


// Data: 17.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
uses Math;
var
s1,s2,sLCS : ansistring;
// Funkcja oblicza długość LCS dla s1 i s2
// i - indeks startu w s1
// j - indeks startu w s2
//----------------------------------------
function LCS(i,j : integer) : integer;
begin
if (s1[i] = #0) or (s2[j] = #0) then
LCS := 0
else if s1[i] = s2[j] then
LCS := 1 + LCS(i+1,j+1)
else
LCS := max(LCS(i+1,j),LCS(i,j+1));
end;
var
i,j : integer;
begin
readln(s1); readln(s2);
s1 := s1 + #0; // wartownik
s2 := s2 + #0; // wartownik
sLCS := ''; i := 1; j := 1;
while (s1[i] <> #0) and (s2[j] <> #0) do
if s1[i] = s2[j] then
begin
sLCS := sLCS + s1[i];
inc(i); inc(j);
end
else if LCS(i+1,j) <= LCS(i,j+1) then inc(j) else inc(i);
writeln(sLCS,' : ',length(sLCS));
end.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 382 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

Code::Blocks

// Wyszukiwanie najdłuższego wspólnego podciągu


// Data: 17.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
string s1,s2,sLCS; // zmienne globalne
// Funkcja oblicza długość LCS dla s1 i s2
// i - indeks startu w s1
// j - indeks startu w s2
//----------------------------------------
int LCS(int i, int j)
{
if(!s1[i] || !s2[j]) return 0;
else if(s1[i] == s2[j]) return 1 + LCS(i+1,j+1);
else return max(LCS(i+1,j),LCS(i,j+1));
}
int main()
{
int i,j;
getline(cin,s1);
getline(cin,s2);
sLCS = ""; i = j = 0;
while(s1[i] && s2[j])
if(s1[i] == s2[j])
{
sLCS += s1[i]; i++; j++;
}
else if(LCS(i+1,j) <= LCS(i,j+1)) j++; else i++;
cout << sLCS << " : " << sLCS.length() << endl;
return 0;
}

Free Basic

' Wyszukiwanie najdłuższego wspólnego podciągu


' Data: 17.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim Shared As String s1,s2
' Funkcja zwraca większy ze swoich argumentów
'--------------------------------------------
Function max(Byval x As Integer, Byval y As Integer) As Integer
If x > y Then
max = x
Else
max = y
End If
End Function
' Funkcja oblicza długość LCS dla s1 i s2
' i - indeks startu w s1
' j - indeks startu w s2
'----------------------------------------
Function LCS(Byval i As Integer, Byval j As Integer) As Integer
If(Mid(s1,i,1) = Chr(0)) Or (Mid(s2,j,1) = Chr(0)) Then
LCS = 0
Elseif Mid(s1,i,1) = Mid(s2,j,1) Then
LCS = 1 + LCS(i+1,j+1)
Else
LCS = max(LCS(i+1,j),LCS(i,j+1))
End If
End Function
Dim As String sLCS
Dim As Integer i,j
Line Input s1: Line Input s2
s1 += Chr(0) ' wartownik
s2 += Chr(0) ' wartownik
sLCS = "": i = 1: j = 1
While (Mid(s1,i,1) <> Chr(0)) And (Mid(s2,j,1) <> Chr(0))
If Mid(s1,i,1) = Mid(s2,j,1) Then
sLCS += Mid(s1,i,1)
i += 1: j += 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 383 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

Elseif LCS(i+1,j) <= LCS(i,j+1) Then


j += 1
Else
i += 1
End If
Wend
Print sLCS;" : ";Len(sLCS)
End

Wynik
ALIBABA
KALIMALBA
ALIABA : 6

Rozwiązanie nr 2
Algorytm rekurencyjny wyznaczania LCS posiada wykładniczą złożoność obliczeniową. Winę za to ponoszą rekurencyjne
wywołania przy wyliczaniu długości LCS, gdzie wielokrotnie wykonywane są te same obliczenia. Usprawnienie zatem polega na
eliminacji wywołań rekurencyjnych i zastosowaniu programowania dynamicznego – wyliczone długości LCS są zapamiętywane w
osobnej tablicy L[(m+1) × (n+1)]. Dzięki takiemu podejściu klasa złożoności obliczeniowej spada do O(mn), jednakże algorytm
wymaga dodatkowej pamięci rzędu O(mn). Element L[i+1,j+1] zawiera długość LCS dla podłańcuchów s1[0:i] oraz s2[0:j].

Algorytm dynamicznego znajdowania długości najdłuższego wspólnego podciągu


Wejście:
s1, s2 – łańcuchy tekstowe, w których szukamy LCS. Na końcu łańcuchów dodajemy wartownika.

Wyjście:
Długość LCS. Dodatkowo efektem działania algorytmu jest wypełniona tablica L, która może posłużyć
do wyznaczania znaków LCS.
Elementy pomocnicze:
m – długość łańcucha s1, m N
n – długość łańcucha s2, n N
L{ ] – tablica długości o indeksach od 0 do m oraz od 0 do n, L N
i,j – indeksy znaków w s1 i s2, i,j N

Lista kroków:
K01: m ← |s1| ; obliczamy długości łańcuchów s1
K02: n ← |s2| ; oraz s2
K03: Dla i = 0,1,...,m: L[i,0] ← 0 ; inicjujemy pierwszą kolumnę tablicy L
K04: Dla j = 0,1,...,n: L[0,j] ← 0 ; oraz pierwszy wiersz
K05: Dla i = 0,1,...,m - 1: wykonuj K06...K10 ; wyznaczamy długości kolejnych LCS
K06: Dla j = 0,1,...,n - 1: wykonuj K07...K10
K07: Jeśli s1[i] ≠ s2[j], to idź do K10 ; sprawdzamy, czy LCS jest rozszerzalny
K08: L[i + 1,j + 1] ← 1 + L[i,j] ; jeśli tak, to zwiększamy poprzednią długość
K09: Następny obieg pętli K06
K10: L[i + 1,j + 1] ← max(L[i + 1,j],L[i,j + 1]) ; jeśli nie, wybieramy dłuższy LCS
K11: Zakończ z wynikiem L[m.n]

Algorytm znajdowania najdłuższego wspólnego podciągu


Wejście:

s1, s2 – łańcuchy tekstowe, w których szukamy LCS.

Wyjście:
Zawartość LCS
Elementy pomocnicze:
sLCS – łańcuch zawierający LCS
L{ ] – tablica długości LCS o indeksach od 0 do m oraz od 0 do n
i,j – indeksy znaków w s1 i s2

Lista kroków:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 384 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

K01: Utwórz i oblicz tablicę L ; wyznaczamy długości kolejnych LCS


K02: sLCS ← "" ; zerujemy LCS
K03: i ← m - 1 ; ustawiamy indeksy na ostatnich znakach s1 i s2
K04: j ← n - 1
K05: Dopóki (i ≥ 0) (j ≥ 0) wykonuj K06...K12
K06: Jeśli s1[i] ≠ s2[j], idź do K11 ; sprawdzamy równość znaków w obu łańcuchach
K07: sLCS ← s1[i] + sLCS ; dodajemy znak na początek LCS - znaki
otrzymujemy od końca!
K08: i ← i - 1 ; przesuwamy się na poprzednią pozycję w obu
łańcuchach
K09: j ← j - 1
K10: Następny obieg pętli K05 ; i kontynuujemy pętlę
K11: Jeśli L[i + 1,j] > L[i,j + 1], to idź do K09 ; cofamy się w s2
K12: i ← i - 1 ; cofamy się w s1
K13: Zakończ z wynikiem sLCS

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwa wiersze znaków, znajduje dla nich najdłuższy wspólny podciąg i wypisuje go wraz z jego
długością.

Lazarus

// Wyszukiwanie najdłuższego podciągu


// Data: 18.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
uses Math;
type
Tint = array of integer;
TTint = array of Tint;
var
s1,s2,sLCS : ansistring;
L : TTint;
i,j,m,n : integer;
begin
readln(s1); readln(s2);
m := length(s1);
n := length(s2);
// tworzymy dynamicznie tablicę L
setlength(L,m + 1);
for i := 0 to m do setlength(L[i],n + 1);
// obliczamy tablicę L
for i := 0 to m do L[i][0] := 0;
for j := 0 to n do L[0][j] := 0;
for i := 1 to m do
for j := 1 to n do
if s1[i] = s2[j] then
L[i][j] := 1 + L[i - 1][j - 1]
else
L[i][j] := max(L[i][j - 1],L[i - 1][j]);
// wyznaczamy LCS
sLCS := ''; i := m; j := n;
while (i > 0) and (j > 0) do
if s1[i] = s2[j] then
begin
sLCS := s1[i] + sLCS;
dec(i); dec(j);
end
else if L[i][j - 1] > L[i - 1][j] then dec(j)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 385 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

else dec(i);
writeln(sLCS,' : ',L[m][n]);
end.

Code::Blocks

// Wyszukiwanie najdłuższego podciągu


// Data: 18.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1,s2,sLCS;
int ** L,i,j,m,n;
getline(cin,s1);
getline(cin,s2);
m = s1.length();
n = s2.length();
// tworzymy dynamicznie tablicę L
L = new int * [m + 1];
for(i = 0; i <= m; i++) L[i] = new int[n + 1];
// obliczamy tablicę L
for(i = 0; i <= m; i++) L[i][0] = 0;
for(j = 0; j <= n; j++) L[0][j] = 0;
for(i = 0; i < m; i++)
for(j = 0; j < n; j++)
if(s1[i] == s2[j])
L[i + 1][j + 1] = 1 + L[i][j];
else
L[i + 1][j + 1] = max(L[i + 1][j],L[i][j + 1]);
// wyznaczamy LCS
sLCS = ""; i = m - 1; j = n - 1;
while((i >= 0) && (j >= 0))
if(s1[i] == s2[j])
{
sLCS = s1[i] + sLCS;
i--; j--;
}
else if(L[i + 1][j] > L[i][j + 1]) j--;
else i--;
cout << sLCS << " : " << L[m][n] << endl;
return 0;
}

Free Basic

' Wyszukiwanie najdłuższego podciągu


' Data: 18.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
' Funkcja zwraca większy ze swoich argumentów
'--------------------------------------------
Function max(Byval x As Integer, Byval y As Integer) As Integer
If x > y Then
max = x
Else
max = y
End If
End Function
Dim As String s1,s2,sLCS
Dim As Integer i,j,m,n
Line Input s1: Line Input s2
m = Len(s1)
n = Len(s2)
' tworzymy dynamicznie tablicę L
Dim As Integer L(m,n)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 386 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

' obliczamy tablicę L


For i = 0 To m: L(i,0) = 0: Next
For j = 0 To n: L(0,j) = 0: Next
For i = 1 To m
For j = 1 To n
If Mid(s1,i,1) = Mid(s2,j,1) Then
L(i,j) = 1 + L(i - 1,j - 1)
Else
L(i,j) = max(L(i,j - 1),L(i - 1,j))
End If
Next
Next
' wyznaczamy LCS
sLCS = "": i = m: j = n
While (i > 0) And (j > 0)
If Mid(s1,i,1) = Mid(s2,j,1) Then
sLCS = Mid(s1,i,1) + sLCS
i -= 1: j -= 1
Elseif L(i,j - 1) > L(i - 1,j) Then
j -= 1
Else
i -= 1
End If
Wend
Print sLCS;" : ";L(m,n)
End

Wynik

Wyszukiwanie najdłuższego wspólnego podciągu


(C)2012 mgr Jerzy Wałaszek

s1 = ALIBABA
s2 = KALIMALBA

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 387 / 519


Algorytmy i Struktury Danych - Najdłuższy wspólny podciąg 2014-10-03

w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0057.php 388 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie najkrótszego wspólnego nadłańcucha


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie max lub min

Problem
Dla danego zbioru łańcuchów znakowych S = {s1, s2, ..., sk} znaleźć najkrótszy łańcuch s, który zawiera wszystkie
łańcuchy zbioru S.

Problem znajdowania najkrótszego wspólnego nadłańcucha (ang. shortest common superstring – SCS) pojawia się w wielu
dziedzinach nauki, np. w badaniach łańcuchów DNA. Samo znalezienie łańcucha zawierającego wszystkie łańcuchy s1, s2, ..., sk
nie jest zadaniem trudnym – można po prostu połączyć je ze sobą. Trudność pojawia się w momencie, gdy żądamy, aby
wynikowy łańcuch posiadał najkrótszą możliwą długość. Okazuje się, iż zadanie to jest problemem NP-zupełnym.
W informatyce termin NP-zupełny odnosi się do klas złożoności obliczeniowej problemów, w których zasoby wymagane do
rozwiązania rosną bardzo szybko (np. wykładniczo) wraz ze wzrostem rozmiaru problemu (tj. liczby przetwarzanych danych). W
efekcie problemy te mogą nie być rozwiązywalne nawet na najszybszych komputerach z uwagi na to, iż czas oczekiwania na
wynik przekracza czas istnienia naszego wszechświata. Jedną z nierozwiązanych zagadek informatyki jest to, czy problemy NP-
zupełne da się rozwiązać przy mniejszej ilości zasobów. Udowodniono nawet, że jeśli dałoby się rozwiązać chociaż jeden z nich,
to pozostałe można by sprowadzić do tego rozwiązania. Jako programista powinieneś umieć rozpoznawać problemy NP-zupełne,
aby na próżno nie tracić czasu na szukanie rozwiązania, którego jeszcze nikt nie znalazł.
Z powyższego powodu, zamiast rozwiązania dokładnego, często zadowalamy się rozwiązaniem przybliżonym, być może
nieoptymalnym, ale dającym się wyliczyć w rozsądnym czasie. Jednym z podejść do rozwiązania jest zastosowanie metody
zachłannej (ang. greedy algorithm), która polega na tym, iż lokalnie dokonujemy najlepszych wyborów na każdym etapie pracy
algorytmu w nadziei, iż znajdziemy w ten sposób optymalne rozwiązanie globalne.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 389 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

Przykład:
Wyznaczyć najmniejszą liczbę banknotów i monet dających w sumie 138 zł.
Rozpoczynamy od największego możliwego banknotu, który nie przekracza danej sumy (optymalne rozwiązanie
lokalne):

138 zł = 100 zł + 38 zł reszty

Operację kontynuujemy dla reszty 38 zł:

128 zł = 100 zł + 20 zł + 18 zł reszty


128 zł = 100 zł + 20 zł + 10 zł + 8 zł reszty
128 zł = 100 zł + 20 zł + 10 zł + 5 zł + 3 zł reszty
128 zł = 100 zł + 20 zł + 10 zł + 5 zł + 2 zł + 1 zł – koniec

W tym przypadku otrzymaliśmy rozwiązanie optymalne.

Często jednak metoda zachłanna nie daje optymalnego rozwiązania. Rozważmy tzw. problem plecakowy (ang. knapsack
problem). Mamy zbiór liczb {2,2,3,3}, które reprezentują rozmiar przedmiotów umieszczanych w plecaku. Plecak posiada
pojemność równą 7. Naszym zadaniem jest optymalne wypełnienie plecaka. Wg metody zachłannej najpierw staramy się w nim
umieścić największe przedmioty, czyli 3 i 3. W plecaku pozostanie miejsca na przedmiot o rozmiarze 7 - 3 - 3 = 1. Takiego
przedmiotu nie mamy, zatem plecak nie będzie wypełniony optymalnie – a zmieściłyby się w nim przedmioty o rozmiarze 2,2,3.

Wracając do problemu SCS będziemy szukać w zbiorze łańcuchów S = {s1,s2,...,sk} dwóch łańcuchów si i sj, i ≠ j, o
najdłuższym pasującym sufiksie i prefiksie. Po znalezieniu takich łańcuchów zastąpimy je jednym, połączonym
łańcuchem.

W efekcie zbiór S będzie zawierał o jeden łańcuch mniej. Operację powyższą powtarzamy dotąd, aż w zbiorze S
pozostanie tylko jeden łańcuch. Łańcuch ten potraktujemy jako przybliżenie SCS.

Algorytm wyszukiwania najkrótszego wspólnego nadłańcucha


Wejście:
S - k elementowy zbiór łańcuchów znakowych, k > 0, indeksy od 0 do k-1.

Wyjście:
Łańcuch znakowy zawierający wszystkie łańcuchy ze zbioru S, być może najkrótszy z możliwych.
Elementy pomocnicze:
i,j – indeksy łańcuchów w zbiorze S, i,j N
maxps – maksymalna długość prefiksu i sufiksu, maxps C
ps – wyliczana długość najdłuższego prefiksu i sufiksu, ps N
sp – indeks w S łańcucha z najdłuższym prefiksem, sp N
ss – indeks w S łańcucha z najdłuższym sufiksem, ss N
k – liczba łańcuchów w S, k N

Lista kroków:
K01: maxps ← -1 ; ustawiamy maksymalny prefiks i sufiks
K02: k ← |S| ; obliczamy ilość elementów w zbiorze S
K03: Jeśli k = 1, to zakończ z wynikiem S[0] ; zwracamy SCS

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 390 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

K03: Jeśli k = 1, to zakończ z wynikiem S[0] ; zwracamy SCS


K04: Dla i = 0,1,...,k - 1: wykonuj K05...K11 ; wyznaczamy najdłuższy prefiks i sufiks
K05: Dla j = 0,1,...,k - 1: wykonuj K06...K11
K06: Jeśli i = j, to następny obieg pętli K05 ; łańcuchy muszą być różne
K07: Oblicz ps dla S[i] i S[j] ; wyznaczamy najdłuższy wspólny sufiks
S[i] i prefiks S[j]
K08: Jeśli ps ≤ maxps, to następny obieg pętli K05
K09: maxps ← ps ; zapamiętujemy długość prefiksu i sufiksu
K10: sp ← j ; zapamiętujemy indeks łańcucha z
prefiksem
K11: ss ← i ; oraz indeks łańcucha z sufiksem
K12: Element S[ss] zastąp złączeniem s-p S[ss] i S[sp] ; wyznaczone łańcuchy łączymy sufiksem i
prefiksem
K13: Element S[sp] usuń z S
K14: Idź do K01

Podany powyżej algorytm nie nadaje się jeszcze do implementacji w postaci programu. Musimy rozwiązać kilka problemów. Od
tego jak to zrobimy, zależeć będzie wynikowa złożoność obliczeniowa.
Zbiór S będziemy reprezentować jednowymiarową tablicą, której elementy będą łańcuchami znaków. Operacja
usuwania elementu z tablicy (K13), przy zachowaniu ciągłości numeracji łańcuchów, wykonywana jest w czasie
liniowym O(n). Z kolei operacja dostępu do elementu (K12) wykonywana jest w czasie stałym O(1). Umawiamy się,
iż elementy tablicy S będą tworzyły ciąg niepustych łańcuchów znakowych. Za długość k zbioru S przyjmiemy
liczbę tych niepustych łańcuchów.
Dla danych dwóch łańcuchów S[i] oraz S[j] musimy wyznaczyć maksymalny sufiks S[i], który pasuje do prefiksu
S[j]. Najprostszy algorytm rozwiązania tego problemu wygląda następująco:
Algorytm wyszukiwania najdłuższego sufiksu pasującego do prefiksu w drugim
łańcuchu
Wejście:

s1,s2 – niepuste łańcuchy znakowe. W łańcuchu s1 szukamy sufiksu pasującego do


prefiksu łańcucha s2.

Wyjście:
Maksymalna długość sufiksu łańcucha s1 pasującego do prefiksu łańcucha s2.

Elementy pomocnicze:
ii – indeks w s1, ii N
ps – długość prtefiksu-sufiksu, ps N

Lista kroków:
K01: ii ← |s1| - |s2| ; ustawiamy indeks
pierwszego znaku sufiksu
K02: Jeśli ii < 0, to ii ← 0
K03: ps ← 0 ; indeks pierwszego znaku
prefiksu
K04: s1 ← s1 + wartownik1 ; na końcu łańcuchów
dodajemy wartowników
K05: s2 ← s2 + wartownik2 ; chroniących przed wyjściem
poza łańcuch
K06: Dopóki s1[ii] ≠ wartownik1 wykonuj K07...K10 ; szukamy sufiksu s1
zgodnego z prefiksem s2
K07: Dopóki s1[ii + ps] = s2[ps], wykonuj ps ← ps + 1 ; porównujemy znaki aż do
napotkania niezgodności
K08: Jeśli s1[ii + ps] = wartownik1, to idź do K11 ; sprawdzamy, czy mamy
pasujący sufiks do prefiksu
K09: ps ← 0 ; jeśli nie, prefiks zerujemy i
szukamy dalej
K10: ii ← ii + 1
K11: Usuń wartowników z s1 i s2 ; przywracamy normalną
postać łańcuchów s1 i s2
K12: Zakończ z wynikiem ps ; zwracamy długość sufiksu-
prefiksu

Teraz musimy określić sposób wykonania złączenia dwóch łańcuchów tak, aby nałożyły się na siebie sufiks i prefiks.
W tym przypadku, skoro znamy długość sufiksu i prefiksu, operacja jest bardzo prosta:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 391 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

S[ss] ← S[ss] + S[sp][maxps:|S[sp|]

I na koniec pozostała nam operacja usunięcia elementu S[sp]. Z tablicy nie można tak po prostu usunąć elementu.
Jeśli umówimy się, iż zbiór S jest reprezentowany przez kolejne elementy od S[0] do S[k-1], to wystarczy element
S[sp] zastąpić ostatnim elementem S[k-1], a następnie zmniejszyć k o 1. W ten sposób otrzymamy o jeden
efektywny element mniej w zakresie od S[0] do S[k-1].
Wynikowy łańcuch SCS można czasami zoptymalizować wyznaczając położenia w nim poszczególnych łańcuchów
ze zbioru S (trzeba je wtedy zapamiętać w innej tablicy) i zapamiętując ostatnie pozycje. Jeśli maksymalna ostatnia
pozycja łańcuchów S w SCS jest mniejsza od ostatniej pozycji SCS, to SCS można obciąć do tej pozycji. W ten
sposób ulepszymy nieco metodę zachłanną, ale lojalnie uprzedzamy, iż istnieją lepsze algorytmy wyznaczania SCS,
np. drzewa sufiksowe.

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje zbiór S zbudowany z 8 łańcuchów zawierających od 3 do 10 znaków należących do alfabetu {A,B}.
Następnie wyznacza dla tego zbioru nadłańcuch i prezentuje w odpowiedni sposób wyniki.

Lazarus

// Wyszukiwanie najkrótszego nadłańcucha


// Data: 22.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
S,P : array[0..7] of string;
i,j,maxps,ps,sp,ss,k,ii : integer;
begin
// generujemy zbiór S
randomize;
for i := 0 to 7 do
begin
k := 3 + random(8);
S[i] := '';
for j := 1 to k do
S[i] := S[i] + chr(65 + random(2));
writeln('S[',i,'] = ',S[i]);
P[i] := S[i];
end;
// wyznaczamy SCS
for k := 8 downto 2 do
begin
maxps := -1;
for i := 0 to k - 1 do
for j := 0 to k - 1 do
if i <> j then
begin
ii := length(S[i]) - length(S[j]) + 1;
if ii < 1 then ii := 1;
ps := 0;
S[i] := S[i] + '@'; // wartownik1
S[j] := S[j] + '#'; // wartownik2
while S[i][ii] <> '@' do
begin
while S[i][ii + ps] = S[j][ps + 1] do inc(ps);
if S[i][ii + ps] = '@' then break;
ps := 0; inc(ii);
end;
delete(S[i],length(S[i]),1);
delete(S[j],length(S[j]),1);
if ps > maxps then
begin
maxps := ps; sp := j; ss := i;
end;
end;
S[ss]:=S[ss]+copy(S[sp],maxps+1,length(S[sp])-maxps);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 392 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

S[sp]:=S[k - 1];
end;
writeln;
// wypisujemy łańcuchy odpowiednio przesunięte
// zapamiętujemy również maksymalną ostatnią pozycję
maxps := 0;
for i := 0 to 7 do
begin
write('S[',i,'] =');
ps := pos(P[i],S[0]);
for j := 1 to ps do write(' ');
writeln(P[i]);
inc(ps,length(P[i]) - 1);
if ps > maxps then maxps := ps;
end;
// optymalizujemy SCS
S[0] := copy(S[0],1,maxps);
writeln(' ',S[0]);
writeln;
end.

Code::Blocks

// Wyszukiwanie najkrótszego nadłańcucha


// Data: 22.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string S[8],P[8];
int i,j,maxps,ps,sp,ss,k,ii;
// generujemy zbiór S
srand((unsigned)time(NULL));
for(i = 0; i < 8; i++)
{
k = 3 + rand() % 8;
S[i] = "";
for(j = 0; j < k; j++)
S[i] += char(65 + rand() % 2);
cout << "S[" << i << "] = " << S[i] << endl;
P[i] = S[i];
}
// wyznaczamy SCS
for(k = 8; k > 1; k--)
{
maxps = -1;
for(i = 0; i < k; i++)
for(j = 0; j < k; j++)
if(i != j)
{
ii = S[i].length() - S[j].length();
if(ii < 0) ii = 0;
ps = 0;
S[i] += '@'; // wartownik1
S[j] += '#'; // wartownik2
while(S[i][ii] != '@')
{
while(S[i][ii + ps] == S[j][ps]) ps++;
if(S[i][ii + ps] == '@') break;
ps = 0; ii++;
}
S[i].erase(S[i].length() - 1,1);
S[j].erase(S[j].length() - 1,1);
if(ps > maxps)
{
maxps = ps; sp = j; ss = i;
}
}
S[ss]+=S[sp].substr(maxps,S[sp].length()-maxps);
S[sp] = S[k - 1];

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 393 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

}
cout << endl;
// wypisujemy łańcuchy odpowiednio przesunięte
// zapamiętujemy również maksymalną ostatnią pozycję
maxps = 0;
for(i = 0; i < 8; i++)
{
cout << "S[" << i << "] =";
ps = S[0].find(P[i]);
for(j = 0; j <= ps; j++) cout << " ";
cout << P[i] << endl;
ps += P[i].length();
if(ps > maxps) maxps = ps;
}
// optymalizujemy SCS
S[0].erase(maxps,S[0].length() - maxps);
cout << " " << S[0] << endl << endl;
return 0;
}

Free Basic

' Wyszukiwanie najkrótszego nadłańcucha


' Data: 22.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String S(7),P(7)
Dim As Integer i,j,maxps,ps,sp,ss,k,ii
' generujemy zbiór S
Randomize
For i = 0 To 7
k = 3 + Cint(Rnd * 7)
S(i) = ""
For j = 1 To k: S(i) += Chr(65 + Cint(Rnd)): Next
Print Using "S[#] = ";i;: Print S(i)
P(i) = S(i)
Next
' wyznaczamy SCS
For k = 8 To 2 Step -1
maxps = -1
For i = 0 To k - 1
For j = 0 To k - 1
If i <> j Then
ii = Len(S(i)) - Len(S(j)) + 1
If ii < 1 Then ii = 1
ps = 0
S(i) += "@" ' wartownik1
S(j) += "#" ' wartownik2
While Mid(S(i),ii,1) <> "@"
While Mid(S(i),ii + ps,1) = Mid(S(j),ps + 1,1): ps += 1: Wend
If Mid(S(i),ii + ps,1) = "@" Then Exit While
ps = 0: ii += 1
Wend
S(i) = Left(S(i),Len(S(i)) - 1)
S(j) = Left(S(j),Len(S(j)) - 1)
If ps > maxps Then
maxps = ps: sp = j: ss = i
End If
End If
Next
Next
S(ss) += Mid(S(sp),maxps + 1,Len(S(sp)) - maxps)
S(sp) = S(k - 1)
Next
Print
' wypisujemy łańcuchy odpowiednio przesunięte
' zapamiętujemy również maksymalną ostatnią pozycję
maxps = 0
For i = 0 To 7
Print Using "S[#] =";i;
ps = Instr(S(0),P(i))
For j = 1 To ps: Print " ";: Next
Print P(i)
ps += Len(P(i)) - 1
If ps > maxps Then maxps = ps

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 394 / 519


Algorytmy i Struktury Danych - Najkrótszy wspólny nadłańcuch 2014-10-03

Next
' optymalizujemy SCS
S(0) = Left(S(0),maxps)
Print " ";S(0)
Print
End

Wynik
S[0] = ABA
S[1] = BBBBB
S[2] = BBAA
S[3] = ABBABBAAB
S[4] = BAABBA
S[5] = ABABBBB
S[6] = BBBAAABAA
S[7] = ABBAABB
S[0] = ABA
S[1] = BBBBB
S[2] = BBAA
S[3] = ABBABBAAB
S[4] = BAABBA
S[5] = ABABBBB
S[6] = BBBAAABAA
S[7] = ABBAABB
ABABBBBBAAABAABBABBAABB

Wyszukiwanie najkrótszego wspólnego nadłańcucha


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany


Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0058.php 395 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie słów podwójnych


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie nr 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie nr 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach

Problem
W łańcuchu s znaleźć wszystkie słowa podwójne.

Przez słowo podwójne (ang. square word) będziemy rozumieli łańcuch tekstowy, który możemy rozdzielić na dwa identyczne
podłańcuchy.

Przykład:
ABBCABBC – jest słowem podwójnym zbudowanym z dwóch identycznych podsłów ABBC
ABBCABBD – nie jest słowem podwójnym

Rozwiązanie nr 1
Pierwsze rozwiązanie polega na bezpośrednim sprawdzaniu każdego podsłowa łańcucha s, czy jest ono słowem podwójnym.
Złożoność obliczeniowa takiego podejścia jest klasy O(n3). Słowo podwójne musi się składać z parzystej liczby znaków – inaczej
nie da się go podzielić na dwa podsłowa. Sprawdzanie polega na porównywaniu ze sobą znaków pierwszej i drugiej połówki. Jeśli
są zgodne, słowo jest podwójne.

Algorytm wyszukiwania słów podwójnych w łańcuchu – wersja nr 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 396 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

Wejście:

s – łańcuch tekstowy.

Wyjście:
Wszystkie słowa podwójne zawarte w łańcuchu s.
Elementy pomocnicze:
i,j – indeksy znaków w łańcuchu s, i,j N
m – długość łańcucha s, m N
n – długość podsłowa, n N

Lista kroków:
K01: m ← |s|
K02: Dla i = 0,1,...,n - 2, wykonuj K03...K07 ; przeglądamy łańcuch s
K03: n ← 2 ; rozpoczynamy od słowa o długości 2
K04: Dopóki i + n ≤ m, wykonuj K05...K07
K05: j ← i + n div 2 ; j wskazuje pierwszy znak drugiej połówki słowa
K06: Jeśli s[i:j] = s[j:i+n], to pisz s[i:i+n] ; sprawdzamy, czy słowo jest podwójne
K07: n←n+ 2 ; przechodzimy do następnego słowa
K08: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 20 znakowy łańcuch zbudowany ze znaków {A,B}, wyszukuje w nim wszystkie słowa podwójne i
wypisuje je z odpowiednim przesunięciem.

Lazarus

// Wyszukiwanie słów podwójnych


// Data: 23.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const M = 20; // długość łańcucha s
var
s : ansistring;
i,j,n : integer;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to M do
s := s + chr(65 + random(2));
// wypisujemy łańcuch s
writeln(s);
// szukamy słów podwójnych
for i := 1 to M - 1 do
begin
n := 2;
while i + n <= M + 1 do
begin
j := i + n div 2;
if copy(s,i,j-i) = copy(s,j,j-i) then
begin
for j := 2 to i do write(' ');
writeln(copy(s,i,n));
end;
inc(n,2);
end;
end;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 397 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

writeln;
end.

Code::Blocks

// Wyszukiwanie słów podwójnych


// Data: 23.07.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
const int M = 20; // długość łańcucha s
string s;
int i,j,n;
// generujemy łańcuch s
srand((unsigned)time(NULL));
s = "";
for(i = 0; i < M; i++)
s += char(65 + rand() % 2);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy słów podwójnych
for(i = 0; i < M - 1; i++)
for(n = 2; i + n <= M; n += 2)
{
j = i + n / 2;
if(s.substr(i,j-i) == s.substr(j,j-i))
{
for(j = 0; j < i; j++) cout << " ";
cout << s.substr(i,n) << endl;
}
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie słów podwójnych


' Data: 23.07.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const M = 20 ' długość łańcucha s
Dim As String s
Dim As Integer i,j,n
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To M: s += Chr(65 + Cint(Rnd)): Next
' wypisujemy łańcuch s
Print s
' szukamy słów podwójnych
For i = 1 To M - 1
n = 2
While i + n <= M + 1
j = i + n \ 2
If Mid(s,i,j-i) = Mid(s,j,j-i) Then
For j = 2 To i: Print " ";: Next
Print Mid(s,i,n)
End If
n += 2
Wend
Next
Print
End

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 398 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

Wynik
ABBAABBAAABAABABBBAA
ABBAABBA
BB
BBAABBAA
AA
BB
AA
AA
AABAAB
ABAABA
AA
ABAB
BB
BB
AA

Wyszukiwanie słów podwójnych


(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Pierwszy algorytm posiada sześcienną klasę złożoności obliczeniowej O(n3), gdzie n jest długością przeszukiwanego łańcucha s.
W praktyce jednak działa on szybciej, ponieważ niezgodność fragmentów łańcucha zwykle wykrywana jest na początku po kilku
testach. Zaletą jest prostota algorytmu.
Jeśli wykorzystamy w odpowiedni sposób algorytm Morrisa-Pratta, to możemy zredukować klasę złożoności obliczeniowej do
O(n2). W algorytmie MP jest wyznaczana dla wzorca s tablica Π zawierająca maksymalne szerokości prefikso-sufiksów. Na
przykład, jeśli weźmiemy prefiks s[0:i], to Π[i] zawiera szerokość prefikso-sufiksu tego prefiksu:

Na początek rozważymy wykrywanie słów podwójnych leżących na początku łańcucha s, a później uogólnimy tę operację na
dowolną pozycję wewnątrz łańcucha. Ponieważ tablica Π jest tworzona dynamicznie w algorytmie MP, możemy w trakcie tego
procesu badać jej zawartość. Jeśli i jest długością prefiksu s, to Π[i] jest długością prefikso-sufiksu dla tego prefiksu (patrz,
rysunek powyżej). Mogą wystąpić trzy możliwe sytuacje:

1. Długość prefikso-sufiksu jest mniejsza od długości połowy prefiksu, czyli Π[i] < i/2:

W tym przypadku nie można utworzyć słowa podwójnego o szerokości i.

2. Długość prefikso-sufiksu jest równa długości połowy prefiksu, czyli Π[i] = i/2:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 399 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

Prefiks s[0:i] jest słowem podwójnym, ponieważ składa się z dwóch, dokładnie takich samych podłańcuchów –
prefiksu i sufiksu tworzących prefikso-sufiks.

3. Długość prefikso-sufiksu jest większa od długości połowy prefiksu, czyli Π[i] > i/2:

Zastanówmy się, kiedy prefiks s[0:i] może być słowem podwójnym. W tym celu przyjrzyjmy się poniższemu
rysunkowi poglądowemu:

Symbolem A oznaczmy pokrywający się fragment prefikso-sufiksu. Ponieważ prefiks i sufiks są sobie równe w
prefikso-sufiksie, fragment A występuje również na początku prefiksu oraz na końcu sufiksu. Aby mogło powstać
słowo podwójne o długości i znaków, i musi być parzyste. Dalej wspólny fragment A sam musi być słowem
podwójnym – w przeciwnym razie początki dwóch podsłów byłyby różne. Podzielmy zatem fragment A na dwa
fragmenty oznaczone małą literką a. Z ostatniego przykładu widzimy wyraźnie, iż pozostały obszar B musi być
podłańcuchem pustym – w przeciwnym razie nie otrzymamy równości podsłowa lewego i prawego:

aaBa ≠ aBaa, jeśli B ≠ Ø

Sytuacja taka wystąpi, gdy szerokość pokrywającego się fragmentu A będzie dokładnie równa 1/3 i, czyli: 3Π[i] = 2i

Ostatni przypadek wystąpi, gdy zachodzi nierówność: 3Π[i] > 2i. Pokażemy, iż w takim razie prefiks s[0:i] jest
słowem podwójnym, jeśli pokrywający się fragment prefikso-sufiksu sam jest słowem podwójnym. Przyjrzyjmy się
poniższemu rysunkowi poglądowemu:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 400 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

Na początku prefiksu pozostaje fragment B. Następnie mamy pokrywający się fragment A i na końcu sufiksu
pozostaje fragment C o tej samej długości co fragment B. Pokrywający się fragment A musi być słowem podwójnym
– dzielimy go zatem na dwie połówki oznaczone małymi literami a. Otrzymujemy dwa podsłowa Ba oraz aC. Aby
prefiks s[0:n] był słowem podwójnym, musi zachodzić równość:

Ba = aC

Przyrównujemy do siebie prefiks i sufiks prefikso-sufiksu. Fragment B jest również prefiksem podsłowa a, natomiast
fragment C jest również sufiksem podsłowa a. Z tego prostego spostrzeżenia bezpośrednio wynika poszukiwana
równość Ba = aC.

Pozostaje jedynie problem sprawdzenia, czy fragment A jest słowem podwójnym. Zwróć jednakże uwagę, iż jest on
zawsze krótszy od prefiksu s[0:i] oraz jest zawsze prefiksem podłańcucha s[0:i]. Zatem algorytm już wcześniej
sprawdzał, czy ten prefiks był słowem podwójnym. Wystarczy zatem zapamiętać ten fakt w osobnej tablicy (nie
zwiększymy złożoności czasowej, jednakże zwiększymy złożoność pamięciową) – lub po prostu sprawdzić, czy A
jest słowem podwójnym (zwiększa się złożoność czasowa). W dodatkowej tablicy zapamiętujemy jedynie fakt, czy
podsłowo A jest podwójne, zatem może to być tablica logiczna. Co więcej nie ma sensu zapamiętywać informacji o
słowach zbudowanych z nieparzystej liczby znaków. Pozwala to zmniejszyć o połowę wielkość tej tablicy.
Podsumowując stwierdzamy:

1. 2Π[i] < i – s[0:i] nie jest słowem podwójnym


2. 2Π[i] = i – s[0:i] jest słowem podwójnym
3. 3Π[i] < 2i – s[0:i] nie jest słowem podwójnym
4. 3Π[i] ≥ 2i – s[0:i] jest słowem podwójnym, jeśli s[0:2Π[i] - i] (prefiks o długości równej pokrywającemu się
fragmentowi prefikso-sufiksu) jest słowem podwójnym.

Wyszukanie wszystkich słów podwójnych w łańcuchu s sprowadza się do wyszukiwania tych słów w kolejnych sufiksach
poczynając od niewłaściwego (obejmującego cały łańcuch) i kończąc na sufiksie dwuznakowym. Algorytm MP działa w czasie
liniowym, natomiast operacja przeszukania wszystkich sufiksów łańcucha s będzie wymagała kwadratowej złożoności
obliczeniowej. Złożoność pamięciowa algorytmu jest liniowa.

Algorytm wyszukiwania słów podwójnych w łańcuchu – wersja nr 2


Wejście:

s – łańcuch tekstowy.

Wyjście:
Wszystkie słowa podwójne zawarte w łańcuchu s.
Elementy pomocnicze:
i,j,k – indeksy znaków w łańcuchu s, i,j,k N
n –

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 401 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

n – długość łańcucha s, n N
b – szerokość prefikso-sufiksu, b C
b2 – podwojona szerokość prefikso-sufiksu, b2 C
Π – tablica maksymalnych prefikso-sufiksów wyznaczanych przez algorytm MP. Indeksy od 0 do
n. Π C
P – tablica logiczna. Indeksy od 0 do n. Element P[i/2] jest równy true, jeśli s[0:i] jest słowem
podwójnym

Lista kroków:
K01: n ← |s| ; wyznaczamy długość
łańcucha s
K02: Dla j = 0,1,...,n-1: wykonuj K03...K17 ; przeglądamy sufiksy s[j:n]
K03: Π[0] ← -1 ; algorytmem MP wyznaczamy
kolejne
K04: b ← -1 ; maksymalne prefikso-sufiksy
dla
K05: Dla i = 1,2,...,n - j: wykonuj K06...KK17 ; danego sufiksu s[j:n]
K06: Dopóki (b > -1) (s[j + b] ≠ s[j + i - 1]):
wykonuj b ← Π[b]
K07: b ←b + 1
K08: Π[i] ← b
K09: b2 ← b shr 1 ; podwójna szerokość prefikso-
sufiksu
K10: Jeśli i nieparzyste, to następny obieg pętli K05 ; słowo podwójne posiada
parzystą liczbę liter
K11: P[i shr 1] ← false ; inicjujemy element tablicy P
K12: Jeśli b2 < i, to następny obieg pętli K05 ; przypadek nr 1
K13: Jeśli b2 = i, to idź do K16 ; przypadek nr 2
K14: Jeśli b2 + b < i shl 1, to następny obieg pętli K05 ; przypadek nr 3 z B ≠ Ø
K15: Jeśli P[(b2 - i) shr 1] = false, to następny obieg pętli K05 ; przypadek nr 3 - obszar
wspólny nie jest słowem
podwójnym
K16: P[i shr 1] ← true; ;zapamiętujemy, iż s[j:j+i] jest
słowem podwójnym
K17: Pisz s[j:j + i] ; wyprowadzamy znalezione
słowo podwójne
K18: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 20 znakowy łańcuch zbudowany ze znaków {A,B}, wyszukuje w nim wszystkie słowa podwójne i
wypisuje je z odpowiednim przesunięciem.

Lazarus

// Wyszukiwanie słów podwójnych


// Data: 9.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const N = 20; // długość łańcucha s
var
s : ansistring;
i,j,k,b,b2 : integer;
PI : array[0..N] of integer;
P : array[0..N shr 1] of boolean;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to N do

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 402 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

s := s + chr(65 + random(2));
// wypisujemy łańcuch s
writeln(s);
// szukamy słów podwójnych
for j := 1 to N - 1 do
begin
PI[0] := -1; b := -1;
for i := 1 to N - j + 1 do
begin
while (b > -1) and (s[j+b] <> s[j+i-1]) do
b := PI[b];
inc(b); PI[i] := b; b2 := b shl 1;
if i and 1 = 0 then
begin
P[i shr 1] := false;
if (b2 < i) then continue;
if (b2 > i) then
begin
if b2 + b < (i shl 1) then continue;
if not(P[(b2 - i) shr 1]) then continue;
end;
P[i shr 1] := true;
for k := 2 to j do write(' ');
writeln(copy(s,j,i));
end;
end;
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie słów podwójnych


// Data: 9.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 20; // długość łańcucha s
int main()
{
string s;
int i,j,k,b,b2,PI[N+1];
bool P[N >> 1];
// generujemy łańcuch s
srand((unsigned)time(NULL));
s = "";
for(i = 0; i < N; i++)
s += char(65 + rand() % 2);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy słów podwójnych
for(j = 0; j < N - 1; j++)
{
PI[0] = b = -1;
for(i = 1; i <= N - j; i++)
{
while((b > -1) && (s[j+b] != s[j+i-1]))
b = PI[b];
PI[i] = ++b; b2 = b << 1;
if(!(i & 1))
{
P[i >> 1] = false;
if(b2 < i) continue;
if(b2 > i)
{
if(b2 + b < (i << 1)) continue;
if(!P[(b2 - i) >> 1]) continue;
}
P[i >> 1] = true;
for(k = 0; k < j; k++) cout << " ";

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 403 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

cout << s.substr(j,i) << endl;


}
}
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie słów podwójnych


' Data: 9.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 20 ' długość łańcucha s
Dim As String s
Dim As Integer i,j,k,b,b2,PI(N)
Dim As Byte P(N Shr 1)
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To N: s += Chr(65 + Cint(Rnd)): Next
' wypisujemy łańcuch s
Print s
' szukamy słów podwójnych
For j = 1 To N - 1
PI(0) = -1: b = -1
For i = 1 To N - j + 1
While (b > -1) And (Mid(s,j+b,1) <> Mid(s,j+i-1,1)): b = PI(b): Wend
b += 1: PI(i) = b: b2 = b Shl 1
If (i And 1) = 0 Then
P(i Shr 1) = 0
If (b2 < i) Then Continue For
If (b2 > i) Then
If b2 + b < (i Shl 1) Then Continue For
If P((b2 - i) Shr 1) = 0 Then Continue For
End If
P(i Shr 1) = 1
For k = 2 To j: Print " ";: Next
Print Mid(s,j,i)
End If
Next
Next
Print
End

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 404 / 519


Algorytmy i Struktury Danych - Słowa podwójne 2014-10-03

szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0059.php 405 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Wyszukiwanie palindromów
Tematy pokrewne Podrozdziały
Łańcuchy znakowe Rozwiązanie 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Rozwiązanie 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach

Problem
W łańcuchu s znaleźć wszystkie palindromy o długości większej od 1.

Przez palindrom (ang. palindrome) rozumiemy łańcuch znakowy s, który czyta się tak samo w obu kierunkach.
Przykład:
ABBCBBA – jest palindromem
ABBCABA – nie jest palindromem

Palindromy pojawiają się w genetyce (łańcuchy DNA, RNA), w tekstach, muzyce, matematyce, geometrii, fizyce itd. Stąd duże
zainteresowanie informatyków w efektywnych algorytmach ich znajdowania. W badaniach genetycznych często szuka się tzw.
przybliżonych palindromów (ang. aproximate palindromes), tzn. palindromów, w których do k-znaków może być błędnych, czyli
nie pasujących do dokładnego palindromu (ang. exact palindrome). Takie palindromy występują w łańcuchach DNA, w których
wystąpiły różnego rodzaju błędy genetyczne. Problemem palindromów przybliżonych nie zajmujemy się w tym opracowaniu.

Wprowadźmy symbol sR, który oznacza łańcuch znakowy o odwróconej kolejności znaków w stosunku do łańcucha s.

Przykład:
s = ABCD

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 406 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

sR = DCBA

Łańcuch s jest palindromem, jeśli da się rozłożyć na dwa podłańcuchy w i wR wg poniższego schematu:

s = wwR – palindrom parzysty (ang. even palindrome), lub


s = wXwR – palindrom nieparzysty (ang. odd palindrome), gdzie X jest dowolnym symbolem alfabetu.

Przykład:
ABCDDCBA – palindrom parzysty → ABCD + DCBA
ABCDADCBA – palindrom nieparzysty → ABCD + A + DCBA

Zauważ, iż zgodnie z tą definicją palindromem jest każdy łańcuch pusty – rozkłada się na dwa puste podłańcuchy – oraz każdy
łańcuch jednoliterowy – rozkłada się na znak X i dwa puste podłańcuchy. Ponieważ są to przypadki trywialne, w zadaniu
wprowadzono zastrzeżenie, iż wyszukiwane palindromy muszą być co najmniej dwuznakowe.

Rozwiązanie nr 1
Pierwszy algorytm wyszukiwania palindromów jest algorytmem naiwnym. Rozkłada on dany łańcuch znakowy s na wszystkie
możliwe podłańcuchy p o długości nie mniejszej niż 2 znaki i sprawdza następnie, czy dadzą się przedstawić w postaci wwR lub
wXwR. Sprawdzenie polega na porównywaniu znaków od początku i od końca podłańcucha. W tym celu wykorzystuje się dwa
indeksy. Jeden z nich ustawia się na pierwszym znaku podłańcucha p, a drugi na ostatnim. Następnie porównujemy wskazywane
przez te indeksy znaki podłańcucha p. Jeśli znaki są różne, to podłańcuch p nie jest palindromem. Jeśli porównywane znaki są
równe, to indeksy przesuwamy – lewy w prawo, a prawy w lewo. Jeśli indeksy się miną, to zachodzi jedna z dwóch równości:

p = wwR, lub p = wXwR

W takim przypadku p jest palindromem. Wyszukanie wszystkich palindromów zawartych w łańcuchu s proponowaną metodą
posiada sześcienną klasę złożoności obliczeniowej O(n3), gdzie n jest długością łańcucha s.

Algorytm naiwny wyszukiwania palindromów


Wejście:
s – łańcuch tekstowy.

Wyjście:
Wszystkie palindromy zawarte w łańcuchu s.
Elementy pomocnicze:
i,j – indeksy znaków w łańcuchu s, i,j N
n – długość łańcucha s, n N
iP – prawy indeks, iP N
iL – lewy indeks, iL N

Lista kroków:
K01: n ← |s|
K02: Dla i = 0,1,...,n - 2, wykonuj K03...K10 ; przeglądamy łańcuch s
K03: Dla j = i+2, i+3,...,n-1: wykonuj K04...K10
K04: iL ← i ; lewy indeks na pierwszym znaku
podsłowa
K05: iP ← j - 1 ; prawy indeks na ostatnim znaku
podsłowa
K06: Dopóki iL < iP wykonuj K07...K09 ; sprawdzamy, czy podsłowo jest
palindromem
K07: Jeśli s[iL] ≠ s[iP], to następny obieg pętli K03
K08: iL ← iL + 1 ; lewy indeks przesuwamy w prawo
K09: iP ← iP - 1 ; a prawy w lewo
K10: Pisz s[i,j] ; wyprowadzamy znaleziony palindrom
K11: Zakończ

Program

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 407 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 40 znakowy łańcuch zbudowany ze znaków {A,B,C,D}, wyszukuje w nim wszystkie palindromy i
wypisuje je z odpowiednim przesunięciem.

Lazarus

// Wyszukiwanie palindromów
// Data: 9.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
const N = 40; // długość łańcucha s
var
s : ansistring;
i,j,k,iP,iL : integer;
t : boolean;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to N do s := s + chr(65 + random(4));
// wypisujemy łańcuch s
writeln(s);
// szukamy palindromów
for i := 1 to N - 1 do
for j := i + 2 to N + 1 do
begin
iL := i; iP := j - 1; t := true;
while iL < iP do
begin
if s[iL] <> s[iP] then
begin
t := false; break;
end;
inc(iL); dec(iP);
end;
if t then
begin
for k := 2 to i do write(' ');
writeln(copy(s,i,j-i));
end;
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie palindromów
// Data: 9.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 40; // długość łańcucha s
int main()
{
string s;
int i,j,k,iP,iL;
bool t;
// generujemy łańcuch s

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 408 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

srand((unsigned)time(NULL));
s = "";
for(i = 0; i < N; i++) s += char(65 + rand() % 4);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy palindromów
for(i = 0; i < N - 1; i++)
for(j = i + 2; j <= N; j++)
{
iL = i; iP = j - 1; t = true;
while(iL < iP)
if(s[iL++] != s[iP--])
{
t = false; break;
}
if(t)
{
for(k = 0; k < i; k++) cout << " ";
cout << s.substr(i,j-i) << endl;
}
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie palindromów


' Data: 9.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Const N = 40 ' długość łańcucha s
Dim As String s
Dim As Integer i,j,k,iP,iL,t
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To N: s += Chr(65 + Cint(Rnd * 3)): Next
' wypisujemy łańcuch s
Print s
' szukamy palindromów
For i = 1 To N - 1
For j = i + 2 To N + 1
iL = i: iP = j - 1: t = 1
While iL < iP
If Mid(s,iL,1) <> Mid(s,iP,1) Then
t = 0: Exit While
End If
iL += 1: iP -= 1
Wend
If t = 1 Then
For k = 2 To i: Print " ";: Next
Print Mid(s,i,j-i)
End If
Next
Next
Print
End

Wynik
BCACCAABCBBDCCCCDBBBDBDDBDCDBBCBADBBBABB
CAC
ACCA
CC
AA
BCB
BB
BBDCCCCDBB
BDCCCCDB
DCCCCD
CC
CCC
CCCC
CC
CCC
CC
DBBBD

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 409 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

DBBBD
BB
BBB
BB
BDB
DBD
DBDDBD
BDDB
DD
DBD
BDCDB
DCD
BB
BCB
BB
BBB
BB
BBABB
BAB
BB

Wyszukiwanie palindromów
(C)2012 mgr Jerzy Wałaszek

Wykonaj

...

Rozwiązanie nr 2
Drugie podejście do rozwiązania problemu wyszukiwania wszystkich palindromów w łańcuchu znakowym s opiera się na
własnościach palindromów. Przedstawiony tutaj algorytm został opracowany w 1975 przez Glenna Manachera z Computer
Center and Department of Information Engineering, University of Illinois, Chicago, IL. Do opisu algorytmu Manachera wprowadzimy
kilka nowych pojęć.
Niech pP będzie palindromem parzystym o postaci pP = wwR, gdzie w jest niepustym podłańcuchem.
Niech pN będzie palindromem nieparzystym o postaci pN = wXwR.

Promieniem rp palindromu p będziemy nazywali długość podsłowa w, czyli rp = |w|


Palindrom parzysty pP posiada zawsze długość |pP| = 2rp. Palindrom nieparzysty pN posiada zawsze długość |pN| =
2rp + 1.
Środkiem palindromu p jest pozycja is = rp – jest to pozycja pierwszego znaku za słowem w (można również
definiować środek palindromu jako pozycję ostatniego znaku podsłowa w, lecz sądzę, iż nasz sposób jest lepszy,
gdyż nie wymaga wprowadzania żadnych zmian dla palindromów nieparzystych) Dla palindromu parzystego środek
wypadnie na pierwszym znaku wR, natomiast dla palindromu nieparzystego środek wypadnie na znaku X:

pP[rp] = wR[0]
pN[rp] = X

Algorytm Manachera nie wyznacza wszystkich palindromów, jak robi to algorytm naiwny, lecz maksymalne palindromy, których
środki występują na kolejnych pozycjach znakowych przeszukiwanego łańcucha s. Dzięki takiemu podejściu redukujemy
złożoność obliczeniową fazy przeszukiwania łańcucha s. Mając maksymalny palindrom możemy bez problemów wyznaczyć
zawarte w nim podpalindromy. Wykorzystujemy tutaj własność symetrii palindromu:

Przykład:
rp palindrom parzysty palindrom nieparzysty
4 ABCDDCBA ABCDADCBA
3 BCDDCB BCDADCB
2 CDDC CDADC
1 DD DAD

Dla danego łańcucha s algorytm Manachera tworzy tablicę dwuwymiarową R.:

R[0,...] – promienie palindromów parzystych


R[1,...] – promienie palindromów nieparzystych

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 410 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

Indeksy tych tablic określają kolejne pozycje znakowe w łańcuchu s, natomiast elementy tablic zawierają maksymalne promienie
palindromów o środkach na danej pozycji znakowej.
Używając w odpowiedni sposób tablicy R oraz własności symetrii palindromu algorytm Manachera wykorzystuje sprytnie
informację o wcześniej wyznaczonych promieniach palindromów maksymalnych do wyszukiwania następnych palindromów. Otóż
po wyznaczeniu promienia rp palindromu na pozycji i-tej w łańcuchu s, sprawdzane są promienie palindromów na kolejnych
pozycjach poprzedzających pozycję i-tą w obszarze podsłowa w – tutaj algorytm wymaga dwóch wersji – osobnej dla palindromów
parzystych i osobnej dla nieparzystych. Zasada jest identyczna dla obu wersji. Rozważmy zatem możliwe przypadki (dla
palindromu parzystego):

Na pozycji i - k, k = 1,2,...,rp, promień palindromu wynosi 0 – czyli nie istnieje palindrom o środku na pozycji i - k.
Skoro tak, to przez symetrię wnioskujemy, iż na pozycji lustrzanej i + k również nie będzie żadnego palindromu.
Pozycja i + k możne zostać pominięta przy dalszym wyszukiwania palindromów.

Na pozycji i - k jest palindrom o promieniu r < rp - k. Taki palindrom w całości zawiera się wewnątrz rozważanego
palindromu i co więcej, nie styka się z jego brzegiem. Poprzez symetrię wnioskujemy, iż na pozycji i + k również
musi występować taki sam palindrom, którego już dalej nie da się rozszerzyć. Pozycji i + k nie musimy już dalej
sprawdzać.

Na pozycji i - k jest palindrom o promieniu r > rp - k. Taki palindrom wykracza z lewej strony poza obszar
rozważanego palindromu. Na pozycji i + k znajduje się palindrom o promieniu r = rp - k i palindromu tego nie da się
już rozszerzyć. Wyjaśnienie tego faktu jest bardzo proste – gdyby palindrom na pozycji i + k posiadał większy
promień niż wyliczone r, to również z uwagi na symetrię przeglądany palindrom posiadałby promień większy od rp, a
przecież jest to palindrom maksymalny. Pozycję i + k również możemy pominąć.

Pozostał ostatni przypadek – na pozycji i - k występuje palindrom o promieniu r = rp - k. Taki sam palindrom musi

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 411 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

być na pozycji i + k, jednakże w tym przypadku palindrom ten może być rozszerzalny. Pozycję i + k musimy zatem
sprawdzić na obecność palindromu o promieniu większym od r.

Z powyższych rozważań otrzymujemy następujący algorytm działający w czasie liniowym O(n):

Algorytm Manachera wyszukiwania palindromów


Wejście

s – łańcuch tekstowy.

Wyjście:
Wszystkie palindromy zawarte w łańcuchu s.
Elementy pomocnicze:
i – indeksy środków palindromów, i N
j – indeksuje wymiar tablicy R. Wartość 0 dotyczy palindromów parzystych, wartość 1 dotyczy
palindromów nieparzystych, j N
k – zmienna pomocnicza do indeksowania środków palindromów wewnętrznych, k N
rp – wyznaczany promień palindromu maksymalnego, rp N
n – długość łańcucha s, n N
R – tablica dwuwymiarowa, pierwszy wymiar określa rodzaj palindromu, drugi wymiar zawiera
indeksy od 0 do n, R N

Lista kroków:
K01: n ← |s| ; obliczamy długość łańcucha s
K02: s ← w1 + s + w2 ; na początku i na końcu s dodajemy
wartowników, w1 ≠ w2
K03: Dla j = 0,1 wykonuj K04...K17 ; wyznaczamy promienie palindromów
parzystych i nieparzystych
K04: R[j,0] ← 0 ; pierwszy promień jest zawsze
zerowy
K05: i ←1 ; ustawiamy i na pierwszym znaku s
za wartownikiem w1
K06: rp ← 0 ; początkowy promień palindromu jest
zerowy
K07: Dopóki i ≤ n, wykonuj K08...K17 ; przechodzimy przez kolejne środki
palindromów
K08: Dopóki s[i - rp - 1] = s[i + j + rp], ; wyznaczamy promień
wykonuj rp ← rp + 1 maksymalnego palindromu o środku
; na pozycji i-tej
K09: R[j,i] ← rp ; wyliczony promień zapamiętujemy w
tablicy R
K10: k ←1 ; przeglądamy promienie
w c z e ś n i e j s z y c h palindromów
wewnętrznych
K11: Jeśli R[j,i - k] = rp - k, to idź do K16 ; sprawdzamy ostatni przypadek
K12: Jeśli k ≥ rp, to idź do K16 ; musimy być wewnątrz palindromu
K13: R[j,i + k] ← min(R[j,i - k],rp - k) ; wyznaczamy promień lustrzanego
palindromu wewnętrznego
K14: k ←k + 1 ; przechodzimy do następnego
palindromu wewnętrznego
K15: Idź do K11
K16: rp ← max(rp - k,0) ; wyznaczamy promień początkowy
palindromu
K17: i ←i + k ; pomijamy wyliczone palindromy
lustrzane
K18: s ← s[1:n] ; usuwamy wartowników w1 i w2 z
łańcucha s
K19: Dla i = 1,2,...,n wykonuj K20...K21 ; wyprowadzamy kolejne palindromy
parzyste i nieparzyste
K20: Dla j = 0,1 wykonuj K21
K21: Dla rp = R[j,i], R[j,i ]- 1,...,1 pisz s[i - rp - 1:i + 2rp + j]
K22: Zakończ

Program
Ważne:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 412 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 40 znakowy łańcuch zbudowany ze znaków {A,B,C,D}, wyszukuje w nim wszystkie palindromy i wypisuje je z
odpowiednim przesunięciem.

Lazarus

// Wyszukiwanie palindromów algorytmem Manachera


// Data: 12.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
uses Math;
const N = 40; // długość łańcucha s
var
s : ansistring;
i,j,rp,k : integer;
R : array[0..1,1..N + 1] of integer;
begin
// generujemy łańcuch s
randomize;
s := '';
for i := 1 to N do
s := s + chr(65 + random(4));
// wypisujemy łańcuch s
writeln(s);
// szukamy palindromów
s := '@' + s + '#'; // wstawiamy wartowników
for j := 0 to 1 do
begin
R[j,1] := 0; i := 2; rp := 0;
while i <= N + 1 do
begin
while s[i - rp - 1] = s[i + j + rp] do inc(rp);
R[j,i] := rp;
k := 1;
while (R[j,i - k] <> rp - k) and (k < rp) do
begin
R[j,i + k] := min(R[j,i - k],rp - k);
inc(k);
end;
rp := max(rp - k,0);
inc(i,k);
end;
end;
s := copy(s,2,N); // usuwamy wartowników
// prezentujemy wyliczone palindromy
for i := 2 to N + 1 do
begin
for j := 0 to 1 do
for rp := R[j,i] downto 1 do
begin
for k := 3 to i - rp do write(' ');
writeln(copy(s,i - rp - 1,2 * rp + j));
end;
end;
writeln;
end.

Code::Blocks

// Wyszukiwanie palindromów algorytmem Manachera


// Data: 12.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 413 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
const int N = 40; // długość łańcucha s
int main()
{
string s;
int i,j,rp,k,R[2][N+1];
// generujemy łańcuch s
srand((unsigned)time(NULL));
s = "";
for(i = 0; i < N; i++) s += char(65 + rand() % 4);
// wypisujemy łańcuch s
cout << s << endl;
// szukamy palindromów
s = "@" + s + "#"; // wstawiamy wartowników
for(j = 0; j <= 1; j++)
{
R[j][0] = rp = 0; i = 1;
while(i <= N)
{
while(s[i - rp - 1] == s[i + j + rp]) rp++;
R[j][i] = rp;
k = 1;
while((R[j][i - k] != rp - k) && (k < rp))
{
R[j][i + k] = min(R[j][i - k],rp - k);
k++;
}
rp = max(rp - k,0);
i += k;
}
}
s = s.substr(1,N); // usuwamy wartowników
// prezentujemy wyliczone palindromy
for(i = 1; i <= N; i++)
{
for(j = 0; j <= 1; j++)
for(rp = R[j][i]; rp > 0; rp--)
{
for(k = 1; k < i - rp; k++) cout << " ";
cout << s.substr(i - rp - 1,2 * rp + j) << endl;
}
}
cout << endl;
return 0;
}

Free Basic

' Wyszukiwanie palindromów algorytmem Manachera


' Data: 12.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Function max(Byval a As Integer, Byval b As Integer) As Integer
If a > b Then
Return a
Else
Return b
End If
End Function
Function min(Byval a As Integer, Byval b As Integer) As Integer
If a < b Then
Return a
Else
Return b
End If
End Function
Const N = 40 ' długość łańcucha s

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 414 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

Dim As String s
Dim As Integer i,j,rp,k,R(1,N+1)
' generujemy łańcuch s
Randomize
s = ""
For i = 1 To N: s += Chr(65 + Cint(Rnd * 3)): Next
' wypisujemy łańcuch s
Print s
' szukamy palindromów
s = "@" + s + "#" ' wstawiamy wartowników
For j = 0 To 1
R(j,1) = 0: i = 2: rp = 0
While i <= N + 1
While Mid(s,i - rp - 1,1) = Mid(s,i + j + rp,1): rp += 1: Wend
R(j,i) = rp
k = 1
While (R(j,i - k) <> rp - k) And (k < rp)
R(j,i + k) = min(R(j,i - k),rp - k)
k += 1
Wend
rp = max(rp - k,0)
i += k
Wend
Next
s = Mid(s,2,N) ' usuwamy wartowników
' prezentujemy wyliczone palindromy
For i = 2 To N + 1
For j = 0 To 1
For rp = R(j,i) To 1 Step -1
For k = 3 To i - rp: Print " ";: Next
Print Mid(s,i - rp - 1,2 * rp + j)
Next
Next
Next
Print
End

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 415 / 519


Algorytmy i Struktury Danych - Palindromy 2014-10-03

I Liceum Ogólnokształcące
GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0060.php 416 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

MasterMind - kompuer kontra człowiek


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach

Problem
Opracować algorytm gry MasterMind w wersji: komputer kontra człowiek.

Gra MasterMind (Mistrz Intelektu/Umysłu) jest grą o prostych regułach, lecz wymagającą logicznego myślenia. Została
wynaleziona przez Izraelczyka Mordecai Meirowica na początku lat 70-tych ubiegłego stulecia. Wynalazca początkowo chciał
zainteresować swoją grą duże firmy produkujące gry, jednakże wszędzie został zignorowany. W końcu udało mu się zawrzeć
kontrakt z małą angielską firmą Invicta Plastics, która produkowała pomoce dydaktyczne. Grę nieco przebudowano i nadano jej
nazwę MasterMind. Wkrótce sprzedano ponad 50 milionów egzemplarzy w ponad 80 krajach, co sprawiło, iż stała się ona
kasowym przebojem rynku gier logicznych w latach 70-tych.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 417 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

Zasady gry są następujące:

Do gry potrzebna jest plansza oraz kolorowe, plastikowe grzybki z wyprofilowanymi główkami, które wtykamy
nóżkami w otwory na planszy. Grzybki są w dwóch rodzajach: kodowe o sześciu różnych kolorach oraz kluczowe,
mniejsze o dwóch kolorach – czarnym i białym. W grze uczestniczy zawsze dwóch graczy – koder (ang. coder)
oraz łamacz kodu (ang. code breaker). Koder przy pomocy grzybków kodowych tworzy tajny kod zbudowany z 4
kolorów. Kod ten zostaje ukryty przed drugim graczem specjalną przykrywką na końcu planszy. Łamacz kodu w co
najwyżej 6 podejściach stara się odgadnąć kod kodera. W tym celu z kolorowych grzybków układa swoje propozycje
4 kolorowych kodów. Dla każdej propozycji koder odpowiada kombinacją co najwyżej 4 grzybków kluczowych.
Grzybek czarny oznacza, iż w kodzie łamacza jeden kolor (nie wiadomo który) jest na tym samym miejscu, co w
kodzie kodera. Grzybek biały oznacza, iż kolor łamacza występuje w kodzie kodera, lecz na innym miejscu. Brak
grzybków kluczowych oznacza, iż żaden z kolorów kodu łamacza nie występuje w kodzie kodera.

Na pierwszy rzut oka gra MasterMind nie wygląda na problem tekstowy. Skoro tak, to sprowadźmy ją do problemu tekstowego. W
tym celu grzybki kodowe przedstawmy jako litery ze zbioru {A,B,C,D,E,F}. Koder będzie tworzył z tych liter 4-ro literowy kod tajny,
który ma odgadnąć drugi gracz – łamacz kodu. Grzybki kluczowe przedstawmy jako znaki:

x – literka kodu łamacza odpowiada dokładnie literce kodu kodera co do rodzaju i co do położenia w kodzie.
o – literka kodu łamacza zgadza się z literką kodu kodera tylko co do rodzaju.

Przykład:
Kod Klucz
Kod kodera BFAC
1. Kod łamacza ABCD ooo
2. Kod łamacza BADE xo
3. Kod łamacza BACF xooo
4. Kod łamacza BFCA xxoo
5. Kod łamacza BFAC xxxx

Zadanie naszego algorytmu będzie polegało zatem na wygenerowaniu 4-literowego kodu kodera, odczycie kodu łamacza,
ocenieniu go i wyświetleniu kodu klucza. Odczyt kodu łamacza przerywany jest po 6 próbach lub po otrzymaniu kodu klucza
równego xxxx – wszystkie litery kodu łamacza zgadzają się z literami kodu kodera co do położenia i rodzaju. W obu przypadkach
wyświetlony będzie kod kodera.
Tworzenie kodu klucza jest dwuetapowe:

Najpierw tworzymy kopię roboczą kodu kodera, ponieważ algorytm niszczy zawartość tego kodu.
W pierwszym etapie wyznaczamy znaki zgodne co do pozycji i rodzaju. W tym celu wystarczy porównać ze sobą
kolejne znaki kodu kodera i łamacza. Jeśli są zgodne, do kodu klucza wstawiamy znak x. Jednakże musimy
pamiętać o zastąpieniu w kodzie kodera i łamacza zgodnego znaku różnymi symbolami, które nie występują w
alfabecie. Chodzi o to, aby w drugim etapie dany znak nie został ponownie zaliczony.
W drugim etapie wyznaczamy znaki zgodne co do rodzaju. W tym celu każdy znak kodu kodera porównujemy z
każdym znakiem kodu łamacza aż do napotkania zgodności. Jeśli mamy zgodność, to do kodu klucza wstawiamy
znak o. Ponieważ znaki zgodne co do rodzaju i pozycji zostały usunięte z kodu łamacza w pierwszym etapie,
zgodność może oznaczać tylko równe znaki, lecz na różnych pozycjach. Znaki zgodne również usuwamy z kodu
łamacza, aby nie zostały ponownie zaliczone.

Algorytm gry MasterMind – komputer kontra człowiek


Wejście

4 znakowe kody łamacza w łańcuchu słamacz

Wyjście:
4 znakowe kody klucza dla kodów łamacza. Na końcu kod kodera.
Elementy pomocnicze:
skoder – kod kodera
sk – kopia kodu kodera
sklucz – kod klucza
i,j – indeksy, i,j N
runda – numer rundy, runda N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 418 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

Lista kroków:
K01: Generuj skoder ; generujemy 4-znakowy kod kodera
K02: Dla runda = 1,2,...,6: wykonuj K03...K19 ; łamacz ma maksymalnie 6 podejść
K03: Czytaj słamacz ; odczytujemy kod łamacza
K04: Dopóki |słamacz| < 4 wykonuj: ; kod łamacza musi być co najmniej
słamacz ← słamacz + w2 ; 4-ro znakowy, w2 – wartownik
K05: sk ← skoder ; kopiujemy kod kodera
K06: sklucz ← '' ; zerujemy kod klucza
K07: Dla i = 1,2,...,4 wykonuj K08...K11 ; szukamy znaków zgodnych co do typu i pozycji
K08: Jeśli sk[i] ≠ slamacz[i], to ; znaki niezgodne pomijamy
następny obieg pętli K07
K09: sklucz ← sklucz + "x" ; w kluczu umieszczamy znak x
K10: sk[i] ← w1 ; znaki zastępujemy wartownikami,
K11: słamacz[i] ← w2 ; aby nie były później zaliczane
K12: Dla i = 1,2,...,4, wykonuj K13...K18 ; szukamy znaków zgodnych co do pozycji
K13: Jeśli sk[i] = w1, to ; znaki usunięte pomijamy
następny obieg pętli K12
K14: Dla j = 1,2,...,4, wykonuj K15...K18 ; znak kodera porównujemy z kolejnymi znakami
łamacza
K15: Jeśli sk[i] ≠ słamacz[j], to ; pomijamy znaki różne
następny obieg pętli K14
K16: sklucz ← sklucz + "o" ; w kluczu umieszczamy znak o
K17: słamacz[j] ← w2 ; znak łamacza zastępujemy wartownikiem
K18: Następny obieg pętli K12 ; przechodzimy do następnego znaku kodera
K19: Pisz sklucz ; wyprowadzamy kod klucza
K20: Jeśli sklucz = "xxxx", to idź do K21 ; kończymy pętlę K02, jeśli kod kodera odgadnięty
K21: Pisz skoder ; wyprowadzamy kod kodera
K22: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program generuje 4 znakowy kod kodera zbudowany ze znaków A,B,C,D,E lub F. Zadaniem gracza jest odgadnięcie
tego kodu w co najwyżej 6 podejściach. W tym celu gracz – łamacz kodu – wprowadza swoje kody. W odpowiedzi
komputer wyświetla dla nich kody klucza zbudowane ze znaków x i o.

Lazarus

// MasterMind - komputer <-> człowiek


// Data: 13.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
skoder,slamacz,sklucz,sk : string;
i,j,runda : integer;
begin
// generujemy kod kodera
randomize;
skoder := '';
for i := 1 to 4 do
skoder := skoder + chr(65+random(6));
// rozpoczynamy rozgrywkę
for runda := 1 to 6 do
begin
// odczytujemy kod łamacza
write('Runda ',runda,' : ');
readln(slamacz);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 419 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

// normalizujemy kod łamacza


while length(slamacz) < 4 do
slamacz := slamacz + '$';
// generujemy kod klucza
sk := skoder; // kopia robocza kodu kodera
sklucz := '';
for i := 1 to 4 do
if sk[i] = slamacz[i] then
begin
sklucz := sklucz + 'x';
sk[i] := '#'; // wartownik w1
slamacz[i] := '$'; // wartownik w2
end;
for i := 1 to 4 do
if sk[i] <> '#' then
for j := 1 to 4 do
if sk[i] = slamacz[j] then
begin
sklucz := sklucz + 'o';
slamacz[j] := '$'; // wartownik w2
break;
end;
// wyświetlamy kod klucza
writeln(' : ',sklucz);
// jeśli odgadnięto kod kodera, kończymy
if sklucz = 'xxxx' then break;
end;
writeln('KOD : ',skoder);
writeln;
end.

Code::Blocks

// MasterMind - komputer <-> człowiek


// Data: 13.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string skoder,slamacz,sklucz,sk;
int i,j,runda;
// generujemy kod kodera
srand((unsigned)time(NULL));
skoder = "";
for(i = 0; i < 4; i++)
skoder += char(65 + rand() % 6);
// rozpoczynamy rozgrywkę
for(runda = 1; runda <= 6; runda++)
{
// odczytujemy kod łamacza
cout << "Runda " << runda << " : ";
cin >> slamacz;
// normalizujemy kod łamacza
while(slamacz.length() < 4)
slamacz += slamacz + '$';
// generujemy kod klucza
sk = skoder; // kopia robocza kodu kodera
sklucz = "";
for(i = 0; i < 4; i++)
if(sk[i] == slamacz[i])
{
sklucz += 'x';

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 420 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

sk[i] = '#'; // wartownik w1


slamacz[i] = '$'; // wartownik w2
}
for(i = 0; i < 4; i++)
if(sk[i] != '#')
for(j = 0; j < 4; j++)
if(sk[i] == slamacz[j])
{
sklucz += 'o';
slamacz[j] = '$'; // wartownik w2
break;
}
// wyświetlamy kod klucza
cout << " : " << sklucz << endl;
// jeśli odgadnięto kod kodera, kończymy
if(sklucz == "xxxx") break;
}
cout << "KOD : " << skoder << endl << endl;
return 0;
}

Free Basic

' MasterMind - komputer <-> człowiek


' Data: 13.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String skoder,slamacz,sklucz,sk
Dim As Integer i,j,runda
' generujemy kod kodera
Randomize
skoder = ""
For i = 1 To 4: skoder += Chr(65 + Cint(Rnd * 5)): Next
' rozpoczynamy rozgrywkę
For runda = 1 To 6
' odczytujemy kod łamacza
Print "Runda";runda;" : ";: Line Input slamacz
' normalizujemy kod łamacza
While Len(slamacz) < 4: slamacz += "$": Wend
' generujemy kod klucza
sk = skoder ' kopia robocza kodu kodera
sklucz = ""
For i = 1 To 4
If Mid(sk,i,1) = Mid(slamacz,i,1) Then
sklucz += "x"
Mid(sk,i,1) = "#" ' wartownik w1
Mid(slamacz,i,1) = "$" ' wartownik w2
End If
Next
For i = 1 To 4
If Mid(sk,i,1) <> "#" Then
For j = 1 To 4
If Mid(sk,i,1) = Mid(slamacz,j,1) Then
sklucz += "o"
Mid(slamacz,j,1) = "$" ' wartownik w2
Exit For
End If
Next
End If
Next
' wyświetlamy kod klucza
Print " : ";sklucz
' jeśli odgadnięto kod kodera, kończymy
If sklucz = "xxxx" Then Exit For
Next
Print "KOD : ";skoder
Print
End

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 421 / 519


Algorytmy i Struktury Danych - MasterMind komputer kontra człowiek 2014-10-03

Wynik
Runda 1 : ABCD
: oo
Runda 2 : BCAF
: xxx
Runda 3 : DCAF
: xxx
Runda 4 : FCAF
: xxx
Runda 5 : ECAF
: xxxx
KOD : ECAF

MasterMind
(C)2012 mgr Jerzy Wałaszek

Nowa gra

Gotowe

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0061.php 422 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

MasterMind - człowiek kontra komputer


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Wbudowane generatory liczb pseudolosowych
Generowanie liczb pseudolosowych
Podstawowe operacje na tablicach

Problem
Opracować algorytm gry MasterMind – człowiek kontra komputer.

Zasady gry MasterMind opisaliśmy w poprzednim rozdziale. Tym razem zadanie jest nieco trudniejsze – kod wymyśla człowiek.
Komputer komunikuje się z człowiekiem za pomocą kodów kodera, które sam tworzy oraz kodów klucza wprowadzanych przez
człowieka na podstawie kodów kodera. Zadaniem algorytmu jest odgadnięcie wymyślonego przez człowieka kodu w co najwyżej 6
ruchach. Dla uproszczenia zakładamy, iż człowiek nie będzie popełniał błędów – w rzeczywistości należałoby je uwzględnić.
Zastanówmy się nad tym z jak dużym zbiorem mamy do czynienia. Kod kodera składa się z 4 liter należących do alfabetu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 423 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

{A,B,C,D,E,F}, z których każda może pojawić się na dowolnej pozycji kodu. Wszystkich możliwych do utworzenia słów kodowych
będzie:

6 × 6 × 6 × 6 = 1296

Jeśli nie znasz kombinatoryki, to wyjaśnienie tego wzoru jest następujące:

Na pierwszej pozycji może pojawić się jedna z 6 liter – A,B,C,D,E lub F. Daje to 6 różnych możliwości:
Axxx, Bxxx, Cxxx, Dxxx, Exxx, Fxxx, gdzie x – pozycja jeszcze nie zajęta
W każdej z tych 6 kombinacji drugą literę możemy wybrać również na 6 różnych sposobów. Na
przykład dla Cxxx otrzymamy:
CAxx, CBxx, CCxx, CDxx, CExx, CFxx
Zatem ze względu na dwie pierwsze litery kodu otrzymujemy 6 × 6 = 36 kombinacji. W każdej z nich
trzecią literę również możemy wybrać na 6 różnych sposobów. Na przykład dla BFxx otrzymamy:
BFAx, BFBx, BFCx, BFDx, BFEx, BFFx
Ze względu na 3 pierwsze litery kodu otrzymamy 6 × 6 × 6 = 216 kombinacji. W każdej z nich
pozostała ostatnia pozycja, w której możemy znów umieścić znak na 6 różnych sposobów i
ostatecznie otrzymujemy wzór wyjściowy.

Nie jest to zbiór specjalnie duży dla współczesnego komputera Pentium – możemy zastosować metodę naiwną, która polega na
tym, iż tworzymy zbiór Z wszystkich możliwych słówek kodowych. Następnie w pętli wykonującej się maksymalnie 6 razy
losujemy ze zbioru Z jedno słowo kodowe słamacz, usuwamy je ze zbioru Z i prezentujemy człowiekowi. W odpowiedzi człowiek
zwraca nam kod klucza sklucz zbudowany z liter x i o wg zasad gry MasterMind (ważne jest, aby litery x poprzedzały w kodzie
litery o). Jeśli otrzymamy kod xxxx, to kończymy, gdyż słowo kodera zostało odgadnięte. W przeciwnym przeglądamy kolejno
pozostałe w zbiorze Z słowa kodowe. Dla przeglądanych słów kodowych oraz dla wylosowanego wcześniej słowa słamacz
obliczamy nowy kod kluczowy sklucz2 i porównujemy go z sklucz wprowadzonym przez człowieka. Jeśli kody się różnią, to dane
słowo kodowe usuwamy ze zbioru, gdyż nie może ono być poszukiwanym słowem kodera. W ten sposób zbiór możliwych słów
kodowych się zmniejsza. Jeśli jest niepusty, to wracamy na początek pętli i kontynuujemy losowanie kolejnego słowa. Jeśli zbiór
jest pusty, to nastąpiła sprzeczność – człowiek popełnił gdzieś pomyłkę przy wprowadzaniu kodów kluczy. Zatem kończymy. Jeśli
kod człowieka nie zostanie odgadnięty w 6 ruchach, to algorytm kończy się wypisaniem wszystkich pozostałych w zbiorze słów
kodowych.
Pomimo swojej prostoty algorytm ten całkiem dobrze radzi sobie z odgadywaniem kodów kodera w 6 ruchach. Oczywiście, z
uwagi na losowy charakter typowania przez komputer swoich ruchów, istnieje mała szansa, iż komputer przegra wybierając
nieefektywne posunięcia (tzn. takie, które eliminują ze zbioru Z małą liczbę kodów).

Zanim przystąpimy do tworzenia algorytmu, musimy rozwiązać kilka problemów. Pierwszym z nich jest reprezentacja zbioru Z,
który musi posiadać właściwość usuwania elementów. Użyjemy do tego celu tablicy Z oraz dodatkowej zmiennej zn, która będzie
przechowywała liczbę elementów pozostałych w tablicy. Aby system ten był efektywny, pozostałe elementy muszą być
umieszczone w kolejnych komórkach od Z[0] do Z[zn - 1].
Teraz musimy określić operację usuwania elementu z tej struktury danych. Z tablicy nie można tak po prostu usunąć komórki.
Dlatego przez usunięcie będziemy rozumieli zmniejszenie zawartości zn oraz przesunięcie o jedną pozycję wstecz zawartości
wszystkich komórek za komórką, z której usuwamy dane.
Przykład:
Z = {0,1,2,3,4,5,6,7,8,9}, zn = 10, usuwamy element 5:

Z[] przed operacją, zn = 10 0 1 2 3 4 5 6 7 8 9


Usuwanie X ← ← ← ←
Z[] po operacji, zn = 9 0 1 2 3 4 6 7 8 9 9

Widzimy, iż fizycznie tablica dalej posiada 10 elementów. Ponieważ jednak zn zostało zmniejszone, to pod uwagę
bierzemy teraz tylko 9 pierwszych elementów tablicy. Element 5 został nadpisany i nie występuje już w tym
obszarze tablicy – usunęliśmy go.

Operacja usuwania jest nam potrzebna po wylosowaniu kodu – aby nie był ponownie losowany. Ze zbioru Z musimy dodatkowo
usuwać kody, które nie zgadzają się z otrzymanym kluczem. Zastosujemy tutaj nieco inną operację usuwania ze względu na
liniową złożoność obliczeniową (zastosowanie zwykłej operacji usuwania prowadziłoby do złożoności kwadratowej). Polega ona na
tym, iż ustawiamy dwa indeksy i1 i i2 na element Z[0]. Następnie przeglądamy indeksem i1 kolejne elementy tablicy od Z[0] do
Z[zn - 1]. Jeśli element Z[i1] ma pozostać w tablicy, to kopiujemy go do Z[i2], po czym i2 zwiększamy o 1. Na końcu do zn
wprowadzamy i2. W efekcie ze zbioru zostaną usunięte pominięte przez i1 elementy.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 424 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

Przykład:
Z = {0,1,2,3,4,5,6,7,8,9}, zn = 10, usuwamy elementy 3,6,7,8:

Lp. Operacja Opis


0 1 2 3 4 5 6 7 8 9
i1 Ustawiamy oba indeksy na pierwszy element zbioru.
1. Za pomocą indeksu i1 przeglądamy kolejne elementy zbioru.
i2

0 1 2 3 4 5 6 7 8 9
2. i1 0 kopiujemy (na siebie, co nie jest szkodliwe). Oba indeksy są zwiększane o 1.
i2

0 1 2 3 4 5 6 7 8 9
3. i1 1 kopiujemy, zwiększamy o 1 oba indeksy.
i2

0 1 2 3 4 5 6 7 8 9
4. i1 2 kopiujemy, zwiększamy o 1 oba indeksy.
i2

0 1 2 3 4 5 6 7 8 9
5. i1 3 pomijamy. Indeks i2 nie jest zwiększany.
i2

0 1 2 4 4 5 6 7 8 9
6. ↑ i1 4 kopiujemy na miejsce 3, zwiększamy o 1 oba indeksy.
i2

0 1 2 4 5 5 6 7 8 9
7. ↑ i1 5 kopiujemy na miejsce 4, zwiększamy o 1 oba indeksy.
i2

0 1 2 4 5 5 6 7 8 9
8. ↑ i1 6 pomijamy
i2

0 1 2 4 5 5 6 7 8 9
9. ↑ i1 7 pomijamy
i2

0 1 2 4 5 5 6 7 8 9
10. ↑ i1 8 pomijamy
i2

0 1 2 4 5 9 6 7 8 9
11. ↑ i1 9 kopiujemy na miejsce 5, zwiększamy i2.
i2

0 1 2 4 5 9 6 7 8 9
12 ↑ i1 Koniec. W zbiorze pozostało i2 elementów, wśród których nie ma już elementów
i2 pominiętych.

Kolejnym problemem jest sposób wygenerowania 1296 łańcuchów kodowych. Zadanie można rozwiązać na wiele sposobów.
Jednym z nich jest potraktowanie każdego kodu jako 4 cyfrowej liczby szóstkowej. W systemie szóstkowym jest 6 cyfr
{0,1,2,3,4,5}. Numer kodu od 0 do 1295 traktujemy jako wartość liczby. Kolejne cyfry zapisu otrzymamy biorąc reszty z dzielenia
liczby przez 6. Za nową liczbę bierzemy wynik dzielenia. Operację tę powtarzamy 4 razy i otrzymujemy 4 kolejne cyfry, które
następnie zamieniamy na litery alfabetu od A do F.
Przykład:
Obliczymy wg tej metody wyraz o numerze 545:

545 : 6 = 90 i reszta 5 → F
90 : 6 = 15 i reszta 0 → A
15 : 6 = 2 i reszta 3 → D
2 : 6 = 0 i reszta 2 → C

Otrzymaliśmy wyraz kodowy FADC. W podobny sposób otrzymujemy pozostałe wyrazy.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 425 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

Algorytm gry MasterMind – człowiek kontra komputer


Wejście

4 znakowe kody klucza

Wyjście:
4 znakowe kody łamacza
Elementy pomocnicze:
Z – tablica kodów. Elementy są 4 znakowymi łańcuchami. Indeksy od 0 do 1295.
zn – przechowuje liczbę elementów zbioru Z. zn N
słamacz – kod łamacza wygenerowany przez komputer
sklucz – kod klucza wprowadzony przez człowieka
sklucz2 – kod klucza wygenerowany przez komputer
i1,i2 – indeksy, i1, i2 N
runda – numer rundy, runda N
losowa(x) – funkcja zwracająca liczbę pseudolosową od 0 do x - 1

Lista kroków:
K01: Utwórz kody w tablicy Z ; tablicę Z wypełniamy 1296 kodami 4
znakowymi
K02: zn ← 1296 ; początkowa liczba kodów w tablicy
K03: Dla runda = 1,2,...,6 wykonuj K04...K17 ; rozpoczynamy rozgrywkę
K04: Jeśli zn = 0, to idź do K18 ; jeśli zbiór Z jest pusty, kończymy –
błędne dane człowieka
K05: i2 ← losowa(zn) ; losujemy słówko kodowe
K06: słamacz ← Z[i2] ; umieszczamy je w łańcuchu słamacz
K07: Usuń kod Z[i2] ze zbioru Z; zn ← zn - 1 ; usuwamy wylosowane słowo i
zmniejszamy zn o 1
K08: Pisz runda, słamacz ; wyświetlamy słowo kodowe dla
człowieka
K09: Czytaj sklucz ; odczytujemy klucz
K10: Jeśli sklucz = "xxxx", to idź do K18 ; słowo odgadnięte? Jeśli tak, to
kończymy
K11: i2 ← 0 ; przeglądamy zbiór Z i usuwamy z
niego
K12: Dla i1 = 0,1,...,zn - 1 wykonuj K13...K16 ; elementy nie pasujące do
otrzymanego klucza
K13: Oblicz sklucz2 dla słamacz i Z[i1] ; algorytm obliczania klucza podaliśmy
w poprzednim rozdziale
K14: Jeśli sklucz2 ≠ sklucz, to następny obieg pętli K12 ; słowa kodowe nie pasujące do
klucza pomijamy
K15: Z[i2] ← Z[i1] ; słowa pasujące do klucza kopiujemy
pod i2
K16: i2 ← i2 + 1
K17: zn ← i2 ; korygujemy liczbę elementów zbioru
Z
K18: Jeśli sklucz = "xxxx", to zakończ
K19: Dla i1 = 0,1,...,zn-1: pisz Z[i1] ; wypisujemy pozostałe w zbiorze Z
kody
K20: Zakończ

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program prowadzi rozgrywkę MasterMind z człowiekiem. Człowiek wymyśla 4-ro literowy kod zbudowany z liter od A
do F. Następnie komputer w co najwyżej 6 rundach stara się ten kod odgadnąć. Prezentuje on człowiekowi swoje
kody wraz z liczbą pozostałych w zbiorze Z kodów. W odpowiedzi człowiek powinien wprowadzić odpowiednie kody
kluczy. Jeśli kod człowieka nie zostanie odgadnięty w ostatniej, szóstej rundzie, to komputer wypisuje wszystkie
pozostałe w zbiorze Z kody.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 426 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

Lazarus

// MasterMind - komputer <-> człowiek


// Data: 17.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
Z : array[0..1295] of string;
skoder,slamacz,sklucz,sklucz2,sl : string;
zn,i,i1,i2,j,runda : integer;
begin
randomize;
// tworzymy zbiór Z
for i := 0 to 1295 do
begin
Z[i] := ''; i2 := i;
for j := 1 to 4 do
begin
Z[i] := Z[i] + chr(65 + (i2 mod 6));
i2 := i2 div 6;
end;
end;
zn := 1296; // liczba kodów w Z
// rozgrywka
for runda := 1 to 6 do
if zn > 0 then
begin
i2 := random(zn);
slamacz := Z[i2];
// usuwamy wylosowane słowo ze zbioru Z
for i := i2 + 1 to zn - 1 do Z[i - 1] := Z[i];
dec(zn);
// wylosowane słowo prezentujemy człowiekowi
write('Runda ',runda,' : ',slamacz,zn:5,' : ');
// odczytujemy kod klucza
readln(sklucz);
// analizujemy dane
if sklucz = 'xxxx' then break;
// ze zbioru Z wyrzucamy nie pasujące kody
i2 := 0;
for i1 := 0 to zn - 1 do
begin
skoder := Z[i1];
sl := slamacz;
sklucz2 := '';
for i := 1 to 4 do
if skoder[i] = slamacz[i] then
begin
sklucz2 := sklucz2 + 'x';
skoder[i] := '#'; // wartownik w1
sl[i] := '$'; // wartownik w2
end;
for i := 1 to 4 do
if skoder[i] <> '#' then
for j := 1 to 4 do
if skoder[i] = sl[j] then
begin
sklucz2 := sklucz2 + 'o';
sl[j] := '$'; // wartownik w2
break;
end;
if sklucz = sklucz2 then
begin
Z[i2] := Z[i1];
inc(i2);
end;
end;
zn := i2;
end

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 427 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

else break;
// wyświetlamy pozostałe kody w Z
writeln;
if sklucz <> 'xxxx' then
begin
for i := 1 to zn do write(Z[i-1],' ');
writeln; writeln;
end;
end.

Code::Blocks

// MasterMind - komputer <-> człowiek


// Data: 17.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <time.h>
using namespace std;
int main()
{
string Z[1296],skoder,slamacz,sklucz,sklucz2,sl;
int zn,i,i1,i2,j,runda;
srand((unsigned)time(NULL));
// tworzymy zbiór Z
for(i = 0; i < 1296; i++)
{
Z[i] = ""; i2 = i;
for(j = 0; j < 4; j++)
{
Z[i] += char(65 + (i2 % 6));
i2 /= 6;
}
}
zn = 1296; // liczba kodów w Z
// rozgrywka
for(runda = 1; runda < 6; runda++)
if(zn > 0)
{
i2 = rand() % zn;
slamacz = Z[i2];
// usuwamy wylosowane słowo ze zbioru Z
for(i = i2 + 1; i < zn; i++) Z[i - 1] = Z[i];
zn--;
// wylosowane słowo prezentujemy człowiekowi
cout << "Runda " << runda << " : " << slamacz
<< setw(5) << zn << " : ";
// odczytujemy kod klucza
cin >> sklucz;
// analizujemy dane
if(sklucz == "xxxx") break;
// ze zbioru Z wyrzucamy nie pasujące kody
for(i2 = i1 = 0; i1 < zn; i1++)
{
skoder = Z[i1];
sl = slamacz;
sklucz2 = "";
for(i = 0; i < 4; i++)
if(skoder[i] == slamacz[i])
{
sklucz2 += 'x';
skoder[i] = '#'; // wartownik w1
sl[i] = '$'; // wartownik w2
}
for(i = 0; i < 4; i++)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 428 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

if(skoder[i] != '#')
for(j = 0; j < 4; j++)
if(skoder[i] == sl[j])
{
sklucz2 += 'o';
sl[j] = '$'; // wartownik w2
break;
}
if(sklucz == sklucz2) Z[i2++] = Z[i1];
}
zn = i2;
}
else break;
// wyświetlamy pozostałe kody w Z
cout << endl;
if(sklucz != "xxxx")
{
for(i = 1; i <= zn; i++) cout << Z[i-1] << " ";
cout << endl << endl;
}
return 0;
}

Free Basic

' MasterMind - komputer <-> człowiek


' Data: 17.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String Z(1295),skoder,slamacz,sklucz,sklucz2,sl
Dim As Integer zn,i,i1,i2,j,runda
Randomize
' tworzymy zbiór Z
For i = 0 To 1295
Z(i) = "": i2 = i
For j = 1 To 4
Z(i) += Chr(65 + (i2 Mod 6))
i2 \= 6
Next
Next
zn = 1296 ' liczba kodów w Z
' rozgrywka
For runda = 1 To 6
If zn > 0 Then
i2 = Cint(Rnd * (zn - 1))
slamacz = Z(i2)
' usuwamy wylosowane słowo ze zbioru Z
For i = i2 + 1 To zn - 1: Z(i - 1) = Z(i): Next
zn -= 1
' wylosowane słowo prezentujemy człowiekowi
Print Using "Runda # : &##### : ";runda;slamacz;zn;
' odczytujemy kod klucza
Line Input sklucz
' analizujemy dane
If sklucz = "xxxx" Then Exit For
' ze zbioru Z wyrzucamy nie pasujące kody
i2 = 0
For i1 = 0 To zn - 1
skoder = Z(i1)
sl = slamacz
sklucz2 = ""
For i = 1 To 4
If Mid(skoder,i,1) = Mid(slamacz,i,1) Then
sklucz2 += "x"
Mid(skoder,i,1) = "#" ' wartownik w1
Mid(sl,i,1) = "$" ' wartownik w2
End If
Next
For i = 1 To 4
If Mid(skoder,i,1) <> "#" Then

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 429 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

For j = 1 To 4
If Mid(skoder,i,1) = Mid(sl,j,1) Then
sklucz2 += "o"
Mid(sl,j,1) = "$" ' wartownik w2
Exit For
End If
Next
End If
Next
If sklucz = sklucz2 Then
Z(i2) = Z(i1)
i2 += 1
End If
zn = i2
Next
Else
Exit For
End If
Next
' wyświetlamy pozostałe kody w Z
Print
If sklucz <> "xxxx" Then
For i = 1 To zn: Print Z(i-1);" ";: Next
Print: Print
End If
End

Wynik
Runda 1 : EBAC 1295 : oo
Runda 2 : ADCF 311 : x
Runda 3 : AAEA 21 : xx
Runda 4 : AABB 1 : xxxx

MasterMind
(C)2012 mgr Jerzy Wałaszek

Nowa gra

Gotowe

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 430 / 519


Algorytmy i Struktury Danych - MasterMind człowiek kontra komputer 2014-10-03

w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0062.php 431 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szyfr Cezara
Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Liniowe generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Problem
Opracować algorytm szyfrujący i deszyfrujący dla szyfru Cezara

Szyfrowanie tekstów (ang. text encryption) ma na celu ukrycie ważnych informacji przed dostępem do nich osób niepowołanych.
Historia kodów szyfrujących sięga czasów starożytnych. Już tysiące lat temu egipscy kapłani stosowali specjalny system
hieroglifów do szyfrowania różnych tajnych wiadomości. Szyfr Cezara (ang. Ceasar's Code lub Ceasar's Cipher) jest bardzo
prostym szyfrem podstawieniowym (ang. substitution cipher). Szyfry podstawieniowe polegają na zastępowaniu znaków tekstu
jawnego (ang. plaintext) innymi znakami przez co zawarta w tekście informacja staje się nieczytelna dla osób
niewtajemniczonych. Współcześnie szyfrowanie stanowi jedną z najważniejszych dziedzin informatyki – to dzięki niej stał się
możliwy handel w Internecie, funkcjonują banki ze zdalnym dostępem do kont, powstał podpis elektroniczny oraz bezpieczne łącza
transmisji danych. Przykładów zastosowania jest bez liku i dokładne omówienie tej dziedziny wiedzy leży daleko poza
możliwościami tego artykułu.
Szyfr Cezara został nazwany na cześć rzymskiego imperatora Juliusza Cezara, który stosował ten sposób szyfrowania do
przekazywania informacji o znaczeniu wojskowym. Szyfr polega na zastępowaniu liter alfabetu A...Z literami leżącymi o trzy
pozycje dalej w alfabecie:

Tekst jawny A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Szyfr Cezara D E F G H I J K L M N O P Q R S T U V W X Y Z A B C

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 432 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

Ostatnie trzy znaki X, Y i Z nie posiadają następników w alfabecie przesuniętych o trzy pozycje. Dlatego umawiamy się, iż alfabet
"zawija się" i za literką Z następuje znów litera A. Teraz bez problemu znajdziemy następniki X → A, Y → B i Z → C.
Przykład:
Zaszyfrować zdanie: NIEPRZYJACIEL JEST BARDZO BLISKO.
Poszczególne literki tekstu jawnego zastępujemy literkami szyfru Cezara zgodnie z powyższą tabelką kodu. Spacje
oraz inne znaki nie będące literami pozostawiamy bez zmian:

NIEPRZYJACIEL JEST BARDZO BLISKO


QLHSUCBMDFLHO MHVW EDUGCR EOLVNR
Deszyfrowanie tekstu zaszyfrowanego kodem Cezara polega na wykonywaniu operacji odwrotnych. Każdą literę kodu zamieniamy
na literę leżącą o trzy pozycje wcześniej w alfabecie.

Szyfr Cezara A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Tekst jawny X Y Z A B C D E F G H I J K L M N O P Q R S T U V W

Podobnie jak poprzednio trzy pierwsze znaki szyfru Cezara nie posiadają bezpośrednich odpowiedników liter leżących o trzy
pozycje wcześniej, ponieważ alfabet rozpoczyna się dopiera od pozycji literki D. Rozwiązaniem jest ponowne "zawinięcie" alfabetu
tak, aby przed literą A znalazły się trzy ostatnie literki X, Y i Z.

Do wyznaczania kodu literek przy szyfrowaniu i deszyfrowaniu posłużymy się operacjami modulo. Operacja modulo jest resztą z
dzielenia danej liczby przez moduł. Wynik jest zawsze mniejszy od modułu. U nas moduł będzie równy 26, ponieważ tyle mamy
liter alfabetu.

Jeśli c jest kodem ASCII dużej litery alfabetu (rozważamy tylko teksty zbudowane z dużych liter), to szyfrowanie
kodem Cezara polega na wykonaniu następującej operacji arytmetycznej:

c = 65 + (c - 62) mod 26

Deszyfrowanie polega na zamianie kodu wg wzoru:

c = 65 + (c - 42) mod 26

Algorytm szyfrowania tekstu kodem Cezara


Wejście
Łańcuch tekstowy s
Wyjście:
Łańcuch tekstowy s zaszyfrowany kodem Cezara
Elementy pomocnicze:
i – indeks, i N
kod(x) – zwraca kod litery x
znak(x) – zamienia kod x na odpowiadający mu znak ASCII
Lista kroków:
K01: Dla i = 0,1,...,|s| - 1 wykonuj K02...K03 ; przeglądamy kolejne znaki tekstu
K02: Jeśli s[i] < "A" s[i] > "Z", to następny obieg pętli K01 ; pomijamy znaki nie będące
literami A...Z
K03: s[i] ← znak(65 + (kod(s[i]) - 62) mod 26) ; szyfrujemy szyfrem Cezara
K04: Pisz s
K05: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 433 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

Program odczytuje wiersz znaków. Zamienia litery małe na duże i szyfruje kodem Cezara wyświetlając wynik.

Lazarus

// Szyfrowanie Kodem Cezara


// Data: 18.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : string;
i : integer;
begin
// odczytujemy wiersz znaków
readln(s);
// zamieniamy małe litery na duże
s := upcase(s);
// tekst kodujemy szyfrem Cezara
for i := 1 to length(s) do
if s[i] in ['A'..'Z'] then s[i] := chr(65 + (ord(s[i]) - 62) mod 26);
// wypisujemy zaszyfrowany tekst
writeln(s);
writeln;
end.

Code::Blocks

// Szyfrowanie Kodem Cezara


// Data: 18.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i;
// odczytujemy wiersz znaków
getline(cin,s);
// zamieniamy małe litery na duże
// i kodujemy szyfrem cezara
for(i = 0; i < s.length(); i++)
{
s[i] = toupper(s[i]);
if((s[i] >= 'A') && (s[i] <= 'Z')) s[i] = char(65 + (s[i] - 62) % 26);
}
// wypisujemy zaszyfrowany tekst
cout << s << endl << endl;
return 0;
}

Free Basic

' Szyfrowanie Kodem Cezara


' Data: 18.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Integer i

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 434 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

' odczytujemy wiersz znaków


Line Input s
' zamieniamy małe litery na duże
s = Ucase(s)
' tekst kodujemy szyfrem Cezara
For i = 1 To Len(s)
If Mid(s,i,1) >= "A" And Mid(s,i,1) <= "Z" Then Mid(s,i,1) = Chr(65 + (Asc(Mid(s,i,1)) - 62) Mod 26)
Next
' wypisujemy zaszyfrowany tekst
Print s
Print
End

Wynik
nieprzyjaciel jest bardzo blisko
QLHSUCBMDFLHO MHVW EDUGCR EOLVNR

Szyfrowanie Szyfrem Cezara


(C)2012 mgr Jerzy Wałaszek

Nieprzyjaciel jest bardzo blisko Szyfruj


.

Algorytm deszyfrowania tekstu zaszyfrowanego kodem Cezara


Wejście
Łańcuch tekstowy s zaszyfrowany kodem Cezara
Wyjście:
Tekst jawny
Elementy pomocnicze:
i – indeks, i N
kod(x) – zwraca kod litery x
znak(x) – zamienia kod x na odpowiadający mu znak ASCII

Lista kroków:
K01: Dla i = 0,1,...,|s| - 1 wykonuj K02...K03 ; przetwarzamy kolejne znaki tekstu
K02: Jeśli s[i] < "A" s[i] > "Z", to następny obieg pętli K01 ; pomijamy znaki nie będące literami A...Z
K03: s[i] ← znak(65 + (kod(s[i] - 42) mod 26) ; deszyfrujemy
K04: Pisz s
K05: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje wiersz znaków zaszyfrowanych szyfrem Cezara, deszyfruje je i wypisuje tekst jawny.

Lazarus

// Deszyfrowanie Kodu Cezara


// Data: 18.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 435 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

var
s : string;
i : integer;
begin
// odczytujemy wiersz znaków
readln(s);
// zamieniamy małe litery na duże
s := upcase(s);
// rozszyfrowujemy
for i := 1 to length(s) do
if s[i] in ['A'..'Z'] then s[i] := chr(65 + (ord(s[i]) - 42) mod 26);
// wypisujemy rozszyfrowany tekst
writeln(s);
writeln;
end.

Code::Blocks

// Deszyfrowanie Kodu Cezara


// Data: 18.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i;
// odczytujemy wiersz znaków
getline(cin,s);
// zamieniamy małe litery na duże
// i rozszyfrowujemy
for(i = 0; i < s.length(); i++)
{
s[i] = toupper(s[i]);
if((s[i] >= 'A') && (s[i] <= 'Z')) s[i] = char(65 + (s[i] - 42) % 26);
}
// wypisujemy rozszyfrowany tekst
cout << s << endl << endl;
return 0;
}

Free Basic

' Deszyfrowanie Kodu Cezara


' Data: 18.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Integer i
' odczytujemy wiersz znaków
Line Input s
' zamieniamy małe litery na duże
s = Ucase(s)
' rozszyfrowujemy
For i = 1 To Len(s)
If Mid(s,i,1) >= "A" And Mid(s,i,1) <= "Z" Then Mid(s,i,1) = Chr(65 + (Asc(Mid(s,i,1)) - 42) Mod 26)
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 436 / 519


Algorytmy i Struktury Danych - Szyfr Cezara 2014-10-03

' wypisujemy rozszyfrowany tekst


Print s
Print
End

Wynik
QLHSUCBMDFLHO MHVW EDUGCR EOLVNR
NIEPRZYJACIEL JEST BARDZO BLISKO

Szyfrowanie Szyfrem Cezara


(C)2012 mgr Jerzy Wałaszek

QLHSUCBMDFLHO MHVW EDUGCR EOLVNR Deszyfruj


.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0063.php 437 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szyfrowanie z pseudolosowym odstępem


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Projekt generatora LCG
Podstawowe pojęcia dotyczące przetwarzania tekstów Szyfrowanie
Podstawowe operacje na łańcuchach znakowych Deszyfrowanie
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Liniowe generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Problem
Opracować algorytm szyfrujący i deszyfrujący z pseudolosowym odstępem

Szyfr Cezara posiada kilka wad, które uniemożliwiają praktyczne zastosowanie. Po pierwsze wystarczy odkryć wartość
przesunięcia kodów (oryginalnie wynosi ono 3, lecz można je zmieniać w zakresie od 1 do 25), aby rozszyfrować całą wiadomość.
W tym celu należy przeprowadzić 25 prób, co dla współczesnych komputerów nie stanowi żadnego problemu.

Przesunięcie kodów Szyfr


0 ALA MA KOTA
+ 1 BMB NB LPUB
+ 2 CNC OC MQVC
+ 3 DOD PD NRWD
+ 4 EPE QE OSXE
+ 5 FQF RF PTYF

Po drugie, z uwagi na stałe przesunięcie kodów, te same znaki są zawsze szyfrowane tak samo. Jeśli na przykład zaszyfrujemy
AAAAAAAAAA, to otrzymamy ciąg DDDDDDDDDD.
Aby znacząco utrudnić rozszyfrowanie wiadomości zaprojektujemy szyfr, w którym przesunięcie kodów będzie się zmieniało w
zakresie od 0 do 25 w trakcie szyfrowania znaków. Do tego celu potrzebna nam jest możliwość generacji tych samych ciągów

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 438 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

liczbowych przy szyfrowaniu oraz rozszyfrowywaniu. Wykorzystamy generator pseudolosowy LCG, który startując od określonego
ziarna tworzy zawsze te same ciągi liczb pseudolosowych. Kluczem szyfrującym będą parametry generatora oraz wartość ziarna
pseudolosowego. Parametry generatora: moduł m, mnożnik a oraz przyrost c zostaną na stałe zdefiniowane w programie (możesz
je zmienić, wtedy zmianie ulegnie sposób szyfrowania). Użytkownik będzie jedynie podawał wartość ziarna pseudolosowego Xo.
Sposób projektowania generatora LCG opisaliśmy w rozdziale o liczbach pseudolosowych.

Projekt generatora LCG


Im większy zakres generowanych liczb pseudolosowych, tym dłuższy okres powtarzania tworzonego ciągu. W efekcie trudniejsze
jest złamanie szyfru. Kryptologia potrafi bez większych problemów łamać szyfry oparte na prostych generatorach pseudolosowych
z uwagi na ich właściwości, dlatego nie są one stosowane w praktycznych systemach szyfrowania – użyty generator musi być
kryptologicznie bezpieczny. My się tym nie będziemy przejmowali, ponieważ nasz projekt jest czysto dydaktyczny.

Wybieramy zakres od 0 do Xmax = 3956279999.


Moduł m jest równy Xmax + 1 = 3956280000
Czynniki pierwsze modułu m = 3956280000 = 2 × 2 × 2 × 2 × 2 × 2 × 3 × 5 × 5 × 5 × 5 × 32969
Przyrost c = 7 × 11 × 17 = 1309
Mnożnik a - 1 = 2 × 2 × 3 × 5 × 32969 = 1978140, stąd a = 1978141.
Podsumowując otrzymujemy następujący generator LCG:
LCG(3956280000,1978141,1309,Xo)
Ziarno Xo jest w zakresie od 0 do Xmax, czyli od 0 do 3956279999. Liczba ta będzie naszym kluczem szyfrującym.

Szyfrowanie
Zasada szyfrowania jest następująca:

Odczytujemy klucz i wprowadzamy go do ziarna pseudolosowego naszego generatora LCG. Na podstawie tego
klucza generator LCG będzie tworzył ściśle określony ciąg liczb pseudolosowych. Odczytujemy następnie łańcuch
s, który ma zostać zaszyfrowany. Zamieniamy wszystkie litery z małych na duże (nasz algorytm szyfruje tylko duże
znaki). Przetwarzamy kolejne znaki tego łańcucha. Dla każdego znaku generujemy liczbę pseudolosową X. Teraz
sprawdzamy, czy przetwarzany znak łańcucha s jest dużą literą od A do Z. Jeśli tak, to obliczamy jego kod ch i
przekształcamy go zgodnie ze wzorem:

ch = 65 + (ch - 65 + X mod 26) mod 26

Wynik jest kodem zaszyfrowanego znaku – umieszczamy go w łańcuchu s na miejscu przetwarzanego znaku i
kontynuujemy te same operacje aż do przetworzenia wszystkich znaków w s. Na końcu łańcuch s zwracamy jako
wynik szyfrowania.

Algorytm szyfrowania z pseudolosowym odstępem


Wejście
X – klucz, X N
m,a, c – parametry generatora LCG, m,a,c N
s – łańcuch tekstowy
Wyjście:
Zaszyfrowany łańcuch s
Elementy pomocnicze:
i – indeks, i N
kod(x) – zwraca kod litery x
znak(x) – zamienia kod x na odpowiadający mu znak ASCII
Lista kroków:
K01: Dla i = 0,1,...,|s| - 1, wykonuj K02...K04 ; przetwarzamy kolejne znaki
łańcucha s
K02: X ← (a × X + c) mod m ; obliczamy nową liczbę
pseudolosową
K03: Jeśli s[i] < 'A' s[i] > 'Z', to następny obieg pętli K01 ; pomijamy znaki nie będące literami
od A do Z
K04: s[i] = znak(65 + (kod(s[i]) - 65 + X mod 26) mod 26) ; szyfrujemy
K05: Pisz s ; wyprowadzamy szyfr
K06: Zakończ ; gotowe

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 439 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje kolejno klucz X oraz łańcuch s, który szyfruje kodem o pseudolosowym odstępie i wypisuje
wynik. Parametry generatora LCG są zdefiniowane wewnątrz programu. Zakres kluczy jest od 0 do 3956279999.

Lazarus

// Szyfrowanie z pseudolosowym odstępem


// Data: 20.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : string;
i : integer;
X,a,m,c : qword;
begin
// definiujemy generator LCG
m := 3956280000;
a := 1978141;
c := 1309;
// odczytujemy klucz i wiersz znaków
readln(X); readln(s);
// zamieniamy małe litery na duże
s := upcase(s);
// szyfrujemy
for i := 1 to length(s) do
begin
// obliczamy kolejną liczbę pseudolosową
X := (a * X + c) mod m;
// szyfrujemy literkę
if s[i] in ['A'..'Z'] then s[i] := chr(65 + (ord(s[i]) - 65 + X mod 26) mod 26);
end;
// wypisujemy zaszyfrowany tekst
writeln(s);
writeln;
end.

Code::Blocks

// Szyfrowanie z pseudolosowym odstępem


// Data: 20.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i;
unsigned long long X,a,m,c;
// definiujemy generator LCG
m = 3956280000ull;
a = 1978141ull;
c = 1309ull;
// odczytujemy klucz i wiersz znaków
cin >> X; cin.ignore(256,'\n');

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 440 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

getline(cin,s);
// szyfrujemy
for(i = 0; i < s.length(); i++)
{
// obliczamy kolejną liczbę pseudolosową
X = (a * X + c) % m;
// szyfrujemy literkę
s[i] = toupper(s[i]);
if((s[i] >= 'A') && (s[i] <= 'Z')) s[i] = 65 + (s[i] - 65 + X % 26) % 26;
}
// wypisujemy zaszyfrowany tekst
cout << s << endl << endl;
return 0;
}

Free Basic

' Szyfrowanie z pseudolosowym odstępem


' Data: 20.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Integer i
Dim As Ulongint X,a,m,c
' definiujemy generator LCG
m = 3956280000
a = 1978141
c = 1309
' odczytujemy klucz i wiersz znaków
Input X
Line Input s
' zamieniamy małe litery na duże
s = Ucase(s)
' szyfrujemy
For i = 1 To Len(s)
' obliczamy kolejną liczbę pseudolosową
X = (a * X + c) Mod m
' szyfrujemy literkę
If Mid(s,i,1) >= "A" And Mid(s,i,1) <= "Z" Then Mid(s,i,1) = Chr(65 + (Asc(Mid(s,i,1)) - 65 + X Mod 26) Mod 26)
Next
' wypisujemy zaszyfrowany tekst
Print s
Print
End

Wynik
1001
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA
WRELMD MUVJQVHPJOXUE ESVRDSNBN V SSXQ SNIRED
1002
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA
FKHGVU HHGMNMAQCVYHP PRWKILEOI U BDOP VSDUPE
1003
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA
ODKRUL MURPUNDRFSJUK AGHDNOLRN J UOFE YHOXAV

Zwróć uwagę, iż dla różnych kluczy otrzymujemy zupełnie inne szyfry. Co więcej powtarzające się literki (tutaj A przed i za
tekstem) zostają zaszyfrowane w różne znaki – to zaleta szyfru z pseudolosowym odstępem. Aby ukryć odstępy między
wyrazami, które pozwalają zidentyfikować słowa, można wpisywać w ich miejsce wybraną literkę (np.X – tak postępowali
operatorzy niemieckich maszyn Enigma w czasie II Wojny Światowej). Wtedy szyfr stanie się jednolitym blokiem liter.

Szyfrowanie kodem o pseudolosowym odstępie


(C)2012 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 441 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

Klucz 0 ... 3956279999 : 1001


AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA

Szyfruj
.

Deszyfrowanie
Zasada rozszyfrowywania jest prawie identyczna jak przy szyfrowaniu. Jedyna różnica leży we wzorze obliczania kodu znaku z
kodu szyfru:

ch = 65 + (ch - 39 - X mod 26) mod 26

Algorytm deszyfrowania z pseudolosowym odstępem


Wejście
X – klucz, X N
m, a, c – parametry generatora LCG, m,a,c N
s – zaszyfrowany łańcuch tekstowy
Wyjście:
Rozszyfrowany łańcuch s
Elementy pomocnicze:
i – indeks, i N
kod(x) – zwraca kod litery x
znak(x) – zamienia kod x na odpowiadający mu znak ASCII

Lista kroków:
K01: Dla i = 0,1,...,|s| - 1, wykonuj K02...K04 ; przetwarzamy kolejne znaki
łańcucha s
K02: X ← (a × X + c) mod m ; obliczamy nową liczbę
pseudolosową
K03: Jeśli s[i] < 'A' s[i] > 'Z', to następny obieg pętli K01 ; pomijamy znaki nie będące literami
od A do Z
K04: s[i] = znak(65 + (kod(s[i]) - 39 - X mod 26) mod 26) ; rozszyfrowujemy
K05: Pisz s ; wyprowadzamy tekst
K06: Zakończ ; gotowe

Program

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje kolejno klucz X oraz łańcuch s, który rozszyfrowuje kodem o pseudolosowym odstępie i wypisuje
wynik. Parametry generatora LCG są zdefiniowane wewnątrz programu. Zakres kluczy jest od 0 do 3956279999.

Lazarus

// Deszyfrowanie z pseudolosowym odstępem


// Data: 20.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
var
s : string;
i : integer;
X,a,m,c : qword;
begin
// definiujemy generator LCG
m := 3956280000;
a := 1978141;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 442 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

c := 1309;
// odczytujemy klucz i szyfr
readln(X); readln(s);
// zamieniamy małe litery na duże
s := upcase(s);
// deszyfrujemy
for i := 1 to length(s) do
begin
// obliczamy kolejną liczbę pseudolosową
X := (a * X + c) mod m;
// deszyfrujemy literkę
if s[i] in ['A'..'Z'] then s[i] := chr(65 + (ord(s[i]) - 39 - X mod 26) mod 26);
end;
// wypisujemy rozszyfrowany tekst
writeln(s);
writeln;
end.

Code::Blocks

// Deszyfrowanie z pseudolosowym odstępem


// Data: 20.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
int i;
unsigned long long X,a,m,c;
// definiujemy generator LCG
m = 3956280000ull;
a = 1978141ull;
c = 1309ull;
// odczytujemy klucz i szyfr
cin >> X; cin.ignore(256,'\n');
getline(cin,s);
// deszyfrujemy
for(i = 0; i < s.length(); i++)
{
// obliczamy kolejną liczbę pseudolosową
X = (a * X + c) % m;
// deszyfrujemy literkę
s[i] = toupper(s[i]);
if((s[i] >= 'A') && (s[i] <= 'Z')) s[i] = 65 + (s[i] - 39 - X % 26) % 26;
}
// wypisujemy rozszyfrowany tekst
cout << s << endl << endl;
return 0;
}

Free Basic

' Deszyfrowanie z pseudolosowym odstępem


' Data: 20.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s
Dim As Integer i
Dim As Ulongint X,a,m,c

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 443 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

' definiujemy generator LCG


m = 3956280000
a = 1978141
c = 1309
' odczytujemy klucz i szyfr
Input X
Line Input s
' zamieniamy małe litery na duże
s = Ucase(s)
' deszyfrujemy
For i = 1 To Len(s)
' obliczamy kolejną liczbę pseudolosową
X = (a * X + c) Mod m
' deszyfrujemy literkę
If Mid(s,i,1) >= "A" And Mid(s,i,1) <= "Z" Then Mid(s,i,1) = Chr(65 + (Asc(Mid(s,i,1)) - 39 - X Mod 26) Mod 26)
Next
' wypisujemy rozszyfrowany tekst
Print s
Print
End

Wynik
1001
WRELMD MUVJQVHPJOXUE ESVRDSNBN V SSXQ SNIRED
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA
1002
FKHGVU HHGMNMAQCVYHP PRWKILEOI U BDOP VSDUPE
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA
1003
ODKRUL MURPUNDRFSJUK AGHDNOLRN J UOFE YHOXAV
AAAAAA NIEPRZYJACIEL ZAATAKUJE W NOCY AAAAAA

Deszyfrowanie kodu o pseudolosowym odstępie


(C)2012 mgr Jerzy Wałaszek

Klucz 0 ... 3956279999 : 1001


WRELMD MUVJQVHPJOXUE ESVRDSNBN V SSXQ SNIRED

Rozszyfruj
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 444 / 519


Algorytmy i Struktury Danych - Szyfrowanie z pseudolosowym odstępem 2014-10-03

rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0064.php 445 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szyfry przestawieniowe
Tematy pokrewne Podrozdziały
Łańcuchy znakowe Przestawianie dwóch sąsiednich liter
Podstawowe pojęcia dotyczące przetwarzania tekstów Szyfr podstawieniowy z tablicą
Podstawowe operacje na łańcuchach znakowych Szyfr podstawieniowy z pseudolosowym mieszaniem
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Liniowe generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Problem
Opracować algorytm szyfrujący i deszyfrujący za pomocą szyfru przestawieniowego

Szyfr przestawieniowy (ang. transposition cifer) polega na zamianie położenia znaków tworzących tekst, przez co wiadomość
staje się nieczytelna dla niewtajemniczonego odbiorcy. W zaszyfrowanym tekście znajdują się wszystkie znaki tekstu jawnego.
Zaczniemy od najprostszych szyfrów przestawieniowych.

Przestawianie dwóch sąsiednich liter


W tym rodzaju szyfru tekst dzielimy na pary znaków. Następnie w każdej parze zamieniamy ze sobą litery,
Przykład:

tekst = ALA MA KOCURKA BURKA I PIESKA BIESKA


pary = AL A MA K OC UR KA B UR KA I P IE SK A BI ES KA
zamiana = LA A AM K CO RU AK B RU AK I P EI KS A IB SE AK
szyfr = LA AAMK CORUAKB RUAKI P EIKS AIBSEAK

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 446 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

Tekst da się podzielić na pary, jeśli zawiera parzystą liczbę znaków. W przeciwnym razie ostatnia para jest niepełna. W takiej
niepełnej parze liter oczywiście nie zamieniamy miejscami. Zwróć uwagę, iż ten szyfr jest symetryczny. Jeśli poddamy
szyfrowaniu tekst poprzednio zaszyfrowany, to otrzymamy z powrotem tekst jawny.

Algorytm szyfrowania przestawieniowego ze zamianą liter w parach


Wejście
Łańcuch tekstowy s, który zawiera tekst poddawany szyfrowaniu lub deszyfrowaniu
Wyjście:
Zaszyfrowany łańcuch s
Elementy pomocnicze:
i – indeks, i N

Lista kroków:
K01: i ← 0 ; rozpoczynamy od pierwszego znaku
K02: Dopóki i + 1 < | s | wykonuj K03...K04
K03: s[i] ↔ s[i + 1] ; zamieniamy znaki w parze
K04: i ← i + 2 ; przesuwamy się do następnej pary znaków
K05: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program wczytuje wiersz tekstu, szyfruje go przez zamianę liter w parach i wyświetla wynik.

Lazarus

// Szyfr przestawieniowy
// Data: 11.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program cifer;
var
s : string;
x : char;
i : integer;
begin
// odczytujemy tekst
readln(s);
// zamieniamy miejscami litery
i := 1;
while i < length(s) do
begin
x := s[i];
s[i] := s[i+1];
s[i+1] := x;
inc(i,2);
end;
// wyświetlamy wynik
writeln(s);
end.

Code::Blocks

// Szyfr przestawieniowy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 447 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

// Data: 11.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
unsigned i;
// odczytujemy tekst
getline(cin,s);
// zamieniamy miejscami litery
for (i = 0; i < s.length()-1; i += 2)
swap(s[i],s[i+1]);
// wyświetlamy wynik
cout << s << endl;
return 0;
}

Free Basic

' Szyfr przestawieniowy


' Data: 11.02.2011
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,x
Dim As Integer i
' odczytujemy tekst
Line Input s
' zamieniamy miejscami litery
For i = 1 To Len(s)-1 Step 2
x = Mid(s,i,1)
Mid(s,i,1) = Mid(s,i+1,1)
Mid(s,i+1,1) = x
Next
' wyświetlamy wynik
Print s
End

Wynik
ALA MA KOCURKA BURKA I PIESKA BIESKA
LA AAMK CORUAKB RUAKI P EIKS AIBSEAK

Szyfrowanie kodem przestawieniowym


(C)2012 mgr Jerzy Wałaszek

ALA MA KOCURKA BURKA I PIESKA BIESKA

Szyfruj
.

Szyfr przestawieniowy z tablicą


W tym przypadku dla danego łańcucha s wyznaczamy taką liczbę naturalną n, aby:

n2 ≤ |s|

Tekst dzielimy na grupy n znakowe. Ostatnia grupa może być niepełna – dopełniamy ją wybranymi znakami – np. znakami kropki
(lub znakami liter A...Z wybieranymi pseudolosowo). Z grup powstaje tablica. Na wyjście przesyłamy kolejne kolumny z tej tablicy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 448 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

(lub znakami liter A...Z wybieranymi pseudolosowo). Z grup powstaje tablica. Na wyjście przesyłamy kolejne kolumny z tej tablicy
(w odmianach tego szyfru kolumny mogą być permutowane).

tekst = KUBUŚ PUCHATEK NIE MIAŁ DZIŚ MIODKU – 35 znaków, n = 6


grupy = KUBUŚ PUCHAT EK NIE MIAŁ DZIŚ M IODKU#
tablica = KUBUŚ
PUCHAT
EK NIE
MIAŁ
DZIŚ M
IODKU.
kolumny = KPE DI UUKMZO BC IID UHNAŚK ŚAIŁ U TE M.
szyfr = KPE DIUUKMZOBC IIDUHNAŚKŚAIŁ U TE M.

W rzeczywistości tablicy nie musimy wcale tworzyć. Kolejne kolumny uzyskamy odczytując znaki z odstępem n. Na przykład
znaki w pierwszej kolumnie leżą na pozycjach 1 + 6(i - 1), w drugiej kolumnie na pozycjach 2 + 6(i - 1), itd.
Deszyfrowanie polega na ponownym wykonaniu tego samego algorytmu nad szyfrem – zatem szyfr jest symetryczny.

Algorytm szyfrowania przestawieniowego za pomocą tablicy


Wejście
Łańcuch tekstowy s, który zawiera tekst jawny
Wyjście:
Zaszyfrowany łańcuch t,
Elementy pomocnicze:
n – liczba wierszy/kolumn w tablicy, n N
i,j – indeksy, i,j N

Lista kroków:
K01: n ← 1 ; wyznaczamy n
K02: Dopóki n2 < |s| wykonuj n ← n + 1
K03: Dopóki |s| < n2 wykonuj s ← s + "." ; dopasowujemy długość s
K04: t ←"" ; zerujemy szyfr
K05: Dla j = 1,2,...,n wykonuj K06 ; szyfrujemy
K06: Dla i = 0,1,...,n-1 wykonuj t ← t + s[j + n × i] ; do szyfru dołącz znak z tekstu
K07: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje liczbę, która decyduje o tym, czy wczytany tekst będzie szyfrowany, czy rozszyfrowywany.
Umawiamy się, iż liczba 0 oznacza szyfrowanie, liczba różna od zera oznacza rozszyfrowywanie. Następnie program
odczytuje łańcuch znaków, które szyfruje lub rozszyfrowuje w zależności od wprowadzonej na początku liczby.

Lazarus

// Szyfr przestawieniowy
// Data: 12.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program cifer;
var
s,t : string;
n,i,j : integer;
begin
// odczytujemy tekst/szyfr

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 449 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

readln(s);
// dopasowujemy n
n := 1;
while n * n < length(s) do inc(n);
// dopasowujemy s
while length(s) < n * n do s := s + '.';
// szyfrujemy/deszyfrujemy
t := '';
for j := 1 to n do
for i := 0 to n - 1 do t := t + s[j + n * i];
// wypisujemy wynik
writeln(t);
end.

Code::Blocks

// Szyfr przestawieniowy
// Data: 12.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s,t;
unsigned n,i,j;
// odczytujemy tekst/szyfr
getline(cin,s);
// dopasowujemy n
for(n = 1; n * n < s.length(); n++);
// dopasowujemy s
while(s.length() < n * n) s += ".";
// szyfrujemy/deszyfrujemy
t = "";
for(j = 0; j < n; j++)
for(i = 0; i < n; i++) t += s[j + n * i];
// wypisujemy wynik
cout << t << endl;
return 0;
}

Free Basic

' Szyfr przestawieniowy


' Data: 12.02.2011
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,t
Dim As Integer n,i,j
' odczytujemy tekst/szyfr
Line Input s
' dopasowujemy n
n = 1
While n * n < Len(s)
n += 1

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 450 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

Wend
' dopasowujemy s
While Len(s) < n * n
s += "."
Wend
' szyfrujemy/deszyfrujemy
t = ""
For j = 1 To n
For i = 0 To n - 1
t += Mid(s,j + n * i,1)
Next
Next
' wypisujemy wynik
Print t
End

Wynik
KUBUŚ PUCHATEK NIE MIAŁ DZIŚ MIODKU
KPE DIUUKMZOBC IIDUHNAŚKŚAIŁ U TE M.
KPE DIUUKMZOBC IIDUHNAŚKŚAIŁ U TE M.
KUBUŚ PUCHATEK NIE MIAŁ DZIŚ MIODKU.

Szyfrowanie kodem przestawieniowym z wykorzystaniem tablicy


(C)2012 mgr Jerzy Wałaszek

KUBUŚ PUCHATEK NIE MIAŁ DZIŚ MIODKU

Szyfruj/Deszyfruj
.

Szyfr przestawieniowy z pseudolosowym mieszaniem


Tworzymy własny generator pseudolosowy, który posłuży nam do generowania pozycji znaków. Kluczem będzie ziarno tego
generatora. Następnie dla kolejnych znaków szyfrowanego tekstu wyznaczamy liczbę pseudolosową, która będzie nową pozycją
tego znaku w tekście. Pozycja ta musi być w zakresie od 1 do |s|. Po wyznaczeniu nowej pozycji zamieniamy ze sobą znak
bieżący ze znakiem na pseudolosowej pozycji.
Rozszyfrowywanie wymaga wykonania operacji w kolejności odwrotnej do szyfrowania. Zatem po wczytaniu szyfru, wyznaczamy w
osobnej tablicy pseudolosowe pozycje znaków identycznie jak przy szyfrowaniu. Następnie pozycje te odczytujemy od końca i
wymieniamy znaki od tyłu szyfru cofając się do początku. W ten sposób usuniemy wymiany znaków wprowadzone w trakcie
szyfrowania.
Na potrzeby naszego artykułu wybierzmy generator pseudolosowy o następujących parametrach (w rzeczywistych
zastosowaniach moduł powinien być bardzo duży – szczególnie, gdy szyfrowane są długie łańcuchy znakowe):

m = 984 = 2 × 2 × 2 × 3 × 41
a = 493 = 2 × 2 × 3 × 41 + 1
c = 385 = 5 × 7 × 11
X0 = 0...983

Generator ten generuje następujące liczby dla X0 = 0:

385 278 663 556 941 834 235 128 513 406 791 684 85 962 363 256 641 534 919 812 213 106 491 384 769 662 63
940 341 234 619 512 897 790 191 84 469 362 747 640 41 918 319 212 597 490 875 768 169 62 447 340 725 618 19
896 297 190 575 468 853 746 147 40 425 318 703 596 981 874 275 168 553 446 831 724 125 18 403 296 681 574
959 852 253 146 531 424 809 702 103 980 381 274 659 552 937 830 231 124 509 402 787 680 81 958 359 252 637
530 915 808 209 102 487 380 765 658 59 936 337 230 615 508 893 786 187 80 465 358 743 636 37 914 315 208
593 486 871 764 165 58 443 336 721 614 15 892 293 186 571 464 849 742 143 36 421 314 699 592 977 870 271
164 549 442 827 720 121 14 399 292 677 570 955 848 249 142 527 420 805 698 99 976 377 270 655 548 933 826
227 120 505 398 783 676 77 954 355 248 633 526 911 804 205 98 483 376 761 654 55 932 333 226 611 504 889
782 183 76 461 354 739 632 33 910 311 204 589 482 867 760 161 54 439 332 717 610 11 888 289 182 567 460 845
738 139 32 417 310 695 588 973 866 267 160 545 438 823 716 117 10 395 288 673 566 951 844 245 138 523 416
801 694 95 972 373 266 651 544 929 822 223 116 501 394 779 672 73 950 351 244 629 522 907 800 201 94 479
372 757 650 51 928 329 222 607 500 885 778 179 72 457 350 735 628 29 906 307 200 585 478 863 756 157 50 435
328 713 606 7 884 285 178 563 456 841 734 135 28 413 306 691 584 969 862 263 156 541 434 819 712 113 6 391
284 669 562 947 840 241 134 519 412 797 690 91 968 369 262 647 540 925 818 219 112 497 390 775 668 69 946
347 240 625 518 903 796 197 90 475 368 753 646 47 924 325 218 603 496 881 774 175 68 453 346 731 624 25 902

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 451 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

303 196 581 474 859 752 153 46 431 324 709 602 3 880 281 174 559 452 837 730 131 24 409 302 687 580 965 858
259 152 537 430 815 708 109 2 387 280 665 558 943 836 237 130 515 408 793 686 87 964 365 258 643 536 921
814 215 108 493 386 771 664 65 942 343 236 621 514 899 792 193 86 471 364 749 642 43 920 321 214 599 492
877 770 171 64 449 342 727 620 21 898 299 192 577 470 855 748 149 42 427 320 705 598 983 876 277 170 555
448 833 726 127 20 405 298 683 576 961 854 255 148 533 426 811 704 105 982 383 276 661 554 939 832 233 126
511 404 789 682 83 960 361 254 639 532 917 810 211 104 489 382 767 660 61 938 339 232 617 510 895 788 189
82 467 360 745 638 39 916 317 210 595 488 873 766 167 60 445 338 723 616 17 894 295 188 573 466 851 744 145
38 423 316 701 594 979 872 273 166 551 444 829 722 123 16 401 294 679 572 957 850 251 144 529 422 807 700
101 978 379 272 657 550 935 828 229 122 507 400 785 678 79 956 357 250 635 528 913 806 207 100 485 378 763
656 57 934 335 228 613 506 891 784 185 78 463 356 741 634 35 912 313 206 591 484 869 762 163 56 441 334 719
612 13 890 291 184 569 462 847 740 141 34 419 312 697 590 975 868 269 162 547 440 825 718 119 12 397 290
675 568 953 846 247 140 525 418 803 696 97 974 375 268 653 546 931 824 225 118 503 396 781 674 75 952 353
246 631 524 909 802 203 96 481 374 759 652 53 930 331 224 609 502 887 780 181 74 459 352 737 630 31 908 309
202 587 480 865 758 159 52 437 330 715 608 9 886 287 180 565 458 843 736 137 30 415 308 693 586 971 864 265
158 543 436 821 714 115 8 393 286 671 564 949 842 243 136 521 414 799 692 93 970 371 264 649 542 927 820
221 114 499 392 777 670 71 948 349 242 627 520 905 798 199 92 477 370 755 648 49 926 327 220 605 498 883
776 177 70 455 348 733 626 27 904 305 198 583 476 861 754 155 48 433 326 711 604 5 882 283 176 561 454 839
732 133 26 411 304 689 582 967 860 261 154 539 432 817 710 111 4 389 282 667 560 945 838 239 132 517 410
795 688 89 966 367 260 645 538 923 816 217 110 495 388 773 666 67 944 345 238 623 516 901 794 195 88 473
366 751 644 45 922 323 216 601 494 879 772 173 66 451 344 729 622 23 900 301 194 579 472 857 750 151 44 429
322 707 600 1 878 279 172 557 450 835 728 129 22 407 300 685 578 963 856 257 150 535 428 813 706 107 0

W algorytmie będą zaszyte wartości współczynników tego generatora.

Algorytm szyfrowania przestawieniowego z pseudolosowym mieszaniem


Wejście
Łańcuch tekstowy s, który zawiera tekst jawny
Wyjście:
Zaszyfrowany łańcuch t,
Elementy pomocnicze:
n – liczba wierszy/kolumn w tablicy, n N
i,j – indeksy, j,j N

Lista kroków:
K01: n ← 1 ; wyznaczamy n
K02: Dopóki n2 < |s| wykonuj n ← n + 1
K03: Dopóki |s| < n2 wykonuj s ← s + "#" ; dopasowujemy długość s
K04: t ←"" ; zerujemy szyfr
K05: Dla j = 1,2,...,n wykonuj K06 ; szyfrujemy
K06: Dla i = 0,1,...,n-1 wykonuj t ← t + s[j + n × i] ; do szyfru dołącz znak z tekstu
K07: Zakończ

Algorytm rozszyfrowywania przestawieniowego z pseudolosowym mieszaniem


Wejście
Łańcuch tekstowy s, który zawiera tekst jawny
Wyjście:
Zaszyfrowany łańcuch t,
Elementy pomocnicze:
n – liczba wierszy/kolumn w tablicy, n N
i,j – indeksy, i,j N

Lista kroków:
K01: n ← 1 ; wyznaczamy n
K02: Dopóki n2 < |s| wykonuj n ← n + 1
K03: Dopóki |s| < n2 wykonuj s ← s + "#" ; dopasowujemy długość s
K04: t ←"" ; zerujemy szyfr
K05: Dla j = 1,2,...,n wykonuj K06 ; szyfrujemy
K06: Dla i = 0,1,...,n-1 wykonuj t ← t + s[j + n × i] ; do szyfru dołącz znak z tekstu
K07: Zakończ

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 452 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje klucz szyfrujący oraz tekst. Jeśli klucz ma wartość dodatnią, to tekst jest szyfrowany. Jeśli klucz
ma wartość ujemną, to tekst zostaje rozszyfrowany.

Lazarus

// Szyfr przestawieniowy
// Data: 12.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program cifer;
var
s : string;
i,m,a,c,X0,p : integer;
x : char;
t : array of integer;
begin
// odczytujemy klucz
readln(X0);
// odczytujemy tekst/szyfr
readln(s);
// definiujemy generator pseudolosowy
m := 984;
a := 493;
c := 385;
// jeśli klucz > 0, to szyfrujemy
// inaczej rozszyfrowujemy
if X0 > 0 then
// przestawiamy znaki tekstu
for i := 1 to length(s) do
begin
// wyznaczamy nową pozycję znaku
X0 := (a * X0 + c) mod m;
p := 1 + X0 mod length(s);
// wymieniamy znaki
x := s[i];
s[i] := s[p];
s[p] := x;
end
else
begin
// odtwarzamy klucz
X0 := - X0;
// tworzymy tablicę dynamiczną na pozycje
SetLength(t,length(s));
// wyliczamy pozycje jak przy szyfrowaniu
for i := 0 to length(s) - 1 do
begin
X0 := (a * X0 + c) mod m;
t[i] := 1 + X0 mod length(s);
end;
// wykorzystujemy pozycje od końca szyfru

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 453 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

for i := length(s) downto 1 do


begin
p := t[i - 1];
x := s[i];
s[i] := s[p];
s[p] := x;
end;
// usuwamy tablicę dynamiczną
SetLength(t,0);
end;
// wypisujemy wynik
writeln(s);
end.

Code::Blocks

// Szyfr przestawieniowy
// Data: 19.02.2011
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
using namespace std;
int main()
{
string s;
int i,m,a,c,X0,*t;
// odczytujemy klucz
cin >> X0;
// odczytujemy tekst/szyfr
cin.ignore(255,'\n');
getline(cin,s);
// definiujemy generator pseudolosowy
m = 984;
a = 493;
c = 385;
// jeśli klucz > 0, to szyfrujemy
// inaczej rozszyfrowujemy
if(X0 > 0)
// przestawiamy znaki tekstu
for(i = 0; i < (int)s.length(); i++)
{
// wyznaczamy nową pozycję znaku
X0 = (a * X0 + c) % m;
// wymieniamy znaki
swap(s[i],s[X0 % s.length()]);
}
else
{
// odtwarzamy klucz
X0 = - X0;
// tworzymy tablicę dynamiczną na pozycje
t = new int[s.length()];
// wyliczamy pozycje jak przy szyfrowaniu
for(i = 0; i < (int)s.length(); i++)
{
X0 = (a * X0 + c) % m;
t[i] = X0 % s.length();
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 454 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

// wykorzystujemy pozycje od końca szyfru


for(i = s.length() - 1; i >= 0; i--)
{
swap(s[i],s[t[i]]);
}
// usuwamy tablicę dynamiczną
delete [] t;
}
// wypisujemy wynik
cout << s << endl;
return 0;
}

Free Basic

' Szyfr przestawieniowy


' Data 19.02.2011
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
Dim As String s,x
Dim As Integer i,m,a,c,X0,p
' odczytujemy klucz
Input X0
' odczytujemy tekst/szyfr
Line Input s
' definiujemy generator pseudolosowy
m = 984
a = 493
c = 385
' jeśli klucz > 0, to szyfrujemy
' inaczej rozszyfrowujemy
If X0 > 0 Then
' przestawiamy znaki tekstu
For i = 1 To Len(s)
' wyznaczamy nową pozycję znaku
X0 = (a * X0 + c) Mod m
p = 1 + X0 Mod Len(s)
' wymieniamy znaki
x = Mid(s,i,1)
Mid(s,i,1) = Mid(s,p,1)
Mid(s,p,1) = x
Next
Else
' odtwarzamy klucz
X0 = - X0
' tworzymy tablicę dynamiczną na pozycje
Dim As Integer t(Len(s))
' wyliczamy pozycje jak przy szyfrowaniu
For i = 1 To Len(s)
X0 = (a * X0 + c) Mod m
t(i) = 1 + X0 Mod Len(s)
Next
' wykorzystujemy pozycje od końca szyfru
For i = Len(s) To 1 Step -1
p = t(i)

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 455 / 519


Algorytmy i Struktury Danych - Szyfry przestawieniowe 2014-10-03

x = Mid(s,i,1)
Mid(s,i,1) = Mid(s,p,1)
Mid(s,p,1) = x
Next
End If
' wypisujemy wynik
Print s
End

Wynik
127
ATAK WIECZOREM OD STRONY RZEKI
CIKYIOATAR W E ZORNKEMZESTD RO
-127
CIKYIOATAR W E ZORNKEMZESTD RO
ATAK WIECZOREM OD STRONY RZEKI

Szyfrowanie kodem przestawieniowym z pseudolosowym mieszaniem


(C)2012 mgr Jerzy Wałaszek

127

ATAK WIECZOREM OD STRONY RZEKI

Szyfruj/Deszyfruj
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na
prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też
tłumaczenia zagadnień szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0065.php 456 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Szyfr Enigmy
Tematy pokrewne Podrozdziały
Łańcuchy znakowe Szyfr podstawieniowy
Podstawowe pojęcia dotyczące przetwarzania tekstów Budowa maszyny Enigma
Podstawowe operacje na łańcuchach znakowych Symulator maszyny Enigma
Naiwne wyszukiwanie wzorca w tekście Program symulatora Enigmy
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Liniowe generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Problem
Opracować uproszczony algorytm szyfrowania przy pomocy niemieckiej maszyny Enigma.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 457 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Szyfr podstawieniowy
Szyfry podstawieniowe (ang. substitution ciphers) polegają na zastępowaniu liter tekstu jawnego innymi literami (lub znakami)
wg określonej reguły. Poznany przez nas wcześniej Szyfr Cezara jest takim właśnie szyfrem podstawieniowym. Prosty szyfr
podstawieniowy możemy skonstruować w sposób następujący:
Przykład:
Zapisujemy dwa wiersze liter od A do Z:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ

Dolny wiersz mieszamy losowo – np. wielokrotnie zamieniając miejscami dwie losowo wybrane literki. W efekcie
otrzymamy tabelkę szyfrowania.

ABCDEFGHIJKLMNOPQRSTUVWXYZ
ILDNSEWMCHPROFXGVAUQJYBZKT

Aby zaszyfrować tekst, podmieniamy literki z górnego wiersza odpowiadającymi im literami wiersza dolnego:

WIRXGREIFENXANXENDE (niem. – ATAKUJEMY STOP, spacje zastąpiliśmy znakiem X)


BCUZWASCESFZIFZSFNS

Prosty szyfr podstawieniowy można łatwo złamać – np. wykorzystując informację o statystycznej częstości występowania
poszczególnych liter w danym języku. Te same znaki są zastępowane zawsze tą samą literą szyfru – np. tekst DDDDD zostanie
zaszyfrowany jako NNNNN. Zastosujmy zatem prostą modyfikację. Tabelkę szyfrowania zwińmy w pierścień – za literą Z będą
występowały litery A, B, C ..., – przed literą A znajdą się litery ... X, Y, Z.

... X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C ...
... Z K T I L D N S E W M C H P R O F X G V A U Q J Y B Z K T I L D ...

Umówmy się następnie, iż pierścień szyfrujący może się obracać wokół swojej osi. Nad pierścieniem umieśćmy nieruchome litery
od A do Z. Poniżej przedstawiamy ten układ w rozwinięciu na płaszczyznę:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
... X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C ...
... Z K T I L D N S E W M C H P R O F X G V A U Q J Y B Z K T I L D ...

Gdy pierścień szyfrujący znajduje się w pokazanym powyżej położeniu, to otrzymujemy szyfr podstawowy pierścienia:

A→A→I
B→B→L
C→C→D
...
Obróćmy teraz pierścień szyfrujący o jedną pozycję w lewo tak, aby pod stałą literą A znalazła się litera B pierścienia:

ABCDEFGHIJKLMNOPQRSTUVWXYZ
... Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D ...
... K T I L D N S E W M C H P R O F X G V A U Q J Y B Z K T I L D N ...

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 458 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Teraz sposób szyfrowania tego układu zmienia się:

A→B→L
B→C→D
C→D→N
...
Dalej umówmy się, iż pierścień szyfrujący obraca się o jedną pozycję w lewo po zaszyfrowaniu każdej litery. Ponieważ sposób
szyfrowania się zmienia, to ciąg tych samych liter nie zostanie już zaszyfrowany w ten sam znak. Sprawdź, iż przy początkowym
ustawieniu pierścienia szyfrującego A - A (pod stałą literą A jest litera A pierścienia) wyraz MAMA zostanie zaszyfrowany jako:
OLXN.
Jeden ruchomy pierścień szyfrujący nie daje odpowiednio dużej kombinacji szyfrów – tylko 26 różnych alfabetów podmieniających.
Jeśli jednak dodamy drugi pierścień szyfrujący (wg innego szyfru podstawieniowego), na którego wejście wprowadzimy wyjście z
pierwszego pierścienia, to liczba kombinacji alfabetów wzrośnie do:

26 × 26 = 676

Drugi pierścień wykonuje obrót o jedną pozycję, gdy pierścień pierwszy wykona pełen obrót – podobnie jak w mechanizmie
licznikowym. Wtedy otrzymamy wszystkie kombinacje alfabetów pierwszego pierścienia z alfabetami pierścienia drugiego.
Dodanie trzeciego pierścienia szyfrującego zwiększy liczbę szyfrów do

26 × 26 × 26 = 17576

Budowa maszyny Enigma


W czasie trwania II Wojny Światowej armia niemiecka posługiwała się powszechnie elektryczno-mechaniczną maszyną szyfrującą
zwaną Enigma (z łac. tajemnica). Zasada działania oparta była o opisane powyżej szyfry podstawieniowe. Elementem
szyfrującym w maszynie były specjalne, ruchome pierścienie szyfrujące.

Pierścienie Enigmy wyposażone były z obu stron w kontakty elektryczne. Na powyższej fotografii widzimy tylko jedną stronę
pierścienia. Kontakty są okrągłymi blaszkami rozmieszczonymi wzdłuż obwodu pierścienia. Z drugiej, niewidocznej strony
umieszczone były identyczne kontakty. Kontakty z obu stron łączono przewodami w pary. Każdy kontakt odpowiadał jednej literze
alfabetu. Sposób połączenia kontaktów z jednej strony pierścienia z kontaktami po drugiej stronie określał sposób szyfrowania
przez pierścień.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 459 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Jeśli do wybranego kontaktu po jednej stronie pierścienia doprowadzimy napięcie elektryczne, to pojawi się ono po drugiej stronie
n a kontakcie połączonym w pierścieniu przewodem elektrycznym z pierwszym kontaktem. W maszynie Enigma kontakty
kolejnych trzech pierścieni szyfrujących były połączone ze sobą za pomocą sprężystych styków. Zatem napięcie na wyjściu
pierwszego pierścienia przenosiło się dalej na wejście kolejnego, itd. Na powyższym rysunku drogę przepływu prądu przez
pierścienie zaznaczono czerwonymi liniami.
Na końcu układu pierścieni znajdował się tzw. bęben odwracający, który posiadał kontakty tylko po jednej stronie. Łączyły się one
z kontaktami wyjściowymi trzeciego pierścienia szyfrującego. Kontakty bębna odwracającego połączone były w pary za pomocą
przewodów elektrycznych. Zatem prąd wchodzący do jednego kontaktu pojawiał się na innym kontakcie bębna odwracającego i
wracał z powrotem do trzeciego pierścienia szyfrującego, a stąd dalej poprzez pierścienie dwa i jeden wychodził na kontakt
wejściowy pierwszego pierścienia, skąd dalej zapalał żarówkę podświetlającą literkę na blacie maszyny Enigma. Droga powrotna
prądu zaznaczona jest liniami niebieskimi. W tym układzie literka C szyfrowana jest na literkę B.
Dzięki zastosowaniu bębna odwracającego szyfr Enigmy stał się szyfrem symetrycznym. Zwróć uwagę, iż jeśli w powyższym
układzie pierścieni szyfrujących wpuścimy prąd kontaktem B, to wyjdzie on kontaktem C. Zatem ten sam klucz (wstępne
położenie pierścieni szyfrujących) można stosować zarówno do szyfrowania jak i do deszyfrowania.
W maszynach Enigma stosowano 10 różnych pierścieni szyfrujących. Poniższa tabela przedstawia ich sposoby szyfrowania.

Permutacje wprowadzane przez pierścienie


Litery wejściowe A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Pierścień I E K M F L G D Q V Z N T O W Y H X U S P A I B R C J
Pierścień II A J D K S I R U X B L H W T M C Q G Z N P Y F V O E
Pierścień III B D F H J L C P R T X V Z N Y E I W G A K M U S Q O
Pierścień IV E S O V P Z J A Y Q U I R H X L N F T G K D C M W B
Pierścień V V Z B R G I T Y U P S D N H L X A W M J Q O F E C K
Pierścień VI J P G V O U M F Y Q B E N H Z R D K A S X L I C T W
Pierścień VII N Z J H G R C X M Y S W B O U F A I V L P E K Q D T
Pierścień VIII F K Q H T L X O C B J S P D Z R A M E W N I U Y G V
Pierścień Beta L E Y J V C N I X W P B Q M D R T A K Z G F U H O S
Pierścień Gamma F S O K A N U E R H M B T I Y C W L Q P Z X V G J D

W powszechnym użyciu były pierścienie I...V. Na obwodzie każdego pierścienia umieszczony był karb, dzięki któremu ruch
pierścienia przenosił się w odpowiednim miejscu do pierścienia następnego. W celu utrudnienia identyfikacji pierścieni
szyfrujących przez przeciwnika, karby umieszczano w różnych miejscach pierścieni (w rzeczywistości była to kryptologiczna
pomyłka, która ułatwiała polskim kryptologom identyfikację zastosowanych pierścieni). Zasada działania układu przeniesienia
napędu była podobna do układów mechanicznych liczników. Gdy pierwszy pierścień wykonał pełny obrót, to karb na jego obwodzie
powodował zazębienie się specjalnej zapadki obracającej następny pierścień o 1/26 obwodu. W drugim pierścieniu identyczny
układ obracał trzeci pierścień, gdy pierścień drugi wykonał pełny obrót.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 460 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Lewa strona pierścienia szyfrującego Enigmy. Prawa strona pierścienia szyfrującego Enigmy.
Z boku widoczny jest karb przeniesienia. Widoczna jest zębatka napędowa oraz kontakty.

W poniższej tabeli przedstawiono położenie karbu w pierścieniach szyfrujących Enigmy.

Punkt przeniesienia obrotu


Pierścień I przy R
Pierścień II przy F
Pierścień III przy W
Pierścień IV przy K
Pierścień V przy A
Pierścienie VI, VII oraz VIII przy A i przy N

Dane w tabeli należy rozumieć następująco:

W blacie Enigmy znajdowały się prostokątne otworki, poprzez które widoczne były litery wygrawerowane na
obwodzie każdego pierścienia szyfrującego (później zamiast liter stosowano liczby 01...26). Poniższe zdjęcie
przedstawia widok pierścieni szyfrującej Enigmy stosowanej przez Abwehrę (wywiad wojskowy) i Kriegsmarine
(marynarka wojenna), w której stosowano 4 pierścienie zamiast 3 używanych przez Enigmy Wehrmachtu.

Załóżmy, iż pierwszym od prawej pierścieniem szyfrującym był pierścień I. Otóż gdy w okienku pojawiła się dla tego
pierścienia literka R, to obrót pierścienia I powodował również obrót sąsiadującego po lewej pierścienia szyfrującego.

W nowszych Enigmach punkt przeniesienia można było obracać na obwodzie pierścienia, co dodatkowo komplikowało system
szyfrowania.
W poniższej tabelce zebraliśmy parametry bębnów odwracających, używanych w Armii Niemieckiej. Bębny były nieruchome, nie
posiadały zatem mechanizmu przenoszenia napędu (w niektórych modelach Enigmy można jednak było je obracać ręcznie, co
wprowadzało dodatkową modyfikację szyfru).

Zamiany liter wprowadzane przez bębny odwracające


Litery wejściowe A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Bęben B Y R U H Q S L D P X N G O K M I E B F Z C W V J A T
Bęben C F V P J I A O Y E D R Z X W G C T K U Q S B N M H L
reflektor B Dünn E N K Q A U Y W J I C O P B L M D X Z V F T H R G S
reflektor C Dünn R D O B J N T K V E H M L F C W Z A X G Y I P S U Q

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 461 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Zwróć uwagę, iż bębny odwracające szyfrują w sposób symetryczny. Np. dla bębna B litera A przechodzi w Y oraz litera Y
przechodzi w A. Spowodowane jest to tym, iż wewnątrz bębna pary kontaktów są połączone ze sobą przewodem. Zatem prąd
wchodzący kontaktem A zawsze pojawi się na kontakcie Y i na odwrót.

Oprócz pierścieni maszyny Enigma posiadały tzw. łącznicę wtyczkową. Za pomocą przewodów z wtyczkami łącznica pozwalała
na zamianę ze sobą par liter docierających z klawiatury Enigmy do pierścieni szyfrujących. Na obrazku widzimy, iż zamieniane są
ze sobą litery:

A z J oraz S z O

Zamiana taka powoduje to, iż jeśli na klawiaturze naciśniemy np. klawisz A, to do pierścieni dotrze sygnał litery J. Podobnie gdy
naciśniemy klawisz J, do pierścieni szyfrujących dotrze sygnał na kontakt A. Również prąd wychodzący kontaktem A spowoduje
zapalenie się lampki J i na odwrót. Dzięki łącznicy ilość możliwych do uzyskania kombinacji szyfrów osiągała astronomiczną
liczbę 15 × 1018. Niemcy uważali, iż kod Enigmy jest niemożliwy do złamania. Jak dzisiaj wiemy, kod ten został złamany przez
trzech polskich kryptologów: M. Rejewskiego, J. Różyckiego oraz H. Zygalskiego. Więcej na temat Enigmy znajdziesz w naszym
artykule o komputerach Colossus.

Symulator maszyny Enigma


Projektowany przez nas symulator Enigmy będzie posiadał następujące elementy składowe:

Wejście dla 26 liter od A do Z, które symuluje klawiaturę Enigmy. Na klawiaturze brak spacji oraz znaków przystankowych. W
charakterze spacji niemieccy szyfranci stosowali nieużywaną w tekstach literę X:

MORGENSXGREIFENXWIRXANXENDE → RANKIEM ATAKUJEMY STOP

Wyjście dla 26 liter od A do Z, które symuluje lampki Enigmy. Lampki zapalając się podświetlały od spodu literki będące wynikiem
szyfrowania literek wprowadzonych do maszyny przy pomocy klawiatury.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 462 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

Zespół trzech pierścieni szyfrujących, które można dowolnie konfigurować z pierścieni od I do V rzeczywistej Enigmy. Zachowamy
tutaj sposób szyfrowania tych pierścieni. Pierścienie będą współpracowały z bębnem odwracającym typu B. Ten element nie
będzie wymienny. W celu uproszczenia założymy również, iż punkty przeniesień dla poszczególnych pierścieni szyfrujących są
stałe, zgodne z podaną wcześniej tabelą. W rzeczywistej Enigmie punkty te można było przemieszczać wraz z tarczą literową, co
powodowało zmianę sposobu szyfrowania dla danego klucza.
Układ pierścieni będzie definiowany 3 cyfrową liczbą dziesiętną. Np. 351 oznacza, licząc od lewej do prawej, kolejno pierścień III,
pierścień V oraz pierścień I. Dane są wprowadzane do pierścienia I, następnie przechodzą do pierścienia V i III, odbijają się w
bębnie odwracającym i wracają poprzez pierścień III, V i I.
Stan początkowy pierścieni (czyli to, co widać w okienkach szyfrowych Enigmy) określany będzie trzyliterowym tekstem. Np. FAD
oznacza (przy powyższym układzie pierścieni szyfrujących), iż pierścień III należy ustawić na F, pierścień V na A i pierścień I na
D.

Łącznicę wtyczkową, która pozwala zamieniać ze sobą pary wybranych liter. W Enigmie stosowano 10 przewodów z wtyczkami,
które umożliwiały dokonanie zamian 20 liter. Wszystkich możliwych permutacji jest 150738274937250. To właśnie dzięki łącznicy
wtyczkowej Enigma posiadała tak wielką liczbę możliwych kodów, iż niemieccy kryptolodzy uznali za niemożliwe złamanie jej
szyfru. Na szczęście dla nas mylili się, co w efekcie kosztowało Niemcy przegranie wojny.
Stan łącznicy wtyczkowej będziemy definiowali tekstem złożonym z par liter, które mają być zamienione miejscami. Np. tekst
AXDSFE oznacza, iż zostaną ze sobą zamienione następujące litery:

A z X, D z S i F z E

Zamiana liter występuje w dwóch miejscach:


Przy wprowadzeniu znaku przed podaniem go na układ pierścieni szyfrujących, czyli np. literka A zostanie na wejściu
zastąpiona literką X i na pierwszy pierścień szyfrujący trafi znak X, a nie znak A.
Przy wyprowadzaniu znaku z pierścieni szyfrujących na wyjście (w Enigmie był to panel z lampkami), czyli np. znak D
zostanie zastąpiony znakiem S i ten znak trafi na wyjście symulatora (w Enigmie zapaliłaby się lampka S zamiast lampki
D).
Program symulatora Enigmy
Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje kolejno:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 463 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

konfigurację 3 pierścieni szyfrujących w postaci liczby trzycyfrowej 111...555 (w rzeczywistych Enigmach


można było stosować tylko po jednym pierścieniu danego rodzaju),
położenie początkowe pierścieni w postaci łańcucha trzyznakowego AAA...ZZZ.
stan łącznicy wtyczkowej w postaci łańcucha zbudowanego z par liter.
szyfrogram w postaci łańcucha znakowego o dowolnej długości.
Wynikiem działania symulatora jest zaszyfrowany / rozszyfrowany tekst szyfrogramu.

Lazarus

// Symulator Enigmy
// Data: 20.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
program prg;
// definicje elementów Enigmy
const
pierscien_szyfr : array[1..5] of string = ('EKMFLGDQVZNTOWYHXUSPAIBRCJ',
'AJDKSIRUXBLHWTMCQGZNPYFVOE',
'BDFHJLCPRTXVZNYEIWGAKMUSQO',
'ESOVPZJAYQUIRHXLNFTGKDCMWB',
'VZBRGITYUPSDNHLXAWMJQOFECK');
przeniesienie : string = 'RFWKA';
beben_odwr : string = 'YRUHQSLDPXNGOKMIEBFZCWVJAT';
var
pierscien : array[1..3] of integer;
szyfr,s,lacznica : string;
i,j,k,n : integer;
c : char;
ruch : boolean;
begin
// odczytujemy konfigurację pierścieni szyfrujących
readln(n);
for i := 3 downto 1 do
begin
pierscien[i] := n mod 10; // numer pierścienia na i-tej pozycji
n := n div 10;
end;
// odczytujemy położenia początkowe pierścieni
readln(szyfr); szyfr := upcase(szyfr);
// odczytujemy stan łącznicy wtyczkowej
readln(s); s := upcase(s);
lacznica := 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
i := 1;
while i < length(s) do
begin
lacznica[ord(s[i]) - 64] := s[i + 1];
lacznica[ord(s[i + 1]) - 64] := s[i];
inc(i,2);
end;
// odczytujemy szyfrogram
readln(s); s := upcase(s);
// szyfrujemy/rozszyfrowujemy szyfrogram
for i := 1 to length(s) do
begin
// najpierw ruch pierścieni szyfrujących
ruch := true; j := 3;
while ruch and (j > 0) do
begin
ruch := szyfr[j] = przeniesienie[pierscien[j]];
szyfr[j] := chr(65 + (ord(szyfr[j]) - 64) mod 26);
dec(j);
end;
// pobieramy znak szyfrogramu

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 464 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

c := s[i];
// przechodzimy przez łącznicę wtyczkową
c := lacznica[ord(c) - 64];
// przechodzimy przez pierścienie w kierunku do bębna odwracającego
for j := 3 downto 1 do
begin
k := ord(szyfr[j]) - 65;
c := pierscien_szyfr[pierscien[j]][1 + (ord(c) - 65 + k) mod 26];
c := chr(65 + (ord(c) - 39 - k) mod 26);
end;
// przechodzimy przez bęben odwracający
c := beben_odwr[ord(c) - 64];
// wracamy ścieżką powrotną
for j := 1 to 3 do
begin
k := ord(szyfr[j]) - 65;
c := chr(65 + (ord(c) - 65 + k) mod 26);
n := 1;
while pierscien_szyfr[pierscien[j]][n] <> c do inc(n);
c := chr(65 + (25 + n - k) mod 26);
end;
// przechodzimy przez łącznicę wtyczkową
c := lacznica[ord(c) - 64];
// uaktualniamy szyfrogram
s[i] := c;
end;
// wyświetlamy szyfrogram
writeln(s);
writeln;
end.

Code::Blocks

// Symulator Enigmy
// Data: 22.08.2008
// (C)2012 mgr Jerzy Wałaszek
//-----------------------------
#include <iostream>
#include <string>
using namespace std;
// definicje elementów Enigmy
const string pierscien_szyfr[5] = {"EKMFLGDQVZNTOWYHXUSPAIBRCJ",
"AJDKSIRUXBLHWTMCQGZNPYFVOE",
"BDFHJLCPRTXVZNYEIWGAKMUSQO",
"ESOVPZJAYQUIRHXLNFTGKDCMWB",
"VZBRGITYUPSDNHLXAWMJQOFECK"};
const string przeniesienie = "RFWKA";
const string beben_odwr = "YRUHQSLDPXNGOKMIEBFZCWVJAT";
int main()
{
int pierscien[3],i,j,k,n,c;
bool ruch;
string szyfr,s,lacznica;

// odczytujemy konfigurację pierścieni szyfrujących


cin >> n;
for(i = 2; i >= 0; i--)
{
pierscien[i] = (n % 10) - 1; // numer pierścienia na i-tej pozycji
n /= 10;
}
// odczytujemy położenia początkowe pierścieni
cin >> szyfr;
for(i = 0; i < szyfr.length(); i++) szyfr[i] = toupper(szyfr[i]);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 465 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

// odczytujemy stan łącznicy wtyczkowej


cin >> s;
for(i = 0; i < s.length(); i++) s[i] = toupper(s[i]);
lacznica = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for(i = 0; i < s.length() - 1; i += 2)
{
lacznica[s[i] - 65] = s[i + 1];
lacznica[s[i + 1] - 65] = s[i];
}
// odczytujemy szyfrogram
cin.ignore(256,'\n');
getline(cin,s);
for(i = 0; i < s.length(); i++) s[i] = toupper(s[i]);
// szyfrujemy/rozszyfrowujemy szyfrogram
for(i = 0; i < s.length(); i++)
{
// najpierw ruch pierścieni szyfrujących
for(ruch = true, j = 2; ruch && (j >= 0); j--)
{
ruch = (szyfr[j] == przeniesienie[pierscien[j]]);
szyfr[j] = 65 + (szyfr[j] - 64) % 26;
}
// pobieramy znak szyfrogramu
c = s[i];
// przechodzimy przez łącznicę wtyczkową
c = lacznica[c - 65];
// przechodzimy przez pierścienie w kierunku do bębna odwracającego
for(j = 2; j >= 0; j--)
{
k = szyfr[j] - 65;
c = pierscien_szyfr[pierscien[j]][(c - 65 + k) % 26];
c = 65 + (c - 39 - k) % 26;
}
// przechodzimy przez bęben odwracający
c = beben_odwr[c - 65];
// wracamy ścieżką powrotną
for(j = 0; j < 3; j++)
{
k = szyfr[j] - 65;
c = 65 + (c - 65 + k) % 26;
for(n = 0; pierscien_szyfr[pierscien[j]][n] != c; n++) ;
c = 65 + (26 + n - k) % 26;
}
// przechodzimy przez łącznicę wtyczkową
c = lacznica[c - 65];
// uaktualniamy szyfrogram
s[i] = c;
}
// wyświetlamy szyfrogram
cout << s << endl << endl;
return 0;
}

Free Basic

' Symulator Enigmy


' Data: 22.08.2008
' (C)2012 mgr Jerzy Wałaszek
'-----------------------------
' definicje elementów Enigmy
Dim pierscien_szyfr(5) As String => {"","EKMFLGDQVZNTOWYHXUSPAIBRCJ",_
"AJDKSIRUXBLHWTMCQGZNPYFVOE",_
"BDFHJLCPRTXVZNYEIWGAKMUSQO",_
"ESOVPZJAYQUIRHXLNFTGKDCMWB",_

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 466 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

"VZBRGITYUPSDNHLXAWMJQOFECK"}
Dim przeniesienie As String = "RFWKA"
Dim beben_odwr As String = "YRUHQSLDPXNGOKMIEBFZCWVJAT"
Dim As String szyfr,s,lacznica
Dim As Integer pierscien(3),i,j,k,n,c,ruch
' odczytujemy konfigurację pierścieni szyfrujących
Input n
For i = 3 To 1 Step -1
pierscien(i) = n Mod 10 ' numer pierścienia na i-tej pozycji
n \= 10
Next
' odczytujemy położenia początkowe pierścieni
Input szyfr
szyfr = Ucase(szyfr)
' odczytujemy stan łącznicy wtyczkowej
Input s
s = Ucase(s)
lacznica = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
For i = 1 To Len(s) - 1 Step 2
Mid(lacznica,Asc(Mid(s,i,1)) - 64,1) = Mid(s,i + 1,1)
Mid(lacznica,Asc(Mid(s,i + 1,1)) - 64,1) = Mid(s,i,1)
Next
' odczytujemy szyfrogram
Line Input s
s = Ucase(s)
' szyfrujemy/rozszyfrowujemy szyfrogram
For i = 1 To Len(s)
' najpierw ruch pierścieni szyfrujących
ruch = 1: j = 3
While (ruch = 1) And (j > 0)
If Mid(szyfr,j,1) <> Mid(przeniesienie,pierscien(j),1) Then ruch = 0
Mid(szyfr,j,1) = Chr(65 + (Asc(Mid(szyfr,j,1)) - 64) Mod 26)
j -= 1
Wend
' pobieramy znak szyfrogramu
c = Asc(Mid(s,i,1))
' przechodzimy przez łącznicę wtyczkową
c = Asc(Mid(lacznica,c - 64,1))
' przechodzimy przez pierścienie w kierunku do bębna odwracającego
For j = 3 To 1 Step -1
k = Asc(Mid(szyfr,j,1)) - 65
c = Asc(Mid(pierscien_szyfr(pierscien(j)),1 + (c - 65 + k) Mod 26,1))
c = 65 + (c - 39 - k) Mod 26
Next
' przechodzimy przez bęben odwracający
c = Asc(Mid(beben_odwr,c - 64,1))
' wracamy ścieżką powrotną
For j = 1 To 3
k = Asc(Mid(szyfr,j,1)) - 65
c = 65 + (c - 65 + k) Mod 26
n = 1
While Asc(Mid(pierscien_szyfr(pierscien(j)),n,1)) <> c: n += 1: Wend
c = 65 + (25 + n - k) Mod 26
Next
' przechodzimy przez łącznicę wtyczkową
c = Asc(Mid(lacznica,c - 64,1))
' uaktualniamy szyfrogram
Mid(s,i,1) = Chr(c)
Next
' wyświetlamy szyfrogram
Print s
Print

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 467 / 519


Algorytmy i Struktury Danych - Niemiecka maszyna szyfrująca Enigma 2014-10-03

End

Wynik
123
ABC
ABCD
WIRXGEBENXAUFXENDE
ASDIQXFZJGWONIMHOT

Symulator maszyny Enigma


(C)2012 mgr Jerzy Wałaszek
Kolejność pierścieni szyfrujących
123
Ustawienia początkowe pierścieni
ABC
Stan połączeń łącznicy wtyczkowej
ABCD
Szyfrogram
WIRXGEBENXAUFXENDE

Uruchom Enigmę
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0066.php 468 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Algorytm RSA
Tematy pokrewne Podrozdziały
Łańcuchy znakowe Fazy algorytmu RSA
Podstawowe pojęcia dotyczące przetwarzania tekstów Tworzenie kluczy RSA
Podstawowe operacje na łańcuchach znakowych Szyfrowanie kluczem publicznym RSA
Naiwne wyszukiwanie wzorca w tekście Rozszyfrowywanie kluczem prywatnym RSA
Wyszukiwanie maksymalnego prefikso-sufiksu Program RSA
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta Przykładowe zastosowania RSA
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Liniowe generatory liczb pseudolosowych
Generowanie liczb pseudolosowych

Problem
Opracować niesymetryczny system szyfrowania danych.

Symetryczny system szyfrowania to taki, w którym klucz szyfrujący pozwala zarówno szyfrować dane, jak również odszyfrowywać
je. Opisane w poprzednich rozdziałach systemy były systemami symetrycznymi. Podstawową wadą systemów symetrycznych
jest ścisła konieczność ochrony klucza. Z tego powodu mozna je było stosować tylko w ograniczonych grupach użytkowników.
W roku 1977 trzej profesorowie z MIT w USA, Ronald L. Rivest, Adi Shamir i Leonard
Adleman, opublikowali nowy rodzaj szyfrowania danych, który nazwano od pierwszych
liter ich nazwisk systemem RSA. Jest to niesymetryczny algorytm szyfrujący, którego
zasadniczą cechą są dwa klucze: publiczny do kodowania informacji oraz prywatny do jej
odczytywania. Klucz publiczny (można go udostępniać wszystkim zainteresowanym)
umożliwia jedynie zaszyfrowanie danych i w żaden sposób nie ułatwia ich odczytania, nie
R.L.Rivest A. Shamir L. Adleman musi więc być chroniony. Dzięki temu firmy dokonujące transakcji poprzez sieć Internet
mogą zapewnić swoim klientom poufność i bezpieczeństwo. Drugi klucz (prywatny,
Twórcy algorytmu RSA
przechowywany pod nadzorem) służy do odczytywania informacji zakodowanych przy
pomocy pierwszego klucza. Klucz ten nie jest udostępniany publicznie. System RSA umożliwia bezpieczne przesyłanie danych w
środowisku, w którym może dochodzić do różnych nadużyć. Bezpieczeństwo oparte jest na trudności rozkładu dużych liczb na
czynniki pierwsze.

Przykład:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 469 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

Załóżmy, iż dysponujemy superszybkim komputerem, który jest w stanie sprawdzić podzielność miliarda dużych
liczb w ciągu jednej sekundy. Aby złamać szyfr RSA należy rozbić klucz publiczny na dwie liczby pierwsze będące
jego dzielnikami. Znajomość tych liczb pozwala rozszyfrować każdą informację zakodowaną kluczem prywatnym i
publicznym.
Brzmi dosyć prosto. Jednakże nie ma prostej metody rozbijania dużych liczb na czynniki pierwsze. Nie istnieje
żaden wzór, do którego podstawiamy daną liczbę i w wyniku otrzymujemy wartości jej czynników pierwszych.
Należy je znaleźć testując podzielność kolejnych liczb.
Z rozważań o liczbach pierwszych wynika, iż w przypadku dwóch różnych dzielników pierwszych jeden musi leżeć
poniżej wartości pierwiastka z danej liczby, a drugi powyżej (dlaczego?). Zatem, aby go znaleźć musimy wyliczyć
pierwiastek z rozkładanej liczby, a następnie testować podzielność przez liczby nieparzyste leżące poniżej tego
pierwiastka.
Statystycznie poszukiwany czynnik pierwszy powinien znajdować się w górnej połówce zakresu od 2 do pierwiastka
z n. Ile działań musimy wykonać? Policzmy.
Klucz 128 bitowy. Pierwiastek jest liczbą 64 bitową. W zakresie od 2 do 264 co druga liczba jest nieparzysta, zatem
jest ich około 264 / 2 = 263. Ponieważ interesuje nas tylko górna połówka, to ilość liczb do sprawdzenia jest dwa razy
mniejsza, czyli wynosi 263 / 2 = 262. Ile czasu zajmie naszemu superkomputerowi sprawdzenie podzielności przez
około 262 liczb, jeśli w ciągu 1 sekundy wykonuje on miliard sprawdzeń? Odpowiedź brzmi:

zajmie to około:
262 / 109 = 4611686018 sekund = 76861433 minut = 1281023 godzin = 53375 dni = 146 lat

Czy sądzisz, że ktoś będzie czekał przez prawie dwa życia na złamanie szyfru? Zatem można podać do publicznej
wiadomości liczbę będącą iloczynem dwóch dużych liczb pierwszych i mieć prawie pewność, iż nikt jej nie rozbije na
czynniki pierwsze w rozsądnym czasie. Ostatecznie zamiast 128 bitów możemy zwiększyć klucz do np. 1024 bitów,
a wtedy czas łamania szyfru liczy się miliardami miliardów... miliardów lat.

Fazy algorytmu RSA


Algorytm RSA składa się z trzech podstawowych kroków:
1. Generacja klucza publicznego i tajnego. Klucz publiczny jest przekazywany wszystkim zainteresowanym i umożliwia
zaszyfrowanie danych. Klucz tajny umożliwia rozszyfrowanie danych zakodowanych kluczem publicznym. Jest trzymany w
ścisłej tajemnicy.
2. Użytkownik po otrzymaniu klucza publicznego, np. poprzez sieć Internet, koduje za jego pomocą swoje dane i przesyła je w
postaci szyfru RSA do adresata dysponującego kluczem tajnym, np. do banku, firmy komercyjnej, tajnych służb. Klucz
publiczny nie musi być chroniony, ponieważ nie umożliwia on rozszyfrowania informacji – proces szyfrowania nie jest
odwracalny przy pomocy tego klucza. Zatem nie ma potrzeby jego ochrony i może on być powierzany wszystkim
zainteresowanym bez ryzyka złamania kodu.
3. Adresat po otrzymaniu zaszyfrowanej wiadomości rozszyfrowuje ją za pomocą klucza tajnego.

Tworzenie kluczy RSA

Znajdź dwie duże liczby pierwsze (mające np. po 128 bitów). Oznacz je jako p i q. Istnieją specjalne algorytmy generujące
I
duże liczby pierwsze, które wykorzystują np. test Millera-Rabina.

Oblicz:
Ø = (p - 1) × (q - 1)
II oraz
n=p×q
Liczby pierwsze p i q usuń, aby nie wpadły w niepowołane ręce. Ø to tzw. funkcja Eulera, n jest modułem.
Wykorzystując odpowiednio algorytm Euklidesa znajdź liczbę e, która jest względnie pierwsza z wyliczoną wartością funkcji
III Eulera Ø (tzn. NWD(e, Ø) = 1) Liczba ta powinna również spełniać nierówność 1 < e < n . Nie musi ona być pierwsza lecz
nieparzysta.

IV Oblicz
liczbę odwrotną modulo Ø do liczby e, czyli spełniającą równanie d × e mod Ø = 1. Można to zrobić przy pomocy
rozszerzonego algorytmu Euklidesa, który umieściliśmy w naszym artykule.

V Klucz
publiczny jest parą liczb (e, n), gdzie e nazywa się publicznym wykładnikiem. Możesz go przekazywać wszystkim
zainteresowanym.

VI Klucz tajny to (d, n), gdzie d nazywa się prywatnym wykładnikiem. Klucz ten należy przechowywać pod ścisłym nadzorem.

Przykład:

p = 13 Wybieramy dwie dowolne liczby pierwsze. W naszym przykładzie nie będą one duże, aby nie utrudniać
q = 11 obliczeń. W rzeczywistości liczby te powinny być ogromne.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 470 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

Obliczamy Ø = (p - 1) × (q - 1) , czyli tzw. funkcję Eulera:

Ø = 120 Ø = (13 - 1) × (11 - 1) = 12 × 10 = 120

Obliczamy moduł n:

n = 143
n = p × q = 13 × 11 = 143

Wyznaczamy wykładnik publiczny e. Ma on być względnie pierwszy z Ø czyli z liczbą 120. Warunek
e=7
ten spełnia, np. liczba 7.
Wyznaczamy następnie wykładnik prywatny, który ma być odwrotnością modulo Ø liczby e, czyli
d = 103 d × 7 mod 120 = 1.
Liczbą spełniającą ten warunek jest 103
(7,143) Klucz publiczny (e, n)
(103,143) Klucz tajny (d, n)

Szyfrowanie kluczem publicznym RSA


I Otrzymujesz od adresata klucz publiczny w postaci pary liczb (e, n).
Wiadomość do zaszyfrowania zamieniasz na liczby naturalne t, które muszą spełniać nierówność
0<t <n
II Można tutaj skorzystać np. z łączenia kodów znaków. Oczywiście adresat musi znać użyty przez ciebie sposób
przekształcenia tekstu w liczbę, aby mógł on później odtworzyć otrzymaną wiadomość. Zwykle nie ma z tym problemu,
ponieważ nadawca i odbiorca stosują wspólne oprogramowanie, które troszczy się za ciebie o takie szczegóły techniczne.
Na tak otrzymanych liczbach wykonujesz operację szyfrowania i otrzymujesz liczby

III
c = t e mod n.

Liczby c są zaszyfrowaną postacią liczb t i przekazuje się je adresatowi wiadomości. Klucz (e , n) umożliwił ich
IV zaszyfrowanie, lecz nie pozwala ich rozszyfrować.

Przykład:
e = 7 Otrzymaliśmy klucz publiczny (e, n). Przy jego pomocy możemy zakodować liczby od 0 do 142. Zauważ,
n = 143 iż liczby 0 oraz 1 nie zostaną zakodowane (dlaczego?).
Załóżmy, iż chcemy przesłać adresatowi zaszyfrowaną liczbę t = 123. W tym celu musimy obliczyć
wartość wyrażenia:

c=7 c = 1237 mod 143 = 425927596977747 mod 143 = 7

Wynik jest zaszyfrowaną liczbą 123. Przesyłamy go do adresata.

Rozszyfrowywanie kluczem prywatnym RSA

Jesteś adresatem zaszyfrowanych wiadomości. Wcześniej wszystkim korespondentom przesłałeś wygenerowany klucz
publiczny (e,n), za pomocą którego mogą oni szyfrować i przesyłać ci swoje dane. Otrzymujesz więc zaszyfrowaną
wiadomość w postaci liczb naturalnych c, które muszą spełniać warunek:
I
0<c<n

Liczbę c przekształcasz na pierwotną wartość t stosując wzór:


II t = c d mod n

III Z otrzymanej liczby t odtwarzasz wg ustalonego systemu znaki tekstu. Teraz możesz odczytać przesłaną wiadomość.

Przykład:

d = 103 Otrzymaliśmy zakodowaną wiadomość o wartości 7. Jesteśmy w posiadaniu klucza prywatnego, który
n = 143 służy do rozszyfrowywania wiadomości zakodowanych kluczem publicznym.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 471 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

służy do rozszyfrowywania wiadomości zakodowanych kluczem publicznym.


c=7

Wykonujemy następujące operacje:


t = 7103 mod 143
Potęga jest zbyt duża, aby można ją było w normalny sposób obliczyć (języki programowania mają
zwykle ograniczenia co do wielkości liczb całkowitych). Jednakże nas nie interesuje wartość liczbowa
potęgi, a jedynie reszta z dzielenia jej przez 143. Możemy więc rozłożyć potęgę na iloczyn składników o
wykładnikach równych kolejnym potęgom liczby dwa:

7103 mod 143 = 764 + 32 + 4 + 2 + 1 mod 143 =


(764 mod 143) × (732 mod 143) × (74 mod 143) × (72 mod 143) × 7 mod 143
71 mod 143 = 7
t = 123 72 mod 143 = (71 mod 143)2 mod 143 = 49 mod 143 = 49
74 mod 143 = (72 mod 143)2 mod 143 = 492 mod 143 = 113
78 mod 143 = (74 mod 143)2 mod 143 = 1132 mod 143 = 42
716 mod 143 = (78 mod 143)2 mod 143 = 422 mod 143 = 48
732 mod 143 = (716 mod 143)2 mod 143 = 482 mod 143 = 16
764 mod 143 = (732 mod 143)2 mod 143 = 162 mod 143 = 113

Do wyliczenia potęgi bierzemy tylko te reszty, które występują w sumie potęg 2: (jeśli byłoby ich bardzo
dużo, to każde mnożenie można wykonać z operacją modulo, dzięki czemu wynik nigdy nie wyjdzie poza
wartość modułu)

t = 7103 mod 143 = 113 × 16 × 113 × 49 × 7 mod 143 = 123

Program RSA
Program
Na podstawie podanych informacji napiszemy prostą aplikację, która pełnić będzie rolę kompletnego systemu
szyfrowania RSA. Proces szyfrowania i rozszyfrowywania jest identyczny, różni się tylko rodzajem zastosowanego
klucza. Dlatego w aplikacji występują jedynie dwie opcje: tworzenie kluczy RSA oraz szyfrowanie RSA. W
pierwszym przypadku program generuje dwa klucze, publiczny oraz prywatny. Należy zapamiętać te dane, gdyż
będą one potrzebne w drugiej opcji do szyfrowania lub rozszyfrowywania. Proponujemy zastosowanie tej aplikacji do
prostej zabawy w klasie. Tworzymy jedną grupę uczniów, która utworzy klucz publiczny oraz prywatny. Klucz
publiczny przekaże reszcie klasy, klucz prywatny zachowa dla siebie. Następnie pozostali uczniowie na podstawie
otrzymanych kluczy publicznych mogą kodować swoje dane i przekazywać je pierwszej grupie, która za pomocą
klucza prywatnego dokona rozszyfrowania wiadomości.
Życzymy dobrej zabawy.
Lazarus

{
*******************************************************
** Przykładowa aplikacja obrazująca sposób działania **
** asymetrycznego systemu kodowania informacji RSA. **
** ------------------------------------------------- **
** (C)2012 mgr Jerzy Wałaszek **
** I Liceum Ogólnokształcące **
** im. Kazimierza Brodzińskiego **
** w Tarnowie **
*******************************************************
}
program rsa;
// Procedura oczekuje na naciśnięcie klawisza Enter
// po czym czyści ekran okna konsoli
//-------------------------------------------------
procedure Czekaj;
var
i : integer;
begin
writeln;
writeln('Zapisz te dane i nacisnij Enter');
readln;
for i := 1 to 500 do writeln;
end;
// Funkcja obliczająca NWD dla dwóch liczb
//----------------------------------------
function nwd(a,b : integer) : integer;
var
t : integer;
begin
while b <> 0 do

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 472 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

begin
t := b;
b := a mod b;
a := t
end;
nwd := a
end;
// Funkcja obliczania odwrotności modulo n
//----------------------------------------
function odwr_mod(a,n : integer) : integer;
var
a0,n0,p0,p1,q,r,t : integer;
begin
p0 := 0; p1 := 1; a0 := a; n0 := n;
q := n0 div a0;
r := n0 mod a0;
while r > 0 do
begin
t := p0 - q * p1;
if t >= 0 then
t := t mod n
else
t := n - ((-t) mod n);
p0 := p1; p1 := t;
n0 := a0; a0 := r;
q := n0 div a0;
r := n0 mod a0;
end;
odwr_mod := p1;
end;
// Procedura generowania kluczy RSA
//---------------------------------
procedure klucze_RSA;
const
tp : array[0..9] of integer = (11,13,17,19,23,29,31,37,41,43);
var
p,q,phi,n,e,d : integer;
begin
writeln('Generowanie kluczy RSA');
writeln('----------------------');
writeln;
// generujemy dwie różne, losowe liczby pierwsze
repeat
p := tp[random(10)];
q := tp[random(10)];
until p <> q;
phi := (p - 1) * (q - 1);
n := p * q;
// wyznaczamy wykładniki e i d
e := 3;
while nwd(e,phi) <> 1 do inc(e,2);
d := odwr_mod(e,phi);
// gotowe, wypisujemy klucze
writeln('KLUCZ PUBLICZNY');
writeln('wykladnik e = ',e);
writeln(' modul n = ',n);
writeln;
writeln('KLUCZ PRYWATNY');
writeln('wykladnik d = ',d);
Czekaj;
end;
// Funkcja oblicza modulo potęgę podanej liczby
//---------------------------------------------
function pot_mod(a,w,n : integer) : integer;
var
pot,wyn,q : integer;
begin
// wykładnik w rozbieramy na sumę potęg 2. Dla reszt
// niezerowych tworzymy iloczyn potęg a modulo n.
pot := a; wyn := 1; q := w;
while q > 0 do
begin
if (q mod 2) = 1 then wyn := (wyn * pot) mod n;
pot := (pot * pot) mod n; // kolejna potęga
q := q div 2;
end;
pot_mod := wyn;
end;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 473 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

// Procedura kodowania danych RSA


//-------------------------------
procedure kodowanie_RSA;
var
e,n,t : integer;
begin
writeln('Kodowanie danych RSA');
writeln('--------------------');
writeln;
write('Podaj wykladnik = '); readln(e);
write(' Podaj modul = '); readln(n);
writeln('----------------------------------');
writeln;
write('Podaj kod RSA = '); readln(t);
writeln;
writeln('Wynik kodowania = ',pot_mod(t,e,n));
Czekaj;
end;
// ********************
// ** Program główny **
// ********************
var
w : integer;
begin
randomize;
repeat
writeln('System szyfrowania danych RSA');
writeln('-----------------------------');
writeln(' (C)2012 mgr Jerzy Walaszek ');
writeln;
writeln('MENU');
writeln('====');
writeln('[ 0 ] - Koniec pracy programu');
writeln('[ 1 ] - Generowanie kluczy RSA');
writeln('[ 2 ] - Kodowanie RSA');
writeln;
write('Jaki jest twoj wybor? (0, 1 lub 2) : ');
readln(w);
writeln; writeln; writeln;
case w of
1 : klucze_RSA;
2 : kodowanie_RSA;
end;
writeln; writeln; writeln;
until w = 0;
end.

Code::Blocks

/*
*******************************************************
** Przykładowa aplikacja obrazująca sposób działania **
** asymetrycznego systemu kodowania informacji RSA. **
** ------------------------------------------------- **
** (C)2012 mgr Jerzy Wałaszek **
** I Liceum Ogólnokształcące **
** im. Kazimierza Brodzińskiego **
** w Tarnowie **
*******************************************************
*/
#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <time.h>
using namespace std;
// Funkcja czeka na dowolny klawisz i czyści ekran
//------------------------------------------------
void czekaj(void)
{
char c[1];
cout << "\nZapisz te dane\n\n";
cin.getline(c,1);
cin.getline(c,1);
for(int i = 1; i < 500; i++) cout << endl;
}
// Funkcja obliczająca NWD dla dwóch liczb
//----------------------------------------
int nwd(int a, int b)
{

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 474 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

int t;
while(b != 0)
{
t = b;
b = a % b;
a = t;
};
return a;
}
// Funkcja obliczania odwrotności modulo n
//----------------------------------------
int odwr_mod(int a, int n)
{
int a0,n0,p0,p1,q,r,t;
p0 = 0; p1 = 1; a0 = a; n0 = n;
q = n0 / a0;
r = n0 % a0;
while(r > 0)
{
t = p0 - q * p1;
if(t >= 0)
t = t % n;
else
t = n - ((-t) % n);
p0 = p1; p1 = t;
n0 = a0; a0 = r;
q = n0 / a0;
r = n0 % a0;
}
return p1;
}
// Procedura generowania kluczy RSA
//---------------------------------
void klucze_RSA()
{
const int tp[10] = {11,13,17,19,23,29,31,37,41,43};
int p,q,phi,n,e,d;
cout << "Generowanie kluczy RSA\n"
"----------------------\n\n";
// generujemy dwie różne, losowe liczby pierwsze
do
{
p = tp[rand() % 10];
q = tp[rand() % 10];
} while (p == q);
phi = (p - 1) * (q - 1);
n = p * q;
// wyznaczamy wykładniki e i d
for(e = 3; nwd(e,phi) != 1; e += 2);
d = odwr_mod(e,phi);
// gotowe, wypisujemy klucze
cout << "KLUCZ PUBLICZNY\n"
"wykladnik e = " << e
<< "\n modul n = " << n
<< "\n\nKLUCZ PRYWATNY\n"
"wykladnik d = " << d << endl;
czekaj();
}
// Funkcja oblicza modulo potęgę podanej liczby
//---------------------------------------------
int pot_mod(int a, int w, int n)
{
int pot,wyn,q;
// wykładnik w rozbieramy na sumę potęg 2
// przy pomocy algorytmu Hornera. Dla reszt
// niezerowych tworzymy iloczyn potęg a modulo n.
pot = a; wyn = 1;
for(q = w; q > 0; q /= 2)
{
if(q % 2) wyn = (wyn * pot) % n;
pot = (pot * pot) % n; // kolejna potęga
}
return wyn;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 475 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

// Procedura kodowania danych RSA


//-------------------------------
void kodowanie_RSA()
{
int e,n,t;
cout << "Kodowanie danych RSA\n"
"--------------------\n\n"
"Podaj wykladnik = "; cin >> e;
cout << " Podaj modul = "; cin >> n;
cout << "----------------------------------\n\n"
"Podaj kod RSA = "; cin >> t;
cout << "\nWynik kodowania = " << pot_mod(t,e,n) << endl;
czekaj();
}
// ********************
// ** Program główny **
// ********************
int main()
{
int w;
srand((unsigned)time(NULL));
do
{
cout << "System szyfrowania danych RSA\n"
"-----------------------------\n"
" (C)2012 mgr Jerzy Walaszek\n\n"
"MENU\n"
"====\n"
"[ 0 ] - Koniec pracy programu\n"
"[ 1 ] - Generowanie kluczy RSA\n"
"[ 2 ] - Kodowanie RSA\n\n"
"Jaki jest twoj wybor? (0, 1 lub 2) : ";
cin >> w;
cout << "\n\n\n";
switch (w)
{
case 1 : klucze_RSA(); break;
case 2 : kodowanie_RSA(); break;
}
cout << "\n\n\n";
} while(w != 0);
return 0;
}

Free Basic

/' *******************************************************
** Przykładowa aplikacja obrazująca sposób działania **
** asymetrycznego systemu kodowania informacji RSA. **
** ------------------------------------------------- **
** (C)2012 mgr Jerzy Wałaszek **
** I Liceum Ogólnokształcące **
** im. Kazimierza Brodzińskiego **
** w Tarnowie **
******************************************************* '/
Declare Sub klucze_RSA()
Declare Sub kodowanie_RSA()
Dim w As Integer
Randomize
Do
Print "System szyfrowania danych RSA"
Print "-----------------------------"
Print " (C)2012 mgr Jerzy Walaszek "
Print
Print "MENU"
Print "===="
Print "[ 0 ] - Koniec pracy programu"
Print "[ 1 ] - Generowanie kluczy RSA"
Print "[ 2 ] - Kodowanie RSA"
Print
Input "Jaki jest twoj wybor? (0, 1 lub 2) : ",w
Print: Print: Print
If w = 1 Then
klucze_RSA()
Elseif w = 2 Then
kodowanie_RSA()
End If
Print: Print: Print
Loop Until w = 0

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 476 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

End
' Procedura oczekuje na naciśnięcie klawisza Enter
' po czym czyści ekran okna konsoli
'-------------------------------------------------
Sub Czekaj()
Print
Print "Zapisz te dane i nacisnij Enter"
Getkey
Cls
End Sub
' Funkcja obliczająca NWD dla dwóch liczb
'----------------------------------------
Function nwd(Byval a As Integer, Byval b As Integer) As Integer
Dim t As Integer
While b
t = b
b = a Mod b
a = t
Wend
nwd = a
End Function
' Funkcja obliczania odwrotności modulo n
'----------------------------------------
Function odwr_mod(Byval a As Integer, Byval b As Integer) As Integer
Dim As Integer u,w,x,z,q
u = 1: w = a
x = 0: z = b
While w
If w < z Then
q = u: u = x: x = q
q = w: w = z: z = q
End If
q = w \ z
u = u - q * x
w = w - q * z
Wend
If x < 0 Then x += b
odwr_mod = x
End Function
' Procedura generowania kluczy RSA
'---------------------------------
Sub klucze_RSA()
Dim tp(9) As Integer = {11,13,17,19,23,29,31,37,41,43}
Dim As Integer p,q,phi,n,e,d
Print "Generowanie kluczy RSA"
Print "----------------------"
Print
' generujemy dwie różne, losowe liczby pierwsze
Do
p = tp(Int(Rnd * 10))
q = tp(Int(Rnd * 10))
Loop Until p <> q
phi = (p - 1) * (q - 1)
n = p * q
' wyznaczamy wykładniki e i d
e = 3
While nwd(e,phi) <> 1
e += 2
Wend
d = odwr_mod(e,phi)
' gotowe, wypisujemy klucze
Print "KLUCZ PUBLICZNY"
Print "wykladnik e = ";e
Print " modul n = ";n
Print
Print "KLUCZ PRYWATNY"
Print "wykladnik d = ";d
Czekaj()
End Sub
' Funkcja oblicza modulo potęgę podanej liczby
'---------------------------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 477 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

Function pot_mod(Byval a As Integer, Byval w As Integer, Byval n As Integer) As Integer


Dim As Integer pot,wyn,q
' wykładnik w rozbieramy na sumę potęg 2. Dla reszt
' niezerowych tworzymy iloczyn potęg a modulo n.
pot = a: wyn = 1: q = w
While q > 0
If (q Mod 2) = 1 Then wyn = (wyn * pot) Mod n
pot = (pot * pot) Mod n ' kolejna potęga
q = q \ 2
Wend
pot_mod = wyn
End Function
' Procedura kodowania danych RSA
'-------------------------------
Sub kodowanie_RSA()
Dim As Integer e,n,t
Print "Kodowanie danych RSA"
Print "--------------------"
Print
Input "Podaj wykladnik = "; e
Input " Podaj modul = "; n
Print "----------------------------------"
Print
Input "Podaj kod RSA = "; t
Print
Print "Wynik kodowania = "; pot_mod(t,e,n)
Czekaj()
End Sub

Wynik

System szyfrowania danych RSA Generowanie kluczy RSA


----------------------------- ----------------------
(C)2012 mgr Jerzy Wałaszek
KLUCZ PUBLICZNY
MENU wykładnik e = 3
==== moduł n = 391
[ 0 ] - Koniec pracy programu
[ 1 ] - Generowanie kluczy RSA KLUCZ PRYWATNY
[ 2 ] - Kodowanie RSA wykładnik d = 235
Jaki jest twój wybór? (0, 1 lub 2) : Zapisz te dane i naciśnij Enter

Kodowanie danych RSA Kodowanie danych RSA


-------------------- --------------------
Podaj wykładnik = 3 Podaj wykładnik = 235
Podaj moduł = 391 Podaj moduł = 391
---------------------------------- ----------------------------------
Podaj kod RSA = 123 Podaj kod RSA = 98
Wynik kodowania = 98 Wynik kodowania = 123
Zapisz te dane i naciśnij Enter Zapisz te dane i naciśnij Enter

Instrukcja obsługi skryptu RSA

Generacja kluczy W pierwszej części formularza wygeneruj parę kluczy: publiczny i prywatny. Zachowaj je i wyczyść klucze, aby nikt nie
mógł ich odczytać.
Szyfrowanie W drugiej części formularza wprowadź odpowiedni klucz (wykładnik oraz moduł), wiadomość i kliknij przycisk "koduj
Rozszyfrowywanie RSA". Wynik zostanie wyświetlony poniżej.

Generator kluczy RSA


(C)2012 mgr Jerzy Wałaszek I LO w Tarnowie
Generuj klucze Czyść klucze

Klucz publiczny Klucz prywatny


wykładnik ... wykładnik ...
moduł ... moduł ...

Koder szyfru RSA


(C)2012 mgr Jerzy Wałaszek I LO w Tarnowie

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 478 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

wykładnik

moduł

wiadomość

Koduj RSA Czyść formularz


...

Przykładowe zastosowania RSA


Bezpieczne połączenie internetowe
Sieć komputerowa Internet jest środowiskiem o niskim bezpieczeństwie poufności przesyłanych danych. Pakiety
danych podróżujące pomiędzy różnymi węzłami sieci mogą być podglądane przez osoby nieupoważnione.
Szyfrowanie danych zapewni nam bezpieczeństwo. Nawiązanie bezpiecznego połączenia wykorzystującego
szyfrowanie RSA składa się z następujących etapów:
1. Obie stacje generują zestaw kluczy RSA.
2. Stacje wymieniają się kluczami publicznymi, które posłużą do szyfrowania przesyłanych wiadomości.
Operacja ta jest bezpieczna, ponieważ klucze publiczne nie pozwalają odczytać zaszyfrowanych przy ich
pomocy wiadomości. Zatem przechwycenie klucza publicznego przez osobę nieupoważnioną nie da jej
żadnych korzyści.
3. Wysyłane wiadomości stacje szyfrują przy pomocy otrzymanego klucza publicznego.
4. Odebrane wiadomości stacje rozszyfrowują przy pomocy swojego klucza prywatnego, który nie był ujawniany.
Dzięki temu przechwycenie szyfrogramu w drodze do odbiorcy nie przyniesie osobie nieupoważnionej
żadnych korzyści.

Bezpieczne połączenia internetowe są dzisiaj szeroko wykorzystywane w sieci do prowadzenia działalności


handlowej. Dzięki nim klienci banków mogą bezpiecznie zarządzać swoimi kontami oraz dokonywać zakupów w
sieci z wykorzystaniem kart płatniczych.
Podpis cyfrowy
Załóżmy, iż stacja A chce wysłać do stacji B wiadomość W podpisaną cyfrowo.

1. W tym celu stacja A szyfruje wiadomość W za pomocą swojego klucza tajnego i szyfr dołącza do tej
wiadomości. Klucz tajny stacja A otrzymuje od instytucji zajmującej się przydzielaniem certyfikatów – jest to
tzw. podpis elektroniczny, który jednoznacznie identyfikuje nadawcę wiadomości. W efekcie nowa wiadomość
W' składa się z oryginalnej wiadomości W oraz jej zaszyfrowanej kopii.
2. W takiej postaci wiadomość W' zostaje przesłana do stacji B.
3. Stacja B rozszyfrowuje kopię kluczem publicznym stacji A. Klucz publiczny stacji A może być pobrany z
serwera instytucji przydzielającej certyfikaty lub otrzymany od stacji A i potwierdzony przez instytucję
przydzielającą certyfikaty. W ten sposób stacja B ma pewność, iż klucz publiczny na pewno dotyczy stacji A.
4. Stacja B porównuje obie części wiadomości W'. Jeśli są takie same, to oznacza to, iż pochodzą rzeczywiście
od stacji A.

Jeśli przesyłana wiadomość jest poufna, to do jej przekazania można dodatkowo wykorzystać bezpieczne
połączenie internetowe.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 479 / 519


Algorytmy i Struktury Danych - Algorytm RSA 2014-10-03

Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067.php 480 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Dodawanie dużych liczb


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

Problem
Dodać do siebie dwie dowolnie duże, dodatnie liczby całkowite, przedstawione w postaci łańcucha cyfr.

Problem rozwiążemy w sposób szkolny (profesjonalne algorytmy wymagają innego podejścia). Dodawane liczby musimy wyrównać do
ostatnich cyfr:

21638626396236623668969866232198
95832808595775579737342988203408934789797363

Dodawanie rozpoczniemy od ostatnich cyfr łańcuchów. Stosujemy przy tym poznane w szkole podstawowej zasady dodawania dwóch
liczb. Dodajemy ostatnie cyfry. W łańcuchu wynikowym umieszczamy ostatnią cyfrę wyniku. Natomiast pierwsza cyfra wyniku staje się
przeniesieniem do następnej pozycji:

10
21638626396236623668969866232198
+ 95832808595775579737342988203408934789797363
1

W następnym kroku dodajemy do siebie dwie kolejne cyfry oraz przeniesienie. Do łańcucha wynikowego wpisujemy na przedostatniej
pozycji ostatnią cyfrę wyniku, a pierwsza cyfra wyniku staje się przeniesieniem na dalszą pozycję.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 481 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

100
21638626396236623668969866232198
+ 95832808595775579737342988203408934789797363
61

Jeśli w jednym z łańcuchów skończą się zbyt wcześnie cyfry, to przyjmujemy, że posiada on resztę cyfr równych 0. Sprowadza się to
wtedy do dodawania przeniesień do pozostałych cyfr drugiego łańcucha. Gdy wszystkie cyfry zostaną przetworzone, a przeniesienie ma
wartość większą od 0, to umieszczamy je na początku łańcucha wynikowego jako pierwszą cyfrę wyniku.
Przy dodawaniu cyfr musimy pamiętać, że są one przechowywane w łańcuchach w postaci kodów ASCII:

Cyfra Kod ASCII


0 48
1 49
2 50
3 51
4 52
5 53
6 54
7 55
8 56
9 57

Dlatego w celu otrzymania wartości cyfry należy od jej kodu odjąć 48, a przy otrzymywaniu kodu znaku z wartości cyfry należy do niej
dodać 48.

Algorytm dodawania dwóch dowolnie dużych, nieujemnych liczb całkowitych


Wejście:
s1,s2 – dodawane łańcuchy z cyframi

Wyjście:
s3 – łańcuch wynikowy, który zawiera cyfry sumy

Elementy pomocnicze:
p – przeniesienie z poprzedniej pozycji, p Z
w – wynik dodawania, w N
i,j – indeksy w łańcuchach s1 i s2, i,j Z
k – licznik pętli, k Z
n – długość krótszego z łańcuchów s1 i s2, n Z
kod(x) – zwraca kod znaku x
znak(x) – zamienia kod x na odpowiadający mu znak ASCII
Lista kroków:
K01: i ← |s1| ; wyznaczamy długości łańcuchów
K02: j ← |s2|
K03: n ← i ; w n wyznaczamy długość krótszego z łańcuchów
K04: Jeśli j < i, to n ← j
K05: p ← 0 ; zerujemy przeniesienie
K06: s3 ← "" ; zerujemy łańcuch wyniku s3
K07: Dla k = 1,2,...n: wykonuj K08...K12 ; przebiegamy wstecz przez cyfry łańcuchów
K08: w ← kod(s1[i]) + kod(s2[j]) + p - 96 ; obliczamy sumę cyfr i przeniesienia. 96 = 2 x 48
K09: i ← i - 1 ; cofamy indeksy o 1 pozycję dla kolejnego obiegu
K10: j ← j - 1
K11: p ← w div 10 ; obliczamy przeniesienie do następnej pozycji
K12: s3 ← znak((w mod 10) + 48) + s3 ; cyfrę sumy dołączamy do wyniku
K13: Dopóki i > 0 wykonuj K14...K17 ; jeśli w s1 pozostały cyfry
K14: w ← kod(s1[i]) + p - 48 ; to dodajemy do nich tylko przeniesienia
K15: i ← i - 1
K16: p ← w div 10
K17: s3 ← znak((w mod 10) + 48) + s3
K18 Dopóki j > 0 wykonuj K19...K22 ; to samo dla s2

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 482 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

K19: w ← kod(s2[j]) + p - 48;


K20: j ← j - 1
K21: p ← w div 10
K22: s3 ← znak((w mod 10) + 48) + s3
K23 Jeśli p > 0, to s3 ← znak(p + 48) + s3 ; jeśli jest przeniesienie, to jest ono pierwszą cyfrą wyniku
K24: Jeśli s3 = "", to s3 = "0" ; jeśli wynik nie zawiera cyfr, to s1 i s2 były puste.
K25: Zakończ ; wynik dodawania w s3

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwie liczby o dowolnej ilości cyfr, dodaje je i wyświetla wynik. Program nie sprawdza poprawności
wprowadzonych liczb.

Lazarus

// Dodawanie dużych liczb


// Data: 07.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
program sumbignums;
var
s1,s2,s3 : ansistring;
p,w,i,j,k,n : integer;
begin
// odczytujemy liczby do dodawania
readln(s1);
readln(s2);
// obliczamy długości każdego z łańcuchów
i := length(s1);
j := length(s2);
// w n wyznaczamy długość najkrótszego łańcucha
n := i; if j < i then n := j;
// zerujemy przeniesienie oraz łańcuch wynikowy
p := 0;
s3 := '';
// sumujemy kolejne od końca cyfry obu łańcuchów
for k := 1 to n do
begin
w := ord(s1[i]) + ord(s2[j]) + p - 96;
dec(i); dec(j);
p := w div 10;
s3 := chr((w mod 10) + 48) + s3;
end;
// jeśli łańcuch s1 ma jeszcze cyfry, to dodajemy do nich
// przeniesienia i umieszczamy w łańcuchu wynikowym
while i > 0 do
begin
w := ord(s1[i]) + p - 48;
dec(i);
p := w div 10;
s3 := chr((w mod 10) + 48) + s3;
end;
// jeśli łańcuch s2 ma jeszcze cyfry, to dodajemy do nich
// przeniesienia i umieszczamy w łańcuchu wynikowym
while j > 0 do
begin

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 483 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

w := ord(s2[j]) + p - 48;
dec(j);
p := w div 10;
s3 := chr((w mod 10) + 48) + s3;
end;
// jeśli pozostało przeniesienie, to dołączamy je do cyfr
// w łańcuchu wynikowym
if p > 0 then s3 := chr(p + 48) + s3;
// jeśli w s3 nie ma cyfr, to wpisujemy tam 0
if s3 = '' then s3 := '0';
// wyświetlamy wynik
writeln(s3);
end.

Code::Blocks

// Dodawanie dużych liczb


// Data: 07.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1,s2,s3;
int p,w,i,j,k,n;
// odczytujemy liczby do dodawania
cin >> s1 >> s2;
// obliczamy długości każdego z łańcuchów
i = s1.length();
j = s2.length();
// w n wyznaczamy długość najkrótszego łańcucha
n = i; if(j < i) n = j;
// zerujemy przeniesienie oraz łańcuch wynikowy
p = 0;
s3 = "";
// sumujemy kolejne od końca cyfry obu łańcuchów
for(k = 1; k <= n; k++)
{
w = (int)(s1[--i]) + (int)(s2[--j]) + p - 96;
p = w / 10;
s3 = (char)((w % 10) + 48) + s3;
}
// jeśli łańcuch s1 ma jeszcze cyfry, to dodajemy do nich
// przeniesienia i umieszczamy w łańcuchu wynikowym
while(i)
{
w = s1[--i] + p - 48;
p = w / 10;
s3 = (char)((w % 10) + 48) + s3;
}
// jeśli łańcuch s2 ma jeszcze cyfry, to dodajemy do nich
// przeniesienia i umieszczamy w łańcuchu wynikowym
while(j)
{
w = s2[--j] + p - 48;
p = w / 10;
s3 = (char)((w % 10) + 48) + s3;
}
// jeśli pozostało przeniesienie, to dołączamy je do cyfr
// w łańcuchu wynikowym

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 484 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

if(p) s3 = (char)(p + 48) + s3;


// jeśli w s3 nie ma cyfr, to wpisujemy tam 0
if(s3 == "") s3 = "0";
// wyświetlamy wynik
cout << s3 << endl;
}

Free Basic

' Dodawanie dużych liczb


' Data: 07.10.2012
' (C)2012 mgr Jerzy Wałaszek
'---------------------------------------
Dim As String s1,s2,s3
Dim As Integer p,w,i,j,k,n
' odczytujemy liczby do dodawania
Line Input s1
Line Input s2
' obliczamy długości każdego z łańcuchów
i = Len(s1)
j = Len(s2)
' w n wyznaczamy długość najkrótszego łańcucha
n = i: If j < i Then n = j
' zerujemy przeniesienie oraz łańcuch wynikowy
p = 0
s3 = ""
' sumujemy kolejne od końca cyfry obu łańcuchów
For k = 1 To n
w = Asc(Mid(s1,i,1)) + Asc(Mid(s2,j,1)) + p - 96
i -= 1: j -= 1
p = w \ 10
s3 = Chr((w Mod 10) + 48) + s3
Next
' jeśli łańcuch s1 ma jeszcze cyfry, to dodajemy do nich
' przeniesienia i umieszczamy w łańcuchu wynikowym
While i > 0
w = Asc(Mid(s1,i,1)) + p - 48
i -= 1
p = w \ 10
s3 = Chr((w Mod 10) + 48) + s3
Wend
' jeśli łańcuch s2 ma jeszcze cyfry, to dodajemy do nich
' przeniesienia i umieszczamy w łańcuchu wynikowym
While j > 0
w = Asc(Mid(s2,j,1)) + p - 48
j -= 1
p = w \ 10
s3 = Chr((w Mod 10) + 48) + s3
Wend
' jeśli pozostało przeniesienie, to dołączamy je do cyfr
' w łańcuchu wynikowym
If p > 0 Then s3 = Chr(p + 48) + s3
' jeśli w s3 nie ma cyfr, to wpisujemy tam 0
If s3 = "" Then s3 = "0"
' wyświetlamy wynik
Print s3
End

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 485 / 519


Algorytmy i Struktury Danych - Dodawanie dużych liczb 2014-10-03

Wynik
481084081308409834234234008219934109784217102740921707907072414421
892719749217498721847921749827210740217402147210740210472107402149
1373803830525908556082155758047144850001619249951661918379179816570

Dodawanie dużych liczb dodatnich


(C)2012 mgr Jerzy Wałaszek

481084081308409834234234008219934109784217102740921707907072414421
892719749217498721847921749827210740217402147210740210472107402149

Dodaj

Zadanie
Powyższy algorytm nie usuwa zer wiodących. Suma 0001 i 2 da wynik 0003. Zastanów się, jak usunąć z wyniku te zera wiodące. W
którym miejscu algorytmu najlepiej to zrobić?

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na
prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też
tłumaczenia zagadnień szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067a.php 486 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Mnożenie dużych liczb


Tematy pokrewne Podrozdziały
Łańcuchy znakowe Problem 1
Podstawowe pojęcia dotyczące przetwarzania tekstów Problem 2
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

Problem 1
Pomnożyć dowolnie dużą nieujemną liczbę całkowitą przez nieujemną liczbę całkowitą względnie małą, np. 32
bitową.

Naszym zadaniem jest znalezienie iloczynu:

gdzie: a – liczba duża, b – liczba mała

Liczba mała b rozkłada się na sumę potęg liczby 2 mnożonych przez kolejne bity bi liczby b:

Teraz możemy zapisać:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 487 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

Na pierwszy rzut oka otrzymany wzór wydaje się mało przydatny. Jednakże pozory mylą. Zapiszmy to nieco inaczej:

W powyższym wzorze bi to i-ty bit mniejszej liczby. Natomiast kolejne iloczyny 2ia bardzo łatwo oblicza się dynamicznie za pomocą
dodawania, ponieważ, co łatwo zauważyć, każdy kolejny iloczyn jest dwa razy większy od poprzedniego.:

Iloczyny dodajemy do wyniku W, jeśli odpowiedni bit bi jest równy 1. W całym tym postępowaniu wykonywane są tylko dodawania
dużych liczb, które zostały opisane w poprzednim rozdziale. Bity z liczby b możemy łatwo wydzielać za pomocą operacji koniunkcji i
przesuwów

Algorytm mnożenia dowolnie dużej liczby nieujemnej przez małą liczbę nieujemną
Wejście:
a – duża liczba jako łańcuch znakowy
b – mała liczba, b N

Wyjście:
w – łańcuch wynikowy, który zawiera cyfry iloczynu ab
a – zawartość nieokreślona
b – zawiera zero
Elementy pomocnicze:
dodaj(x,y) – dodaje dwie duże liczby x i y jako łańcuchy i zwraca wynik jako łańcuch
Lista kroków:
K01: w ← "0" ; zerujemy wynik
K02: Jeśli (b and 1) = 1, to w ← dodaj(w,a) ; jeśli bit bi = 1, to dodaj ai do w
K03: b ← b shr 1 ; przesuń bity w b o jedną pozycję w prawo
K04: Jeśli b = 0, to zakończ ; reszta bitów jest zerowa, kończymy
K05: a ← dodaj(a,a) ; oblicz kolejne ai
K06: Idź do K02 ; kontynuuj pętlę

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwie liczby nieujemne. Pierwsza o dowolnej ilości cyfr. Druga w zakresie od 0 do 4294967295. Oblicza
ich iloczyn i wypisuje wynik.

Lazarus

// Mnożenie dużej liczby przez małą


// Data: 23.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 488 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

program mulbigsmall;
// Oblicza sumę podanych liczb
//----------------------------
function dodaj(var x,y : ansistring) : ansistring;
var
z : ansistring;
p,w,i,j,k,n : integer;
begin
i := length(x);
j := length(y);
n := i; if j < i then n := j;
p := 0;
z := '';
for k := 1 to n do
begin
w := ord(x[i]) + ord(y[j]) + p - 96;
dec(i); dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while i > 0 do
begin
w := ord(x[i]) + p - 48;
dec(i);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while j > 0 do
begin
w := ord(y[j]) + p - 48;
dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
if p > 0 then z := chr(p + 48) + z;
if z = '' then z := '0';
dodaj := z; // zwracamy wynik dodawania
end;
//********************
//** PROGRAM GŁÓWNY **
//********************
var
a,w : ansistring;
b : dword;
begin
// odczytujemy liczby do mnożenia
readln(a);
readln(b);
w := '0'; // zerujemy łańcuch wyjściowy
while true do // wykonujemy mnożenie
begin
if (b and 1) = 1 then w := dodaj(w,a);
b := b shr 1;
if b = 0 then break;
a := dodaj(a,a);
end;
writeln(w); // wyświetlamy wynik
end.

Code::Blocks

// Mnożenie dużej liczby przez małą


// Data: 23.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 489 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

#include <iostream>
#include <string>
using namespace std;
// Oblicza sumę podanych liczb
//----------------------------
string dodaj(string & x, string & y)
{
string z;
int p,w,i,j,k,n;
i = x.length();
j = y.length();
n = i; if(j < i) n = j;
p = 0;
z = "";
for(k = 1; k <= n; k++)
{
w = (int)(x[--i]) + (int)(y[--j]) + p - 96;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(i)
{
w = x[--i] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(j)
{
w = y[--j] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
if(p) z = (char)(p + 48) + z;
if(z == "") z = "0";
return z; // zwracamy wynik dodawania
}
//********************
//** PROGRAM GŁÓWNY **
//********************
int main()
{
string a,w;
unsigned int b;
// odczytujemy liczby do mnożenia
cin >> a >> b;
w = "0"; // zerujemy łańcuch wyjściowy
while(true) // wykonujemy mnożenie
{
if(b & 1) w = dodaj(w,a);
if(b >>= 1) a = dodaj(a,a); else break;
}
cout << w << endl; // wyświetlamy wynik
return 0;
}

Free Basic

' Mnożenie dużej liczby przez małą


' Data: 23.10.2012
' (C)2012 mgr Jerzy Wałaszek
'---------------------------------------
' Oblicza sumę podanych liczb
'----------------------------
Function dodaj(Byref x As String, Byref y As String) As String

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 490 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

Dim As String z
Dim As Integer p,w,i,j,k,n
i = Len(x)
j = Len(y)
n = i: If j < i Then n = j
p = 0
z = ""
For k = 1 To n
w = Asc(Mid(x,i,1)) + Asc(Mid(y,j,1)) + p - 96
i -= 1: j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Next
While i > 0
w = Asc(Mid(x,i,1)) + p - 48
i -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
While j > 0
w = Asc(Mid(y,j,1)) + p - 48
j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
If p > 0 Then z = Chr(p + 48) + z
If z = "" Then z = "0"
dodaj = z
End Function
'********************
'** PROGRAM GŁÓWNY **
'********************
Dim As String a,w
Dim As Uinteger b
' odczytujemy liczby do mnożenia
Open Cons For Input As #1
Line Input a
Input #1,b
Close #1
w = "0" ' zerujemy łańcuch wyjściowy
While 1 ' wykonujemy mnożenie
If (b And 1) = 1 Then w = dodaj(w,a)
b Shr= 1
If b = 0 Then Exit While
a = dodaj(a,a)
Wend
Print w ' wyświetlamy wynik
End

Wynik
1234567890123456789012345678901234567890
555
685185179018518517901851851790185185178950

Mnożenie dużej liczby przez małą


(C)2012 mgr Jerzy Wałaszek
Duża liczba →
1234567890123456789012345678901234567890
Mała liczba → 555

Mnóż

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 491 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

Problem 2
Obliczyć wartość iloczynu dwóch dowolnie dużych nieujemnych liczb całkowitych.

Postąpimy zgodnie z algorytmem "szkolnym" (profesjonalne algorytmy wymagają innego podejścia). Liczby zapisujemy jedna nad drugą
– umówmy się, że dłuższą liczbę zapisujemy u góry, a krótszą na dole:

95832808595775579737342988203408934789797363
x 21638626396236623668969866232198

Następnie tworzymy kolejne iloczyny częściowe górnej liczby przez cyfry liczby dolnej. Iloczyny te są przesunięte w lewo zgodnie z
pozycją mnożącej cyfry:

95832808595775579737342988203408934789797363
x 21638626396236623668969866232198
766662468766204637898743905627271478318378904
8624952773619802176360868938306804131081762670
9583280859577557973734298820340893478979736300
191665617191551159474685976406817869579594726000
... ... ... ... ... ... ...
... ... ... ... ... ... ...
... ... ... ... ... ... ...
57499685157465347842405792922045360873878417800000000000000000000000000000
95832808595775579737342988203408934789797363000000000000000000000000000000
+ 1916656171915511594746859764068178695795947260000000000000000000000000000000
2073690341706041464574292083419814736706959241205382993813776933584726093874

Otrzymane iloczyny dodajemy do siebie i otrzymujemy wynik mnożenia. Do wyznaczania iloczynów częściowych możemy wykorzystać
podany wcześniej algorytm mnożenia dużej liczby (pierwszej) przez małą (cyfrę drugiej). Do otrzymanego wyniku należy na końcu
dołączać odpowiednią ilość zer (przesunięcie wg mnożonej cyfry).

Algorytm mnożenia dwóch dowolnie dużych nieujemnych liczb całkowitych


Wejście:
a,b – liczby podane w postaci łańcuchów znakowych

Wyjście:
w – łańcuch wynikowy, który zawiera cyfry iloczynu ab
Elementy pomocnicze:
c – łańcuch pomocniczy dla iloczynu częściowego
z – łańcuch zawierający dodawane zera na końcu iloczynów częściowych
n – zawiera liczbę cyfr w b, n N
i – wskazuje pozycję cyfr w b przez które mnożymy a, i N
dodaj(x,y) – dodaje dwie duże liczby x i y jako łańcuchy i zwraca wynik jako łańcuch
mnóż(x,y) – zwraca łańcuch z iloczynem liczby dużej x jako łańcucha znakowego i liczby małej y, y N
kod(x) – zwraca kod znaku x.

Lista kroków:
K01: w ← "0" ; zerujemy wynik
K02: z ← "" ; będzie zawierać dodawane zera
K03: n ← |b| ; wyznaczamy liczbę cyfr w b
K04: Dla i = n, n-1,...,1: wykonuj K05...K08 ; przetwarzamy cyfry w b
K05: c ← mnóż(a,kod(b[i]) - 48) ; obliczamy iloczyn a przez cyfrę z b
K06: c ← c + z ; dołączamy zera w celu przesunięcia tego iloczynu
K07: w ← dodaj(w,c) ; iloczyn dodajemy do wyniku
K08: z ← z + "0" ; zwiększamy liczbę zer dla następnego obiegu
K09: Zakończ

Program

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 492 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwie dowolnie duże liczby nieujemne, oblicza ich iloczyn i wypisuje wynik.

Lazarus

// Mnożenie dużych liczb


// Data: 23.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
program mulbignum;
// Oblicza sumę podanych liczb
//----------------------------
function dodaj(var x,y : ansistring) : ansistring;
var
z : ansistring;
p,w,i,j,k,n : integer;
begin
i := length(x);
j := length(y);
n := i; if j < i then n := j;
p := 0;
z := '';
for k := 1 to n do
begin
w := ord(x[i]) + ord(y[j]) + p - 96;
dec(i); dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while i > 0 do
begin
w := ord(x[i]) + p - 48;
dec(i);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while j > 0 do
begin
w := ord(y[j]) + p - 48;
dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
if p > 0 then z := chr(p + 48) + z;
if z = '' then z := '0';
dodaj := z; // zwracamy wynik dodawania
end;
// Mnoży dużą liczbę a przez małą b
//---------------------------------
function mnoz(a : ansistring; b : dword) : ansistring;
var
w : ansistring;
begin
w := '0'; // zerujemy łańcuch wyjściowy
while true do // wykonujemy mnożenie
begin
if (b and 1) = 1 then w := dodaj(w,a);
b := b shr 1;
if b = 0 then break;
a := dodaj(a,a);

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 493 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

end;
mnoz := w; // zwracamy wynik mnożenia
end;
//********************
//** PROGRAM GŁÓWNY **
//********************
var
a,b,c,w,z : ansistring;
i : dword;
begin
// odczytujemy liczby do mnożenia
readln(a);
readln(b);
// mnożymy
w := '0';
z := '';
for i := length(b) downto 1 do
begin
c := mnoz(a,ord(b[i])-48) + z;
w := dodaj(w,c);
z := z + '0';
end;
// wypisujemy wyniki
writeln(w);
end.

Code::Blocks

// Mnożenie dużych liczb


// Data: 23.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
#include <iostream>
#include <string>
using namespace std;
// Oblicza sumę podanych liczb
//----------------------------
string dodaj(string & x, string & y)
{
string z;
int p,w,i,j,k,n;
i = x.length();
j = y.length();
n = i; if(j < i) n = j;
p = 0;
z = "";
for(k = 1; k <= n; k++)
{
w = (int)(x[--i]) + (int)(y[--j]) + p - 96;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(i)
{
w = x[--i] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(j)
{
w = y[--j] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 494 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

if(p) z = (char)(p + 48) + z;


if(z == "") z = "0";
return z; // zwracamy wynik dodawania
}
// Mnoży dużą liczbę a przez małą b
//---------------------------------
string mnoz(string a, unsigned int b)
{
string w;
w = "0"; // zerujemy łańcuch wyjściowy
while(true) // wykonujemy mnożenie
{
if(b & 1) w = dodaj(w,a);
if(b >>= 1) a = dodaj(a,a); else break;
}
return w; // zwracamy wynik mnożenia
}
//********************
//** PROGRAM GŁÓWNY **
//********************
int main()
{
string a,b,c,w,z;
int i;
// odczytujemy liczby do mnożenia
cin >> a >> b;
// mnożymy
w = "0";
z = "";
for(i = b.length()-1; i >= 0; i--)
{
c = mnoz(a,b[i] - 48) + z;
w = dodaj(w,c);
z += "0";
}
// wypisujemy wyniki
cout << w << endl;
return 0;
}

Free Basic

' Mnożenie dużych liczb


' Data: 23.10.2012
' (C)2012 mgr Jerzy Wałaszek
'---------------------------------------
' Oblicza sumę podanych liczb
'----------------------------
Function dodaj(Byref x As String, Byref y As String) As String
Dim As String z
Dim As Integer p,w,i,j,k,n
i = Len(x)
j = Len(y)
n = i: If j < i Then n = j
p = 0
z = ""
For k = 1 To n
w = Asc(Mid(x,i,1)) + Asc(Mid(y,j,1)) + p - 96
i -= 1: j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 495 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

While i > 0
w = Asc(Mid(x,i,1)) + p - 48
i -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
While j > 0
w = Asc(Mid(y,j,1)) + p - 48
j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
If p > 0 Then z = Chr(p + 48) + z
If z = "" Then z = "0"
dodaj = z
End Function
' Mnoży dużą liczbę a przez małą b
'---------------------------------
Function mnoz(Byref x As String, Byval b As Uinteger) As String
Dim As String a,w
a = x ' tworzymy kopię roboczą łańcucha
w = "0" ' zerujemy łańcuch wyjściowy
While 1 ' wykonujemy mnożenie
If (b And 1) = 1 Then w = dodaj(w,a)
b Shr= 1
If b = 0 Then Exit While
a = dodaj(a,a)
Wend
mnoz = w ' zwracamy wynik mnożenia
End Function
'********************
'** PROGRAM GŁÓWNY **
'********************
Dim As String a,b,c,w,z
Dim As Uinteger i
' odczytujemy liczby do mnożenia
Line Input a
Line Input b
' mnożymy
w = "0"
z = ""
For i = Len(b) To 1 Step -1
c = mnoz(a,Asc(Mid(b,i,1)) - 48) + z
w = dodaj(w,c)
z += "0"
Next
' wypisujemy wyniki
Print w
End

Wynik
95832808595775579737342988203408934789797363
21638626396236623668969866232198
2073690341706041464574292083419814736706959241205382993813776933584726093874

Mnożenie dużych liczb


(C)2012 mgr Jerzy Wałaszek

95832808595775579737342988203408934789797363

21638626396236623668969866232198

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 496 / 519


Algorytmy i Struktury Danych - Mnożenie dużych liczb 2014-10-03

Mnóż
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na
prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też
tłumaczenia zagadnień szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067b.php 497 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Potęgowanie dużych liczb


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

Problem
Obliczyć xy, gdzie x jest dowolnie dużą nieujemną liczbą całkowitą, a y jest względnie małą nieujemną liczbą
całkowitą, np. 32-bitową.

Normalnie potęgowanie an całkowite wymaga wykonania n-1 mnożeń przez a:

Mnożenie dużych liczb jest bardzo kosztowne czasowo. Istnieje zatem naturalne pytanie, czy takiej operacji potęgowania nie można
wykonać przy mniejszej liczbie mnożeń. Okazuje się, że tak – postąpimy podobnie jak przy mnożeniu – przedstawimy potęgę w postaci
sumy potęg liczby 2. Wykorzystamy prostą własność potęgowania:

Niech:

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 498 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

gdzie bi – i-ty bit liczby b, i = 0,1,...,31 dla liczb 32-bitowych.

Jeśli dokładnie się przyjrzysz ostatniemu wzorowi, to powinieneś spostrzec, że zbudowany on jest z iloczynu wyrażeń:

Kolejne wartości potęg w prosty sposób obliczamy dynamicznie:

Bity bi wydzielamy z b za pomocą koniunkcji oraz przesuwu w prawo w identyczny sposób jak przy mnożeniu.
Możemy już przystąpić do konstrukcji algorytmu potęgującego.

Algorytm potęgowania dowolnie dużej liczby nieujemnej przez małą liczbę nieujemną
Wejście:
a – duża liczba jako łańcuch znakowy
b – mała liczba, b N

Wyjście:

w – łańcuch wynikowy, który zawiera cyfry potęgi ab


a – zawartość nieokreślona
b – zawiera zero
Elementy pomocnicze:
mnóż(x,y) – mnoży dwie duże liczby jako łańcuchy i zwraca wynik jako łańcuch
Lista kroków:
K01: w ← "1" ; ustawiamy wynik na 1
K02: Jeśli (b and 1) = 1, to w ← mnóż(w,a) ; jeśli bit bi = 1, to wymnóż w przez ai
K03: b ← b shr 1 ; przesuń bity w b o jedną pozycję w prawo
K04: Jeśli b = 0, to zakończ ; reszta bitów jest zerowa, kończymy
K05: a ← mnóż(a,a) ; oblicz kolejne ai
K06: Idź do K02 ; kontynuuj pętlę

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje dwie liczby nieujemne a i b. Pierwsza o dowolnej ilości cyfr. Druga w zakresie od 0 do 4294967295.
Oblicza ab i wypisuje wynik.
Uwaga: potęgi dużych liczb mogą wymagać bardzo długiego czasu obliczeń, a wynik nie będzie się mieścił w oknie konsoli.

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 499 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

Lazarus

// Potęgowanie dużej liczby przez małą


// Data: 24.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
program bigpower;
// Oblicza sumę podanych liczb
//----------------------------
function dodaj(var x,y : ansistring) : ansistring;
var
z : ansistring;
p,w,i,j,k,n : integer;
begin
i := length(x);
j := length(y);
n := i; if j < i then n := j;
p := 0;
z := '';
for k := 1 to n do
begin
w := ord(x[i]) + ord(y[j]) + p - 96;
dec(i); dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while i > 0 do
begin
w := ord(x[i]) + p - 48;
dec(i);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while j > 0 do
begin
w := ord(y[j]) + p - 48;
dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
if p > 0 then z := chr(p + 48) + z;
if z = '' then z := '0';
dodaj := z; // zwracamy wynik dodawania
end;
// Mnoży dużą liczbę a przez małą b
//---------------------------------
function mnoz_ab(a : ansistring; b : dword) : ansistring;
var
w : ansistring;
begin
w := '0'; // zerujemy łańcuch wyjściowy
while true do // wykonujemy mnożenie
begin
if (b and 1) = 1 then w := dodaj(w,a);
b := b shr 1;
if b = 0 then break;
a := dodaj(a,a);
end;
mnoz_ab := w; // zwracamy wynik mnożenia
end;
// Mnoży dwie duże liczby
//-----------------------
function mnoz(var a,b : ansistring) : ansistring;
var
c,w,z : ansistring;
i : dword;
begin
// mnożymy

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 500 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

w := '0';
z := '';
for i := length(b) downto 1 do
begin
c := mnoz_ab(a,ord(b[i])-48) + z;
w := dodaj(w,c);
z := z + '0';
end;
mnoz := w; // zwracamy wynik mnożenia
end;
//********************
//** PROGRAM GŁÓWNY **
//********************
var
a,w : ansistring;
b : dword;
begin
// odczytujemy dane
readln(a); // duża liczba
readln(b); // mała liczba
// potęgujemy
w := '1';
while true do
begin
if (b and 1) = 1 then w := mnoz(w,a);
b := b shr 1;
if b <> 0 then a := mnoz(a,a) else break;
end;
// wypisujemy wynik
writeln(w);
end.

Code::Blocks

// Potęgowanie dużej liczby przez małą


// Data: 24.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
#include <iostream>
#include <string>
using namespace std;
// Oblicza sumę podanych liczb
//----------------------------
string dodaj(string & x, string & y)
{
string z;
int p,w,i,j,k,n;
i = x.length();
j = y.length();
n = i; if(j < i) n = j;
p = 0;
z = "";
for(k = 1; k <= n; k++)
{
w = (int)(x[--i]) + (int)(y[--j]) + p - 96;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(i)
{
w = x[--i] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 501 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

}
while(j)
{
w = y[--j] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
if(p) z = (char)(p + 48) + z;
if(z == "") z = "0";
return z; // zwracamy wynik dodawania
}
// Mnoży dużą liczbę a przez małą b
//---------------------------------
string mnoz_ab(string a, unsigned int b)
{
string w;
w = "0"; // zerujemy łańcuch wyjściowy
while(true) // wykonujemy mnożenie
{
if(b & 1) w = dodaj(w,a);
if(b >>= 1) a = dodaj(a,a); else break;
}
return w; // zwracamy wynik mnożenia
}
// Mnoży dwie duże liczby
//-----------------------
string mnoz(string & a, string & b)
{
string c,w,z;
int i;
// mnożymy
w = "0";
z = "";
for(i = b.length()-1; i >= 0; i--)
{
c = mnoz_ab(a,b[i]-48) + z;
w = dodaj(w,c);
z = z + "0";
}
return w; // zwracamy wynik mnożenia
}
//********************
//** PROGRAM GŁÓWNY **
//********************
int main()
{
string a,w;
unsigned int b;
// odczytujemy dane
cin >> a; // duża liczba
cin >> b; // mała liczba
// potęgujemy
w = "1";
while(true)
{
if(b & 1) w = mnoz(w,a);
if(b >>= 1) a = mnoz(a,a); else break;
}
// wypisujemy wynik
cout << w << endl;
}

Free Basic

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 502 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

' Potęgowanie dużej liczby przez małą


' Data: 24.10.2012
' (C)2012 mgr Jerzy Wałaszek
'---------------------------------------
' Oblicza sumę podanych liczb
'----------------------------
Function dodaj(Byref x As String, Byref y As String) As String
Dim As String z
Dim As Integer p,w,i,j,k,n
i = Len(x)
j = Len(y)
n = i: If j < i Then n = j
p = 0
z = ""
For k = 1 To n
w = Asc(Mid(x,i,1)) + Asc(Mid(y,j,1)) + p - 96
i -= 1: j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Next
While i > 0
w = Asc(Mid(x,i,1)) + p - 48
i -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
While j > 0
w = Asc(Mid(y,j,1)) + p - 48
j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
If p > 0 Then z = Chr(p + 48) + z
If z = "" Then z = "0"
dodaj = z
End Function
' Mnoży dużą liczbę a przez małą b
'---------------------------------
Function mnoz_ab(Byref x As String, Byval b As Uinteger) As String
Dim As String a,w
a = x
w = "0" ' zerujemy łańcuch wyjściowy
While 1 ' wykonujemy mnożenie
If (b And 1) = 1 Then w = dodaj(w,a)
b Shr= 1
If b = 0 Then Exit While
a = dodaj(a,a)
Wend
mnoz_ab = w ' zwracamy wynik mnożenia
End Function
' Mnoży dwie duże liczby
'-----------------------
Function mnoz(Byref a As String, Byref b As String) As String
Dim As String c,w,z
Dim As Uinteger i
' mnożymy
w = "0"
z = ""
For i = Len(b) To 1 Step -1
c = mnoz_ab(a,Asc(Mid(b,i,1)) - 48) + z
w = dodaj(w,c)
z += "0"
Next

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 503 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

mnoz = w
End Function
'********************
'** PROGRAM GŁÓWNY **
'********************
Dim As String a,w
Dim As Uinteger b
' odczytujemy dane
Open Cons For Input As #1
Line Input a ' duża liczba
Input #1,b ' mała liczba
Close #1
' potęgujemy
w = "1"
While 1
If (b And 1) = 1 Then w = mnoz(w,a)
b Shr= 1
If b = 0 Then Exit While
a = mnoz(a,a)
Wend
' wypisujemy wynik
Print w
End

Wynik
123456
123
18030210630404480750814092786593857280734268863855968048844015985795850236081373
25021978269698632257308716304364197947589320743503803676976498146265429266026647
07275874269201777743912313197516323690221274713845895457748735309484337191373255
52792827178520638296799898433048210535094222997067705494083821093695230393940165
67561276077785996672437028140727462194319422930054164116350760212960454933051336
45615566590735965652587934290425473827719935012870093575987789431818047013404691
79577317040576461464605494929884618467829681362559533331161138525173524450544844
3050050547161779229749134489643622579100908331839817426366854332416

Potęga dużej liczby przez małą


(C)2012 mgr Jerzy Wałaszek
Duża liczba →
123456
Mała liczba → 123

Potęguj
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)

Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 504 / 519


Algorytmy i Struktury Danych - Potęgowanie dużych liczb 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067c.php 505 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Duże liczby Fibonacciego


Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie

Problem
Oblicz n-tą liczbę Fibonacciego, gdzie n > 100.

Wykorzystując arytmetykę dużych liczb, zadanie jest dosyć łatwe, jeśli n nie jest specjalnie duże. Liczby Fibonacciego obliczamy
dynamicznie:

f0 = 0
f1 = 1
f2 = f0 + f1
f3 = f1 + f2
...
fn = fn-2 + fn-1

Z powyższego wzoru wynika, że program powinien pamiętać dwie ostatnie liczby Fibonacciego, aby policzyć kolejną.

Algorytm obliczania dużych liczb Fibonacciego


Wejście:
n – numer liczby Fibonacciego do wyznaczenia, n N

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 506 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

Wyjście:
f - łańcuch znakowy zawierający kolejne cyfry n-tej liczby Fibonacciego
Elementy pomocnicze:
f0, f1 – dwie poprzednie liczby Fibonacciego jako łańcuchy znakowe
i – zlicza obiegi pętli
dodaj(x,y) – dodaje dwie duże liczby jako łańcuchy i zwraca wynik jako łańcuch

Lista kroków:
K01: Jeśli n = 0, to f ← "0" i zakończ ; dwie pierwsze wartości zwracamy bezpośrednio
K02: Jeśli n = 1, to f ← "1" i zakończ
K03: f0 ← "0" ; ustawiamy dwie początkowe liczby Fibonacciego
K04: f1 ← "1"
K05: Dla i = 2,3,...,n wykonuj K06...K08 ; w pętli liczymy kolejne liczby Fibonacciego
K06: f = dodaj(f0,f1) ; jako sumę dwóch poprzednich
K07: f0 ← f1 ; przygotowujemy dwie poprzednie liczby
K08: f1 ← f ; dla następnego obiegu
K09: Zakończ

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program odczytuje liczbę n i wylicza n-tą liczbę Fibonacciego.

Lazarus

// Duże liczby Fibonacciego


// Data: 25.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
program bigfibo;
// Oblicza sumę podanych liczb
//----------------------------
function dodaj(var x,y : ansistring) : ansistring;
var
z : ansistring;
p,w,i,j,k,n : integer;
begin
i := length(x);
j := length(y);
n := i; if j < i then n := j;
p := 0;
z := '';
for k := 1 to n do
begin
w := ord(x[i]) + ord(y[j]) + p - 96;
dec(i); dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while i > 0 do
begin
w := ord(x[i]) + p - 48;
dec(i);
p := w div 10;
z := chr((w mod 10) + 48) + z;
end;
while j > 0 do
begin
w := ord(y[j]) + p - 48;
dec(j);
p := w div 10;
z := chr((w mod 10) + 48) + z;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 507 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

end;
if p > 0 then z := chr(p + 48) + z;
if z = '' then z := '0';
dodaj := z; // zwracamy wynik dodawania
end;
//********************
//** PROGRAM GŁÓWNY **
//********************
var
f0,f1,f : ansistring;
i,n : dword;
begin
// odczytujemy n
readln(n);
// obliczamy fn
if n = 0 then f := '0'
else if n = 1 then f := '1'
else
begin
f0 := '0';
f1 := '1';
for i := 2 to n do
begin
f := dodaj(f0,f1);
f0 := f1;
f1 := f;
end;
end;
// wyświetlamy wynik
writeln(f);
end.

Code::Blocks

// Duże liczby Fibonacciego


// Data: 25.10.2012
// (C)2012 mgr Jerzy Wałaszek
//---------------------------------------
#include <iostream>
#include <string>
using namespace std;
// Oblicza sumę podanych liczb
//----------------------------
string dodaj(string & x, string & y)
{
string z;
int p,w,i,j,k,n;
i = x.length();
j = y.length();
n = i; if(j < i) n = j;
p = 0;
z = "";
for(k = 1; k <= n; k++)
{
w = (int)(x[--i]) + (int)(y[--j]) + p - 96;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
while(i)
{
w = x[--i] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 508 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

while(j)
{
w = y[--j] + p - 48;
p = w / 10;
z = (char)((w % 10) + 48) + z;
}
if(p) z = (char)(p + 48) + z;
if(z == "") z = "0";
return z; // zwracamy wynik dodawania
}
//********************
//** PROGRAM GŁÓWNY **
//********************
int main()
{
string f0,f1,f;
unsigned int i,n;
// odczytujemy n
cin >> n;
// obliczamy fn
if(!n) f = "0";
else if(n == 1) f = "1";
else
{
f0 = "0";
f1 = "1";
for(i = 2; i <= n; i++)
{
f = dodaj(f0,f1);
f0 = f1;
f1 = f;
}
}
// wyświetlamy wynik
cout << f << endl;
return 0;
}

Free Basic

' Duże liczby Fibonacciego


' Data: 25.10.2012
' (C)2012 mgr Jerzy Wałaszek
'---------------------------------------
' Oblicza sumę podanych liczb
'----------------------------
Function dodaj(Byref x As String, Byref y As String) As String
Dim As String z
Dim As Integer p,w,i,j,k,n
i = Len(x)
j = Len(y)
n = i: If j < i Then n = j
p = 0
z = ""
For k = 1 To n
w = Asc(Mid(x,i,1)) + Asc(Mid(y,j,1)) + p - 96
i -= 1: j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Next
While i > 0
w = Asc(Mid(x,i,1)) + p - 48
i -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 509 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

While j > 0
w = Asc(Mid(y,j,1)) + p - 48
j -= 1
p = w \ 10
z = Chr((w Mod 10) + 48) + z
Wend
If p > 0 Then z = Chr(p + 48) + z
If z = "" Then z = "0"
dodaj = z
End Function
'********************
'** PROGRAM GŁÓWNY **
'********************
Dim As String f0,f1,f
Dim As Uinteger i,n
' odczytujemy n
Open Cons For Input As #1
Input #1,n
Close #1
' obliczamy fn
If n = 0 Then
f = "0"
Elseif n = 1 Then
f = "1"
Else
f0 = "0"
f1 = "1"
For i = 2 To n
f = dodaj(f0,f1)
f0 = f1
f1 = f
Next
End If
' wyświetlamy wynik
Print f
End

Wynik
2000
42246963333923048787067256023414827825798528402506810980102801373143085843701307
07224123599639141511088446087538909603607640194711643596029271983312598737326253
55580260699158591522949245390499872225679531698287448247299226390183371677806060
70116154978867198798583114688708762645973690867228840236544222952433479644801395
15349562972087652656069529806499841977448720155612802665404554171717881930324025
204312082516817125

Duże liczby Fibonacciego


(C)2012 mgr Jerzy Wałaszek

n= 2000

Olblicz
.

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 510 / 519


Algorytmy i Struktury Danych - Duże liczby Fibonacciego 2014-10-03

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067d.php 511 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

©2014 mgr Jerzy Wałaszek


I LO w Tarnowie

Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych.


Autor artykułu: mgr Jerzy Wałaszek, wersja 2.0

Haszowanie
Tematy pokrewne
Łańcuchy znakowe
Podstawowe pojęcia dotyczące przetwarzania tekstów
Podstawowe operacje na łańcuchach znakowych
Naiwne wyszukiwanie wzorca w tekście
Wyszukiwanie maksymalnego prefikso-sufiksu
Szybkie wyszukiwanie wzorca algorytmem Morrisa-Pratta
Szybkie wyszukiwanie wzorca algorytmem Knutha-Morrisa-Pratta
Szybkie wyszukiwanie wzorca uproszczonym algorytmem Boyera-Moore'a
Szybkie wyszukiwanie wzorca pełnym algorytmem Boyera-Moore'a
Wyszukiwanie wzorca algorytmem Karpa-Rabina
Zliczanie słów w łańcuchu
Dzielenie łańcucha na słowa
Wyszukiwanie najdłuższego słowa w łańcuchu
Wyszukiwanie najdłuższego wspólnego podłańcucha
Wyszukiwanie najdłuższego wspólnego podciągu
Wyszukiwanie najkrótszego wspólnego nadłańcucha
Wyszukiwanie słów podwójnych
Wyszukiwanie palindromów
MasterMind – komputer kontra człowiek
MasterMind – człowiek kontra komputer
Szyfr Cezara
Szyfrowanie z pseudolosowym odstępem
Szyfry przestawieniowe
Szyfr Enigmy
Szyfrowanie RSA
Dodawanie dużych liczb
Mnożenie dużych liczb
Potęgowanie dużych liczb
Duże liczby Fibonacciego
Haszowanie
Podstawowe operacje na tablicach
Wyszukiwanie liniowe

Problem
W ciągu łańcuchów wyszukać zadany łańcuch w najkrótszym czasie.

Problemy tego typu pojawiają się często w bazach danych, gdzie szybko należy wyszukać rekord o zadanej zawartości (np. wg
nazwiska). Zachodzi pytanie, czy dany łańcuch możemy znaleźć wśród innych łańcuchów w czasie porównywalnym z czasem stałym
O(1). Rozwiązaniem jest haszowanie (ang. hashing). Wyobraźmy sobie, że posiadamy pewną funkcję, która dla danego łańcucha daje w
wyniku liczbę całkowitą z zakresu od 0 do n-1. Tworzymy tablicę n elementową łańcuchów i łańcuchy umieszczamy w tej tablicy na
pozycjach określonych przez naszą funkcję, którą będziemy nazywać funkcją haszującą (ang. hash function). Aby teraz znaleźć
łańcuch w tablicy, wyznaczamy dla niego wartość funkcji haszującej i sięgamy do komórki o tym indeksie. Nie musimy wcale przeglądać
reszty komórek. Brzmi wspaniale. Lecz rzeczywistość, niestety, nie jest aż tak idealna, co zaraz pokażemy.
Wyobraźmy sobie, że w tablicy T chcemy przechowywać 5 znakowe łańcuchy. Jeśli znaki będą w kodzie ASCII, to każdy z nich będzie
składał się z 8 bitów. Nasza funkcja mogłaby po prostu łączyć bity tych znaków w ciąg 40 bitowy, który określałby indeks znaku. Jednak
mamy tutaj problem - liczba 40 bitowa ma zakres od 0 do 240-1, a jest to naprawdę dużo, brakłoby nam pamięci w komputerze. Cóż
zatem zrobić? Możemy naszą funkcję haszującą hf() ograniczyć za pomocą działania modulo. Na przykład: sumujemy kody znaków
ASCII w łańcuchu, a wynik bierzemy modulo n. Wtedy łańcuchy mogą mieć dowolną długość. Sprawdźmy dla kilku łańcuchów ASCII:

Litera A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
Kod ASCII 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
n = 10

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 512 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

s1 = "ALA" hf(s1) = (65 + 76 + 65) mod 10 =6


s2 = "MA" hf(s2) = (77 + 65) mod 10 =2
s3 = "KOTA" hf(s3) = (75 + 79 + 84 + 65) mod 10 = 3

Dla łańcuchów s1, s2, s3 otrzymaliśmy indeksy 6, 2 i 3. Łańcuchy te umieszczamy zatem w komórkach tablicy T o podanych indeksach:

T[0] = ""
T[1] = ""
T[2] = "MA"
T[3] = "KOTA"
T[4] = ""
T[5] = ""
T[6] = "ALA"
T[7] = ""
T[8] = ""
T[9] = ""

Załóżmy dalej, że chcemy do naszej tablicy T wstawić łańcuch BAR, hf("BAR") = (66 + 65 + 82) mod 10 = 3. Mamy problem! Komórka
T[3] jest już zajęta, ponieważ przechowuje łańcuch "KOTA". Natrafiliśmy na tzw. kolizyjność haszowania – wiele różnych łańcuchów
posiada taką samą wartość funkcji haszującej (np. dla naszej funkcji haszującej te same wartości otrzymamy w słowach zbudowanych z
tych samych liter – KOTA, KATO, AKOT, OKAT, RAB, ARB...) . Jest to konsekwencją ograniczenia zbioru jej wartości. Problemu kolizji
nie unikniemy, musimy go zatem rozwiązać. Możemy, co prawda, bardziej skomplikować funkcję haszującą lub zwiększyć zakres
wartości. Dla pewnych łańcuchów otrzymamy wtedy różne wartości indeksu. Jednakże na dłuższą metę i tak natrafimy na problem kolizji,
ponieważ jest on cechą wewnętrzną haszowania.
Prostym rozwiązaniem jest tzw. próbkowanie liniowe (ang. linear probing). Polega ono na tym, iż przy wstawianiu łańcuchów
sprawdzamy, czy na pozycji wyliczonej przez funkcję haszującą jest wolne miejsce. Jeśli tak, to wstawiamy tam nasz łańcuch. Jeśli
komórka jest już zajęta, to liniowo szukamy pierwszej wolnej, tzn. przeglądamy kolejne komórki tablicy, aż do napotkania komórki wolnej
– po komórce T[n-1] następuje komórka T[0].
W naszym przykładzie łańcuch "BAR" umieszczamy w T[4]:

T[0] = ""
T[1] = ""
T[2] = "MA"
T[3] = "KOTA"
T[4] = "BAR"
T[5] = ""
T[6] = "ALA"
T[7] = ""
T[8] = ""
T[9] = ""

Gdyby znów pojawił się łańcuch, dla którego hf() = 3, to umieścilibyśmy go w T[5], następny w T[7] (ponieważ T[6] zajmuje już łańcuch
"ALA") itd. Warunkiem powodzenia jest, aby tablica posiadała odpowiedni rozmiar. Gdy w tablicy jest już n łańcuchów, to próba dodania
następnego spowoduje zapętlenie, ponieważ nie znajdziemy wolnej komórki. Wtedy należy dodatkowo sprawdzać, czy nie wróciliśmy do
wyjściowej komórki i przerwać operację wstawiania, jeśli tak się stało.
Jeśli stosowaliśmy próbkowanie liniowe przy wstawianiu łańcuchów do tablicy, to przy wyszukiwaniu łańcucha musimy je również
stosować. Zasada jest następująca:

Wyznaczamy indeks funkcją haszującą. Sprawdzamy, czy w komórce o podanym indeksie jest nasz łańcuch. Jeśli tak, to
kończymy. Jeśli nie, to liniowo sprawdzamy kolejne komórki aż do momentu, gdy znajdziemy poszukiwany łańcuch lub natrafimy
na komórkę pustą, lub wrócimy z powrotem do komórki o indeksie początkowym (zdarzy się to wtedy, gdy cała tablica jest
zapełniona łańcuchami, a poszukiwanego łańcucha brak). W dwóch ostatnich przypadkach poszukiwanie powinno zwrócić wartość
informującą o błędzie.

Przy próbkowaniu liniowym złożoność operacji wstawiania i wyszukiwania może się degenerować do klasy O(n). Z drugiej strony przy
wstawianiu łańcucha szukamy jedynie pustej komórki, nie musimy porównywać łańcuchów ze sobą (chyba że chcemy wyeliminować
duplikaty). Jeśli przy wstawianiu elementów nie wystąpiło dużo kolizji, to łańcuchy są rozmieszczone w tablicy w większości wg wartości
funkcji haszującej, zatem klasa złożoności wyszukiwania jest bliska ideałowi O(1).
Drugą metodę haszowania przedstawiamy w rozdziale "Haszowanie z wykorzystaniem list jednokierunkowych".

Algorytm wstawiania łańcucha do tablicy haszowanej


Wejście

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 513 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

s – łańcuch do umieszczenia w tablicy haszowanej


T – tablica łańcuchów
n – rozmiar tablicy, n N

Wyjście:
Tablica T z wstawionym łańcuchem s, jeśli było dla niego wolne miejsce.
Elementy pomocnicze:
i – indeks, i N
h – przechowuje wartość haszu, h N
hf(x,n) – oblicza indeks w T na podstawie łańcucha x i liczby komórek n.
Lista kroków dla wersji z duplikatami:
K01: h ← hf(s,n) ; obliczamy indeks początkowy
K02: i ← h ; przeszukiwanie tablicy rozpoczynamy od wyliczonego indeksu
K03: Jeśli T[i] = "", to T[i] ← s i zakończ ; w puste miejsce zapisujemy łańcuch
K04: i ← (i + 1) mod n ; przechodzimy do następnej komórki
K05: Jeśli i = h, to zakończ ; Jeśli wróciliśmy w to samo miejsce, to kończymy
K06: Idź do K03 ; inaczej kontynuujemy pętlę

Lista kroków dla wersji bez duplikatów:


K01: h ← hf(s,n) ; obliczamy indeks początkowy
K02: i ← h ; przeszukiwanie tablicy rozpoczynamy od wyliczonego indeksu
K03: Jeśli T[i] = "", to T[i] ← s i zakończ ; w puste miejsce zapisujemy łańcuch
K04: Jeśli T[i] = s, to zakończ ; jeśli w tablicy już jest łańcuch s, to kończymy bez wstawiania
K05: i ← (i + 1) mod n ; przechodzimy do następnej komórki
K06: Jeśli i = h, to zakończ ; Jeśli wróciliśmy w to samo miejsce, to kończymy
K07: Idź do K03 ; inaczej kontynuujemy pętlę

Algorytm wyszukiwania łańcucha w tablicy haszowanej


Wejście
s – łańcuch do wyszukania w tablicy haszowanej
T – tablica łańcuchów
n – rozmiar tablicy, n N

Wyjście:
Indeks łańcucha w T lub -1, jeśli łańcucha nie ma w T.
Elementy pomocnicze:
i – indeks, i N
h – przechowuje wartość haszu, h N
hf(x,n) – oblicza indeks w T na podstawie łańcucha x i liczby komórek n.
Lista kroków:
K01: h ← hf(s,n) ; obliczamy indeks początkowy
K02: i ← h ; przeszukiwanie tablicy rozpoczynamy od wyliczonego indeksu
K03: Jeśli T[i] = "", to zakończ z wynikiem -1 ; brak łańcucha
K04: Jeśli T[i] = s, to zakończ z wynikiem i ; łańcuch odnaleziony
K05: i ← (i + 1) mod n ; przechodzimy na następną pozycję
K06: Jeśli i = h, to zakończ z wynikiem -1 ; powrót na pozycję wyjściową, czyli brak łańcucha
K07: Idź do K03 ; inaczej kontynuujemy pętlę

Program
Ważne:
Zanim uruchomisz program, przeczytaj wstęp do tego artykułu, w którym wyjaśniamy funkcje tych programów
oraz sposób korzystania z nich.

Program ma na celu przetestowanie efektywności haszowania z próbkowaniem liniowych. Tworzy on 10-cio elementową
tablicę haszowaną łańcuchów. Następnie generuje 10 losowych łańcuchów 4-ro znakowych z liter A i B, po czym
umieszcza je w tablicy haszowanej. Ponieważ łańcuchy są tworzone losowo, to będą się pojawiały duplikaty, których
program nie umieści w tablicy, zatem część jej komórek pozostanie niezajęta. Również pojawią się łańcuchy o tej samej

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 514 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

wartości funkcji haszującej. W takim przypadku próbkowanie liniowe umieści je w innych komórkach, niż wychodzi to z ich
haszu.
Po wypełnieniu tablicy jej zawartość jest wyświetlana w oknie konsoli wraz z wartościami haszu. Jeśli wartość haszu różni
się od indeksu tablicy, to dany łańcuch został zapisany w innej komórce, ponieważ jego komórka była zajęta przez inny
łańcuch.
W drugiej części program generuje wszystkie łańcuchy 4-znakowe z liter A i B, a następnie wyszukuje je w tablicy
haszowanej, wyświetlając kolejno: łańcuch, wartość haszu hf(), liczbę obiegów pętli wyszukującej c oraz informację o
pozycji łańcucha w tablicy, jeśli się tam znajduje. Zwróć uwagę na pozycje, dla których liczba obiegów c jest równa zero –
te łańcuchy znaleziono od razu za pierwszym podejściem.

Lazarus

// Haszowanie z próbkowaniem liniowym


// Data: 16.06.2013
// (C)2013 mgr Jerzy Wałaszek
//-----------------------------------
program hashing;
// Funkcja haszująca
//------------------
function hf(s : string) : integer;
var
h,i : integer;
begin
h := 0;
for i := 1 to length(s) do
h := 2 * h + 1 - (ord(s[i]) and 1);
hf := h mod 10;
end;
// Funkcja tworzy łańcuch 4 znakowy z A i B
// na podstawie wartości bitów x
//-----------------------------------------
function s4(x : integer) : string;
var
m : integer;
s : string;
begin
s := '';
m := 8; // Maska bitowa
while m > 0 do
begin
if x and m = 0 then s := s + 'A' else s := s + 'B';
m := m shr 1;
end;
s4 := s;
end;
//**********************
//*** PROGRAM GŁÓWNY ***
//**********************
var
T : array[0..9] of string;
i,j,h,c,p : integer;
s : string;
begin
randomize;
// Zerujemy tablicę haszowaną
for i := 0 to 9 do T[i] := '';
// Tablicę wypełniamy łańcuchami
for i := 0 to 9 do
begin
// Generujemy losowy łańcuch z 4 znaków A,B
s := s4(random(16));
// Łańcuch umieszczamy w tablicy haszowanej
h := hf(s);
j := h;
while true do
begin
if T[j] = '' then
begin
T[j] := s;
break;
end;
if T[j] = s then break;
j := (j + 1) mod 10;
if j = h then break;
end;
end;

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 515 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

// Wyświetlamy zawartość tablicy wraz z wartością funkcji haszującej


for i := 0 to 9 do
begin
write('T[',i,'] = ');
if T[i] <> '' then writeln(T[i],' hf() = ',hf(T[i]))
else writeln('-');
end;
writeln;
// Sprawdzamy obecność łańcuchów w tablicy haszowanej
for i := 0 to 15 do
begin
s := s4(i); // Łańcuch
h := hf(s); // Hasz łańcucha
c := 0; // Licznik obiegów
j := h;
p := -1;
while true do
begin
if T[j] = '' then break;
if T[j] = s then
begin
p := j; break;
end;
j := (j + 1) mod 10;
if j = h then break;
inc(c);
end;
// Wyświetlamy wyniki
write(s,' hf() = ',h,' c = ',c,' ');
if p > -1 then writeln('is in T[',p,']')
else writeln('-');
end;
writeln;
end.

Code::Blocks

// Haszowanie z próbkowaniem liniowym


// Data: 16.06.2013
// (C)2013 mgr Jerzy Wałaszek
//-----------------------------------
#include <iostream>
#include <string>
#include <cstdlib>
#include <ctime>
using namespace std;
// Funkcja haszująca
//------------------
int hf(string s)
{
unsigned int h,i;
h = 0;
for(i = 0; i < s.length(); i++)
h = 2 * h + 1 - (s[i] & 1);
return h % 10;
}
// Funkcja tworzy łańcuch 4 znakowy z A i B
// na podstawie wartości bitów x
//-----------------------------------------
string s4(int x)
{
string s = "";
int m = 8; // Maska bitowa
while(m)
{
s += (x & m)? 'B' : 'A';
m >>= 1;
}
return s;
}
//**********************
//*** PROGRAM GŁÓWNY ***
//**********************
int main()
{
string T[10],s;
int i,j,h,c,p;
srand(time(NULL));

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 516 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

// Zerujemy tablicę haszowaną


for(i = 0; i < 10; i++) T[i] = "";
// Tablicę wypełniamy łańcuchami
for(i = 0; i < 10; i++)
{
// Generujemy losowy łańcuch z 4 znaków A,B
s = s4(rand() % 16);
// Łańcuch umieszczamy w tablicy haszowanej
h = hf(s);
j = h;
while(true)
{
if(T[j] == "")
{
T[j] = s;
break;
}
if(T[j] == s) break;
j = (j + 1) % 10;
if(j == h) break;
}
}
// Wyświetlamy zawartość tablicy wraz z wartością funkcji haszującej
for(i = 0; i < 10; i++)
{
cout << "T[" << i << "] = ";
if(T[i] != "") cout << T[i] << " hf() = " << hf(T[i]);
else cout << "-";
cout << endl;
}
cout << endl;
// Sprawdzamy obecność łańcuchów w tablicy haszowanej
for(i = 0; i < 16; i++)
{
s = s4(i); // Łańcuch
h = hf(s); // Hasz łańcucha
c = 0; // Licznik obiegów
j = h;
p = -1;
while(true)
{
if(T[j] == "") break;
if(T[j] == s)
{
p = j; break;
};
j = (j + 1) % 10;
if(j == h) break;
c++;
}
// Wyświetlamy wyniki
cout << s << " hf() = " << h << " c = " << c << " ";
if(p > -1) cout << "is in T[" << p << "]";
else cout << "-";
cout << endl;
}
cout << endl;
return 0;
}

Free Basic

' Haszowanie z próbkowaniem liniowym


' Data: 16.06.2013
' (C)2013 mgr Jerzy Wałaszek
'-----------------------------------
' Funkcja haszująca
'------------------
Function hf(Byref s As String) As Integer
Dim As Integer h,i
h = 0
For i = 1 To Len(s)
h = 2 * h + 1 - (Asc(Mid(s,i,1)) And 1)
Next
Return h Mod 10
End Function
' Funkcja tworzy łańcuch 4 znakowy z A i B

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 517 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

' na podstawie wartości bitów x


'-----------------------------------------
Function s4(x As Integer) As String
Dim As String s = ""
Dim As Integer m = 8 ' Maska bitowa
While m > 0
If (x And m) = 0 Then
s += "A"
Else
s += "B"
End If
m Shr= 1
Wend
s4 = s
End Function
'**********************
'*** PROGRAM GŁÓWNY ***
'**********************
Dim As String T(9),s
Dim As Integer i,j,h,c,p
Randomize
' Zerujemy tablicę haszowaną
For i = 0 To 9: T(i) = "": Next
' Tablicę wypełniamy łańcuchami
For i = 0 To 9
' Generujemy losowy łańcuch z 4 znaków A,B
s = s4(Int(Rnd() * 16))
' Łańcuch umieszczamy w tablicy haszowanej
h = hf(s)
j = h
While 1
If T(j) = "" Then
T(j) = s
Exit While
End If
If T(j) = s Then Exit While
j = (j + 1) Mod 10
If j = h Then Exit While
Wend
Next
' Wyświetlamy zawartość tablicy wraz z wartością funkcji haszującej
For i = 0 To 9
Print Using "T[&] = ";i;
If T(i) <> "" Then
Print Using "& hf() = &";T(i);hf(T(i))
Else
Print "-"
End If
Next
Print
' Sprawdzamy obecność łańcuchów w tablicy haszowanej
For i = 0 To 15
s = s4(i) ' Łańcuch
h = hf(s) ' Hasz łańcucha
c = 0 ' Licznik obiegów
j = h
p = -1
While 1
If T(j) = "" Then Exit While
If T(j) = s Then
p = j
Exit While
End If
j = (j + 1) Mod 10
If j = h Then Exit While
c += 1
Wend
' Wyświetlamy wyniki
Print Using "& hf() = & c = & ";s;h;c;
If p > -1 Then
Print Using "is in T[&]";p
Else
Print "-"
End If
Next
Print

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 518 / 519


Algorytmy i Struktury Danych - Haszowanie 2014-10-03

End

Wynik
T[0] = AAAA hf() = 0
T[1] = -
T[2] = AABA hf() = 2
T[3] = AABB hf() = 3
T[4] = BBBA hf() = 4
T[5] = BBBB hf() = 5
T[6] = BBAB hf() = 3
T[7] = ABBB hf() = 7
T[8] = -
T[9] = -
AAAA hf() = 0 c = 0 is in T[0]
AAAB hf() = 1 c = 0 -
AABA hf() = 2 c = 0 is in T[2]
AABB hf() = 3 c = 0 is in T[3]
ABAA hf() = 4 c = 4 -
ABAB hf() = 5 c = 3 -
ABBA hf() = 6 c = 2 -
ABBB hf() = 7 c = 0 is in T[7]
BAAA hf() = 8 c = 0 -
BAAB hf() = 9 c = 0 -
BABA hf() = 0 c = 1 -
BABB hf() = 1 c = 0 -
BBAA hf() = 2 c = 6 -
BBAB hf() = 3 c = 3 is in T[6]
BBBA hf() = 4 c = 0 is in T[4]
BBBB hf() = 5 c = 0 is in T[5]

List do administratora Serwisu Edukacyjnego I LO


Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:

Uwaga: ← tutaj wpisz wyraz ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048


Wyślij Kasuj

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby
rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień
szeroko opisywanych w podręcznikach.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji I Liceum Ogólnokształcące


GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek

http://edu.i-lo.tarnow.pl/inf/alg/001_search/0067e.php 519 / 519

You might also like