Professional Documents
Culture Documents
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
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
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
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
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
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
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
Temat:
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/index.php 8 / 519
Algorytmy i Struktury Danych - Wstęp 2014-10-03
Wstęp
Podrozdziały
Algorytmy i struktury danych
Języki programowania
Aplikacja konsoli
Prezentacja algorytmu
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
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.
Idź do Kxx
Następnym krokiem wykonywanym przez algorytm będzie krok Kxx.
Pisz wyrażenie
Algorytm wyprowadza na wyjście wartość wyrażenia.
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.
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0001.php 11 / 519
Algorytmy i Struktury Danych - Wstęp 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).
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/0001.php 12 / 519
Algorytmy i Struktury Danych - Przedziały liczbowe i liczby 2014-10-03
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.
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>.
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.
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.
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
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
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
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>.
// 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.
// 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
...
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
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/0003.php 19 / 519
Algorytmy i Struktury Danych - Liczby podzielne i niepodzielne 2014-10-03
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.
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
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
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.
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
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
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
Code::Blocks
Free Basic
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
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.
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
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
...
Temat:
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
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
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.
ai = a1 + (i - 1) × d
a1 – pierwszy wyraz ciągu
d – stały przyrost
i – numer wyrazu do wygenerowania
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
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
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
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
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
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
Free Basic
Wynik
20 2000 123
2000 2123 2246 2369 2492 2615 2738 2861 2984 3107
3230 3353 3476 3599 3722 3845 3968 4091 4214 4337
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/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.
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0005.php 33 / 519
Algorytmy i Struktury Danych - NWD - algorytm Euklidesa 2014-10-03
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
Code::Blocks
Free Basic
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:
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
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
Free Basic
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
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
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
Free Basic
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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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/0006.php 40 / 519
Algorytmy i Struktury Danych - Liczby względnie pierwsze 2014-10-03
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.
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
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
Code::Blocks
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
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
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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/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
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)
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
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.
Wynik
9 6
18
Wykonaj
...
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
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/0008.php 47 / 519
Algorytmy i Struktury Danych - Odwrotność modulo 2014-10-03
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:
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)
a×1+b ×0=a
a×0+b ×1=b
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:
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
u ←u-q× x
v ←v-q× y
w ← w - q × z = w mod z
(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
(1) 2×0+5×1=5
(2) 2×1+5×0=2
(1) 2 × (0 - 2 × 1) + 5 × (1 - 2 × 0) = 5 - 2 × 2
(1) 2 × (-2) + 5 × 1 = 1
(2) 2×1+5×0=2
(1) 2×1+5×0=2
(2) 2 × (-2) + 5 × 1 = 1
(1) 2 × (1 - 2 × (-2)) + 5 × (0 - 2 × 1) = 2 - 2 × 1
(1) 2 × 5 + 5 × (- 2) = 0
(2) 2 × (-2) + 5 × 1 = 1
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
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.
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".
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
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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/0009.php 52 / 519
Algorytmy i Struktury Danych - Liczby pierwsze - sprawdzanie podzielności 2014-10-03
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.
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
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ęć.
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.
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.
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0010.php 58 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03
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:
{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}
{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}
{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.
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
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
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
p0 = 3, q0 = 4 – wartości startowe
Dla i > 0:
pi = pi-1 + 2
qi = qi-1 + 2(pi - 1)
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
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ć:
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
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.
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
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
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0011.php 68 / 519
Algorytmy i Struktury Danych - Sito Eratostenesa 2014-10-03
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
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).
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/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
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:
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).
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.
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
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
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).
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/0012.php 75 / 519
Algorytmy i Struktury Danych - Sito Atkina Bernsteina 2014-10-03
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.
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
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
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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/0013.php 80 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03
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:
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:
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.
44100 = 2 × 2 × 3 × 3 × 5 × 5 × 7 × 7
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.
// 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.
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
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.
// 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
Wykonaj
...
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0014.php 84 / 519
Algorytmy i Struktury Danych - Czynniki pierwsze 2014-10-03
Temat:
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/0014.php 85 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03
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),
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.
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
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:
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:
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.
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
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.
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
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 91 / 519
Algorytmy i Struktury Danych - Metoda Fermata 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 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.
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
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
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
http://edu.i-lo.tarnow.pl/inf/alg/001_search/0015.php 95 / 519
Algorytmy i Struktury Danych - Metoda Fermata 2014-10-03
Wykonaj
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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/0015.php 96 / 519
Algorytmy i Struktury Danych - Pierwszość liczby naturalnej 2014-10-03
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.
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.
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.
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.
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
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 ± 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ń.
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.
Badanie pierwszości
(C)2012 mgr Jerzy Wałaszek
p= 32767
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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
...
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ą.
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:
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:
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
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.
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
Code::Blocks
#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
Wynik
283125637241 551842729812
4916904288980
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 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
Code::Blocks
}
// 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
Wynik
9223372036854775783
TAK
9223372036854775787
NIE
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
Problem
Sprawdzić, czy liczba naturalna p jest pierwsza.
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.
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 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.
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
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
'---------------------------------
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
t = 0: Exit For
End If
Next
End If
If t = 1 Then Print "TAK": Else Print "NIE"
End
Wynik
9223372036854775783
TAK
9223372036854775787
NIE
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).
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.
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,
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.
Wyjście:
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
//----------------------------
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.
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;
}
}
cout << (t ? "TAK" : "NIE") << endl;
return 0;
}
Free Basic
Wynik
9223372036854775783 20
TAK
9223372036854775787 1
NIE
Temat:
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.
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:
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ć:
Jeśli współczynniki a, c i m są źle dobrane, to cykl powtarzania może być krótszy niż 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
a - 1 = 4 × 3 = 12
a = 12 + 1 = 13
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
...
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:
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.
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.
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
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
<< endl;
return 0;
}
Free Basic
Wynik
9987
m = 9988
a = 9989
c = 1785
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
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
Free Basic
Wynik
m = 71
a = 72
c = 8
71
OK
m = 111
a = 112
c = 9
37
---
Jeśli m = 2k, to
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):
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
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>:
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:
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.
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.
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:
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
Code::Blocks
Free Basic
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
Wykonaj
...
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).
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.
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
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.
Wyjście:
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
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
Free Basic
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
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).
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.
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
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:
Zadanie to jest trudne obliczeniowo, ponieważ liczby Mersenne'a bardzo szybko rosną. Na dzień dzisiejszy największą znaną liczbą Mersenne'a
jest:
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:
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:
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
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
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
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
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
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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:
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
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.
...
randomize; // inicjujemy generator pseudolosowy
...
for i := 1 to 10 do // generujemy liczby pseudolosowe
begin
writeln(random(100));
end;
...
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.
Lazarus
19
20
16
19
19
16
11
15
12
18
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!
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>.
Jeśli potrzebujemy większego zakresu liczb pseudolosowych niż 15 bitów, to możemy wykorzystać funkcję rand()
kilkakrotnie:
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
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:
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.
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.
Free Basic
a + Rnd * (b - a)
a + (1 - Rnd) * (b - a)
Aby uzyskać rzeczywistą liczbę pseudolosową w przedziale domkniętym <a,b> stosujemy konstrukcję:
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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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;
}
Free Basic
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ń.
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ą
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
Code::Blocks
{
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
Wynik
1 80 20
39 79 12 38 80 3 11 33 9 76 19 21 8 14 37 29 24 71 50 1
a= 1 , b= 80 , n = 20
Wykonaj
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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.
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
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.
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ę
ciągu Fibonacciego.
Wynik
93
12200160415121876738
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).
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.
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:
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:
gdzie
Przykład:
W systemie trójkowym wagi pozycji są kolejnymi potęgami podstawy tego systemu, czyli liczby 3. Cyfry należą do
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)
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 ...
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:
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.
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
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.
Wynik
1111
7
Wykonaj
...
Problem
Daną, dodatnią liczbę dziesiętną L zapisać w systemie liczbowym Fibonacciego.
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.
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ę
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.
Wynik
1234567890
100000101010010100000001001001000101010001010
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
02 12 22 32 ...i2
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 ...
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:
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
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.
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
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
Wynik
135
11
121
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=
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.
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.
// 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.
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.
// 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;
}
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.
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.
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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:
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:
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
...
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:
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:
Dwie końcowe składnie pozwalają szybko deklarować jednym poleceniem Dim zmienne tego samego typu, na przykład:
...
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:
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:
Poniższy przykład tworzy tablicę 10 liczb całkowitych i wypełnia ją kolejnymi liczbami Fibonacciego.
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:
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:
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;
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:
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]
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
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;
...
delete [] b; // usuwamy obszar wskazywany przez b
delete [] c; // usuwamy obszar wskazywany przez c
...
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:
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:
A[15] = ...
4. Gdy tablica przestanie być potrzebna, należy zwolnić zajmowaną przez nią pamięć:
Delete [] A
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
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
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
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
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.
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.
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.
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()
{
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
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
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
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
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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:
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.
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
Przypadek pierwszy: poszukiwanej liczby nie ma w zbiorze. Poniżej przedstawiamy liczbę wykonań poszczególnych operacji w
algorytmie:
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.
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).:
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.
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
Code::Blocks
Free Basic
Wynik
5
13
18
954
235
12
111
-1
Temat:
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.
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.
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
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.
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;
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
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
Wykonaj
...
Temat:
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.
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.
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
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
Code::Blocks
Free Basic
Wynik
89
282
5838
8656
6423
5088
4859
2017
8291
8524
127
621
3826
4914
1733
89 8656
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.
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
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
Code::Blocks
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
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
Temat:
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.
Problem
W n-elementowym zbiorze Z znaleźć element maksymalny i minimalny.
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.
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
Code::Blocks
Free Basic
Wynik
4850
3151
2213
272
7933
2490
1267
4923
713
987
3253
5533
1759
8103
9414
272 : 9414
Temat:
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.
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.
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
Code::Blocks
Free Basic
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
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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:
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ę
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
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
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.
Lista kroków:
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
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
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.
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:
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
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)
{
Free Basic
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.
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.
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>
Free Basic
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
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.
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.
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.
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.
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');
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
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
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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:
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
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.
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
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);
// 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
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
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
...
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).
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.
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:
isr - ip k - Z[ip]
≈ , mnożymy obustronnie przez ik - ip
ik - ip 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
ip + ik 0 + 18 18
isr = = = =9
2 2 2
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).
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
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
// 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
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
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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
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
Code::Blocks
#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
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
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ę.
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
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
Code::Blocks
// 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
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.
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:
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
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
Free Basic
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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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
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.
Wyjście:
j – pozycja elementu zwrotnego w Z. Element ten dzieli partycję wejściową na dwie partycje:
Elementy pomocnicze:
v – wartość elementu zwrotnego
i,j – indeksy w tablicy Z, i,j C
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ę 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
Code::Blocks
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
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
--------------------------------|-----------------------------------------------
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:
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ę 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;
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;
Free Basic
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
---
Wykonaj
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
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.
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
// 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
//---------------------------
#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
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.
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
Code::Blocks
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
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
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.
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
//-----------------------------
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
Free Basic
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.
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.
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
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
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.
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
Code::Blocks
Free Basic
' **********************
' *** 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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
Ł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.
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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).
| 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]).
Pref(s) = s[0 : k]
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:
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.
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]
Border(s) s Border(s)
Border(s) s Border(s)
←d→
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.
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).
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.
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.
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:
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.
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.
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.
Program tworzy trzyznakową tablicę i wpisuje do niej wyraz ALO. Następnie literki są wypisywane w kierunku
odwrotnym:
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):
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:
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.
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.
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;
return 0;
}
Free Basic
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
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:
Język FreeBasic nie posiada bezpośredniej funkcji wstawiania znaku lub łańcucha do innego łańcucha. Dlatego posiłkujemy się
dwoma funkcjami pomocniczymi:
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
"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
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'.
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
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".
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.
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
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).
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.
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
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
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.
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(' ');
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
Wynik
CBC
AACBBAACCACBCCBABBBBABACAABAAACABCABBCBAAAAAAACCAAACBBAACABCBCCABCBCCBCBCAABACAC
^ ^ ^ ^ ^
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.
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
Code::Blocks
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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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).
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>
#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
Wynik
ABBAAABBBAAAAABBABAABAABABBBABABBABBABBA
4
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
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.
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
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:
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).
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
//-----------------------------
#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
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).
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.
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:
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.
Lista kroków:
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
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
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
Wynik
BABBB
AABABBBBABBBBABABAAABBBAABBBABABBABABBAABABBBBBAABAAAAAAABABBBABBBABAABBBBAAAABB
^ ^ ^ ^ ^
Wykonaj
...
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).
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.
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) :
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 Π.
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.
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
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
Free Basic
Wynik
BABBB
AABABBBBABBBBABABAAABBBAABBBABABBABABBAABABBBBBAABAAAAAAABABBBABBBABAABBBBAAAABB
^ ^ ^ ^ ^
Wykonaj
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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.
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ć:
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.
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
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
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
end.
Code::Blocks
Free Basic
'---------------------------------------
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
^ ^ ^ ^
Wykonaj
...
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
Problem
W łańcuchu s wyznaczyć wszystkie wystąpienia wzorca p.
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ęć.
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:
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.
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:
W efekcie otrzymujemy wartość przesunięcie okna wzorca p w łańcuchu s, po którym zrównują się pozycje
pasującego prefikso-sufiksu we wzorcu i w oknie. Prześledźmy teraz tworzenie obu tablic dla wzorca ABBABBBA:
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
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
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
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:
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
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
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
Free Basic
Wynik
ABBBA
BBAABBBAABBBAABAAABBBABBBAAABABBAAABBAAAABBABAAABBAABAABBBBBBBBAABABBAAABAAAABBB
^ ^ ^ ^
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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:
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
H = (H + 1) mod 23
H = (19 + 1) mod 23
H = 20 mod 23
H = 20
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
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
Code::Blocks
{
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
Next
h = hx
End Function
Wynik
BABC
CCABABABCBBBCAABCBCCAAAAACBABAABCABBBCCCCBCABACBBBBAABCBABCBACCACBBBABCBAAABBBAB
^ ^ ^
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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.
s – łańcuch tekstowy.
Wyjście:
Liczba słów zawartych w łańcuchu s.
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
Code::Blocks
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
Wynik
Ala ma krokodyla z plastiku i gumy, ale co to nas obchodzi.
12
Wprowadź tekst:
Wykonaj
...
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).
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.
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.
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.
Lazarus
Code::Blocks
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
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ć]
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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ść.
s – łańcuch tekstowy.
Wyjście:
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
Code::Blocks
Free Basic
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
Wprowadź tekst:
Wykonaj
...
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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
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
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
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
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
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
Wynik
s1 = AAAAAABBAAABBBBBAABBAAAAABBBABAABBBBABBA
s2 = AAAABBBBBABBBAAAAAAABAABBAABBAAABBBAABBA
AAAAAABBAAABBBBBAABBAAAAABBBABAABBBBABBA
AABBAAABBB : 10
AAAABBBBBABBBAAAAAAABAABBAABBAAABBBAABBA
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:
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ć.
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
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
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
{
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
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).
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.
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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
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.
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:
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
Wyjście:
Zawartość sLCS
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
Code::Blocks
Free Basic
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].
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]
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:
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
else dec(i);
writeln(sLCS,' : ',L[m][n]);
end.
Code::Blocks
Free Basic
Wynik
s1 = ALIBABA
s2 = KALIMALBA
Wykonaj
...
Temat:
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.
w Tarnowie
(C)2014 mgr Jerzy Wałaszek
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.
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):
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.
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
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:
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:
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
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
}
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
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
Wykonaj
...
Temat:
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.
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.
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
writeln;
end.
Code::Blocks
Free Basic
Wynik
ABBAABBAAABAABABBBAA
ABBAABBA
BB
BBAABBAA
AA
BB
AA
AA
AABAAB
ABAABA
AA
ABAB
BB
BB
AA
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:
2. Długość prefikso-sufiksu jest równa długości połowy prefiksu, czyli Π[i] = i/2:
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:
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:
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:
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.
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 –
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
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
Free Basic
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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ń
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
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:
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:
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.
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
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
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
Wynik
BCACCAABCBBDCCCCDBBBDBDDBDCDBBCBADBBBABB
CAC
ACCA
CC
AA
BCB
BB
BBDCCCCDBB
BDCCCCDB
DCCCCD
CC
CCC
CCCC
CC
CCC
CC
DBBBD
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.
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
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
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.
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:
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
Code::Blocks
#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
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
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
I Liceum Ogólnokształcące
GNU Free Documentation License. im. Kazimierza Brodzińskiego
w Tarnowie
(C)2014 mgr Jerzy Wałaszek
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.
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.
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
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
Code::Blocks
Free Basic
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
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).
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.
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
{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
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:
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.
Przykład:
Z = {0,1,2,3,4,5,6,7,8,9}, zn = 10, usuwamy elementy 3,6,7,8:
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
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.
Lazarus
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
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
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
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
w Tarnowie
(C)2014 mgr Jerzy Wałaszek
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
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:
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
c = 65 + (c - 42) mod 26
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. Zamienia litery małe na duże i szyfruje kodem Cezara wyświetlając wynik.
Lazarus
Code::Blocks
Free Basic
Wynik
nieprzyjaciel jest bardzo blisko
QLHSUCBMDFLHO MHVW EDUGCR EOLVNR
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
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
Free Basic
Wynik
QLHSUCBMDFLHO MHVW EDUGCR EOLVNR
NIEPRZYJACIEL JEST BARDZO BLISKO
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).
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.
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.
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
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.
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:
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.
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
Code::Blocks
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
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.
Szyfruj
.
Deszyfrowanie
Zasada rozszyfrowywania jest prawie identyczna jak przy szyfrowaniu. Jedyna różnica leży we wzorze obliczania kodu znaku z
kodu szyfru:
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
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
Free Basic
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
Rozszyfruj
.
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).
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.
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.
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.
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
// 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
Wynik
ALA MA KOCURKA BURKA I PIESKA BIESKA
LA AAMK CORUAKB RUAKI P EIKS AIBSEAK
Szyfruj
.
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
(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).
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.
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
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
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.
Szyfruj/Deszyfruj
.
m = 984 = 2 × 2 × 2 × 3 × 41
a = 493 = 2 × 2 × 3 × 41 + 1
c = 385 = 5 × 7 × 11
X0 = 0...983
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
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
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
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 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
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();
}
Free Basic
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
127
Szyfruj/Deszyfruj
.
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).
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.
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.
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:
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 ...
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
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ń.
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.
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.
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 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).
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.
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:
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.
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
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
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;
Free Basic
"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
End
Wynik
123
ABC
ABCD
WIRXGEBENXAUFXENDE
ASDIQXFZJGWONIMHOT
Uruchom Enigmę
.
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).
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.
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:
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.
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.
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)
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:
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
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.
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)
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
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;
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)
{
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;
}
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
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
'---------------------------------------------
Wynik
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.
wykładnik
moduł
wiadomość
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.
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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ę.
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:
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.
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
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
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
Free Basic
Wynik
481084081308409834234234008219934109784217102740921707907072414421
892719749217498721847921749827210740217402147210740210472107402149
1373803830525908556082155758047144850001619249951661918379179816570
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ć?
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
Problem 1
Pomnożyć dowolnie dużą nieujemną liczbę całkowitą przez nieujemną liczbę całkowitą względnie małą, np. 32
bitową.
Liczba mała b rozkłada się na sumę potęg liczby 2 mnożonych przez kolejne bity bi liczby b:
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
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
#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
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
Mnóż
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).
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
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
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
Free Basic
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
95832808595775579737342988203408934789797363
21638626396236623668969866232198
Mnóż
.
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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ą.
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:
Jeśli dokładnie się przyjrzysz ostatniemu wzorowi, to powinieneś spostrzec, że zbudowany on jest z iloczynu wyrażeń:
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:
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.
Lazarus
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
}
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
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ęguj
.
Temat:
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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ą.
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.
Lazarus
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
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
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
n= 2000
Olblicz
.
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.
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
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".
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ę
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
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
Code::Blocks
Free Basic
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]
Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).
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.