Przeskocz do treści

Delta mi!

Liniowe sito

Jakub Radoszewski

o artykule ...

  • Publikacja w Delcie: styczeń 2013
  • Publikacja elektroniczna: 01-01-2013

Jeśli potrzebne nam są do czegoś liczby pierwsze z pewnego początkowego zakresu, zazwyczaj wyznaczamy je za pomocą sita Eratostenesa...

obrazek

Zaczynamy od wypisania kolejno wszystkich liczb od math do math Następnie zaznaczamy 2 i wykreślamy wszystkie jej wielokrotności, zaznaczamy 3 i wykreślamy jej wielokrotności, dalej to samo z kolejnymi niewykreślonymi liczbami: 5, 7 itd. Jest to bardzo efektywna metoda; wykonujemy w niej rzędu math  operacji, o czym można przekonać się, czytając artykuł pt. „Jak szybko działa sito?” w Delcie 4/2012. math  to prawie math  ale jednak nie. Większa niż liniowa złożoność czasowa związana jest z tym, że w sicie Eratostenesa pozwalamy sobie na pewną rozrzutność, gdyż niektóre liczby złożone wykreślamy wielokrotnie. Zależnie od szczegółów implementacyjnych, pierwszą taką liczbą złożoną jest 6 albo 12. Zastanówmy się jednak, czy nie dałoby się każdej liczby złożonej wykreślić dokładnie raz?

Tak jak w zwykłym sicie, na początku tworzymy listę wszystkich liczb od math do math Znów w pierwszym kroku ustalamy liczbę pierwszą math Dalej będzie trochę inaczej niż poprzednio, ale nie tak znowu skomplikowanie. Rozważamy kolejne (niewykreślone i nie mniejsze niż math) liczby math na liście i dla każdej z nich wykreślamy wszystkie liczby postaci math dla math

Zobaczmy to na przykładzie; niech math

Na początku mamy math i listę przeglądamy, począwszy od math W pierwszym kroku wykreślamy liczby postaci math dla math czyli potęgi dwójki:

obrazek

Przyszła pora na math wykreślamy wszystkie liczby postaci math:

obrazek

Kolejną nieskreśloną liczbą jest math Wykreślamy więc liczby postaci math:

obrazek

Nietrudno zgadnąć, co będzie dalej. Czytelnik zechce sprawdzić, że po pełnym rozpatrzeniu math na liście pozostaną po prostu wszystkie liczby nieparzyste.

obrazek

Przyszła wreszcie pora na math Rozważamy wszystkie dotychczas nieskreślone wartości math nie mniejsze niż math Zaczynamy od math czyli najpierw wykreślamy potęgi trójki:

obrazek

Następnie mamy math i wykreślamy tylko 15; math i znika 21; dalej wykreślimy jeszcze 33 i 39:

obrazek

W ten sposób rozważyliśmy math przechodzimy do math Za pomocą math wykreślimy już tylko 25, a za pomocą math wykreślimy 35.

obrazek

Dla każdego kolejnego kandydata na math liczba math jest większa niż math możemy więc śmiało stwierdzić, że na liście pozostały nam już tylko liczby pierwsze (i to wszystkie w badanym zakresie).

W tym przykładzie rzeczywiście każdą liczbę złożoną skreśliliśmy dokładnie raz. Nie jest to przypadek; liczbę złożoną

display-math

skreślamy w przebiegu algorytmu, w którym math i  math (a jeśli math to, oczywiście, dla math). To, w szczególności, oznacza, że dla każdej liczby złożonej obliczamy przy okazji jej najmniejszy dzielnik pierwszy.

Możemy więc pokusić się o stwierdzenie, że cała metoda wykonuje wymarzone math  operacji. Aby jednak dało się ją przekuć na algorytm o liniowym koszcie czasowym, musimy przyjrzeć się dokładniej używanej strukturze danych. Mamy tu do czynienia z dwoma typami operacji: znajdowaniem następnego elementu na liście (potrzebne do rozpatrywania kolejnych math i  math) oraz wykreśleniem danej liczby z listy. O ile pierwszą z tych operacji wykonuje się na liście standardowo w czasie stałym, o tyle druga z nich sprawia pewien kłopot. W przypadku listy nie mamy bowiem swobodnego dostępu do jej elementów. Taki dostęp daje np. tablica, która to struktura nie pozwala z kolei na proste usuwanie bądź wykreślanie elementów...

Klucz do rozwiązania tej ostatniej trudności stanowi połączenie dwóch wspomnianych struktur danych, czyli listy i tablicy. Dokładniej, oprócz dwukierunkowej listy wszystkich niewykreślonych elementów utrzymujemy tablicę wskaźników do poszczególnych liczb od 2 do math na liście. Jeśli danej liczby nie ma już na liście, w odpowiednim polu tablicy możemy wstawić np. nil. Za pomocą wskaźników zapisanych w tablicy łatwo znajdujemy elementy listy, które chcemy wykreślić, a potem usuwamy je już standardowo – podpinając do siebie wzajemnie następny i poprzedni element listy.

Przykładowo, opisana struktura danych dla math po wykreśleniu potęg dwójki wygląda tak:

obrazek

Teraz możemy już z całą pewnością powiedzieć, że otrzymaliśmy liniową metodę wykrywania wszystkich liczb pierwszych w danym początkowym zakresie liczb.

Czy w praktyce jest ona lepsza od klasycznego sita Eratostenesa? Nie sądzę. Za to ma ona pewne ciekawe z teoretycznego punktu widzenia zastosowanie, o czym można przeczytać w kolejnym artykule w tym numerze Delty.


Opisany algorytm pochodzi z 1978 roku i jest autorstwa Davida Griesa i Jayadeva Misry. Co ciekawe, wszystkie liczby pierwsze z zakresu od 2 do math można znaleźć nawet w czasie math(Paul Pritchard, 1981).