Przykład optymalizacji sita Eratostenesa w języku C++

Algorytm sita Eratostenesa możemy optymalizować. Przeanalizujmy jeden z przykładów takiej optymalizacji.

Zanim przystąpimy do pisania kodu, trzeba zauważyć, że jedyną parzystą liczbą pierwszą jest 2 - każda kolejna liczba parzysta: 4, 6, 8, ... jest złożona.

Utwórzmy tablicę , składającą się z 26 komórek, której elementy będą przechowywać kolejne liczby nieparzyste, zaczynając od liczby 3. W języku C++ tablice indeksujemy od 0 – na potrzeby omawianego algorytmu komórkę kryjącą się pod tym indeksem wypełnimy wartością 0; pomoże nam to w lepszym zrozumieniu algorytmu. Tablica będzie więc składała się z następujących elementów: pod indeksem 1 będzie kryła się wartość 3. Możemy przyjąć, że: , ... .

RVVLMlbb8rr8H
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-ND 1.0.

Zauważmy, że dzięki wypełnieniu tablicy danymi od indeksu 1 możemy wprowadzić korelacje między numerem indeksu, a wartością, jaką ten indeks przechowuje.

Komórka o indeksie  będzie przechowywać liczbę o wartości:

Np. komórka o indeksie 9 będzie przechowywać wartość , z kolei komórka o indeksie 15: .

Wyprowadzenie wzoru przedstawionego powyżej pozwala nam w łatwy sposób obliczyć indeks komórki, w której została zapisana wartość .

Ponownie rozpatrzmy kilka przykładów. Liczba 59 będzie przechowywana pod indeksem 29, ponieważ ; z kolei liczba 49 będzie przechowywana pod indeksem 24.

Na potrzeby niniejszego algorytmu wprowadźmy dwie zmienne indeksowane:

  • – indeks kwadratu liczby, której wielokrotność usuwamy z tablicy ,

  • – odstęp (liczba komórek w tablicy ) między kolejnymi wielokrotnościami, które będziemy wykreślać.

Algorytm zaprezentowany w poprzedniej sekcji tłumaczył, dlaczego pierwszą „wykreślaną” wielokrotnością jest kwadrat przetwarzanej liczby. Tablica pod indeksem 1 przechowuje liczbę 3 – jej kwadratem jest liczba 9, kryjąca się pod indeksem . Zauważmy, że kolejne wielokrotności liczbywielokrotność liczbywielokrotności liczby 3 znajdują się w odległości – te liczby nie są liczbami pierwszymi; wykreślamy je.

R12A0kIMqMegG
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.

Kolejną niewykreśloną liczbą jest 5. Jej kwadrat – 25 kryje się pod indeksem . Zauważmy, że wartość możemy przedstawić również jako (dokładne omówienie wzoru nastąpi poniżej). Dystans między kolejnymi wielokrotnościami liczby 5 wynosi i możemy go przedstawić jako . Ponownie wykreślamy wielokrotności.

R14Eo2iPARyKH
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.

Proces powtarzamy dla liczby 7. Jej kwadrat – 49 został zapisany pod indeksem (). Dystans między jej kolejnymi wielokrotnościami wynosi ().

R10eJf4PRoMGh
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.

Następna liczba (9) została wykreślona już wcześniej. Aby lepiej zrozumieć algorytm,  omówmy i ją. Wskazujemy indeks komórki, w której umieszczono kwadrat liczby 9, czyli 81: (). Odległość między wielokrotnościami wynosi (). Zauważmy, że liczba 81 (kwadrat liczby 9) znajduje się poza zakresem tablicy . Jest to moment, w którym przerywamy algorytm.

RyNGn0CT7iUro
Źródło: Contentplus.pl sp. z o.o., licencja: CC BY-SA 3.0.

Na podstawie przedstawionych rozważań możemy zbudować tabelę:

0

3

4

1

3 + 2 = 5

2 * (5 - 1) = 8

4 + 8 = 12

2

5 + 2 = 7

2 * (7 - 1) = 12

12 + 12 = 24

3

7 + 2 = 9

2 * (9 - 1) = 16

24 + 16 = 40

Zauważmy, że kolejne powstaje poprzez dodanie do poprzedniej wartości liczby 2. Podobną zależność jesteśmy w stanie wyprowadzić dla – do poprzedniej wartości dodajemy .

W ten sposób dotarliśmy do momentu, w którym możemy wyprowadzić następujący wzór rekurencyjny:

  • ,

  • ,

  • dla każdego > :

    • .

Implementacja algorytmu

Implementacje algorytmu rozpocznijmy od zadeklarowania funkcji main, wczytania biblioteki iostream oraz poinformowania kompilatora o wykorzystaniu typów pochodzących z biblioteki standardowej.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 8. zamknij nawias klamrowy.

Tworzymy zmienną n, która będzie przechowywać rozmiar generowanego sita. Zapisujemy w niej liczbę naturalną dodatnią pobraną od użytkownika.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 9. zamknij nawias klamrowy.

Przedstawiony algorytm nie wymaga do swojego działania tablicy o długości n. Już na wstępie do omawiania algorytmu założyliśmy wykreślenie wszystkich liczb parzystych większych od 2 – potrzebujemy tylko połowy komórek, które użylibyśmy w przypadku niezoptymalizowanego algorytmu sita Eratostenesa.

Sprawdzamy, czy wprowadzona przez użytkownika liczba n jest nieparzysta. Jeżeli badany warunek jest prawdziwy, wówczas zwiększamy wartość n o 1.

Tworzymy zmienną pomocniczą polowa, którą wykorzystamy do tworzenia tablicy, a także jako warunek wykonania kolejnych pętli.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. zamknij nawias klamrowy.

Następnie tworzymy tablicę wartości logicznych tab o długości polowa. Wypełniamy ją wartościami true. Dlaczego w komórkach tablicy przechowujemy wartości logiczne, skoro podczas omawiania algorytmu przez cały czas pokazywaliśmy liczby naturalne?

Zauważmy, że dzięki wyprowadzeniu korelacji między – indeksem komórki tablicy oraz – wartością przechowywaną w komórce, nie musimy zapisywać informacji o liczbie kryjącej się pod indeksem. Podobnie jak w przypadku niezoptymalizowanego algorytmu sita Eratostenesa, jeżeli komórka kryjąca się pod indeksem i przechowuje wartość false, oznacza to wykreślenie liczby k. Co więcej, sam algorytm opiera się na badaniu odległości między kolejnymi indeksami zapisanymi w tablicy – same liczby są wyłącznie dodatkową reprezentacją.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 20. zamknij nawias klamrowy.

Dodajemy kolejne zmienne:

  • i – będzie wskazywać akualnie przetwarzany indeks tablicy tab,

  • d – będzie wskazywać odległość między kolejnymi wykreślanymi indeksami,

  • a – będzie przechowywać pierwszą wielokrotność liczby kryjącej się pod indeksem i.

Zmienną d inicjalizujemy wartością 3, z kolei zmienną a wartością 4. Są to wartości początkowe wcześniej przedstawionego wzoru rekurencyjnego.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int i znak równości 1 średnik. Linia 22. int d znak równości 3 średnik. Linia 23. int a znak równości 4 średnik. Linia 24. zamknij nawias klamrowy.

Dodajemy pierwszą pętlę – while. Będzie się wykonywać, dopóki nie przekroczymy badanego zakresu. W jej wnętrzu sprawdzamy, czy komórka kryjąca się pod indeksem i przechowuje wartość true – oznacza to, że badana liczba nie została wykreślona wcześniej, czyli jest pierwsza.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int i znak równości 1 średnik. Linia 22. int d znak równości 3 średnik. Linia 23. int a znak równości 4 średnik. Linia 25. while otwórz nawias okrągły a otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 26. if otwórz nawias okrągły tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości znak równości true zamknij nawias okrągły otwórz nawias klamrowy. Linia 28. zamknij nawias klamrowy. Linia 29. zamknij nawias klamrowy. Linia 30. zamknij nawias klamrowy.

Jeżeli liczba kryjąca się pod indeksem i jest pierwsza, czyli przedstawiony warunek jest prawdziwy, musimy wykreślić jej wielokrotności. Tworzymy zmienną pomocniczą z, do której przypisujemy indeks pierwszej wielokrotności liczby kryjącej się pod indeksem i. Następnie w zagnieżdżonej pętli while ustawiamy wartość false kolejnym indeksom oddalonym o d.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int i znak równości 1 średnik. Linia 22. int d znak równości 3 średnik. Linia 23. int a znak równości 4 średnik. Linia 25. while otwórz nawias okrągły a otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 26. if otwórz nawias okrągły tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości znak równości true zamknij nawias okrągły otwórz nawias klamrowy. Linia 27. int z znak równości a średnik. Linia 28. while otwórz nawias okrągły z otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 29. tab otwórz nawias kwadratowy z zamknij nawias kwadratowy znak równości false średnik. Linia 30. z plus znak równości d średnik. Linia 31. zamknij nawias klamrowy. Linia 32. zamknij nawias klamrowy. Linia 33. zamknij nawias klamrowy. Linia 34. zamknij nawias klamrowy.

Poza instrukcją warunkową zwiększamy wartość iteratora i. Zgodnie z przedstawionym wcześniej wzorem wyznaczamy nowe wartości zmiennych d oraz a.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int i znak równości 1 średnik. Linia 22. int d znak równości 3 średnik. Linia 23. int a znak równości 4 średnik. Linia 25. while otwórz nawias okrągły a otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 26. if otwórz nawias okrągły tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości znak równości true zamknij nawias okrągły otwórz nawias klamrowy. Linia 27. int z znak równości a średnik. Linia 28. while otwórz nawias okrągły z otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 29. tab otwórz nawias kwadratowy z zamknij nawias kwadratowy znak równości false średnik. Linia 30. z plus znak równości d średnik. Linia 31. zamknij nawias klamrowy. Linia 32. zamknij nawias klamrowy. Linia 33. i plus plus średnik. Linia 34. d plus znak równości 2 średnik. Linia 35. a znak równości a plus 2 asterysk otwórz nawias okrągły d minus 1 zamknij nawias okrągły średnik. Linia 36. zamknij nawias klamrowy. Linia 37. zamknij nawias klamrowy.

Implementacje algorytmu kończymy, wypisując wszystkie niewykreślone liczby pierwsze, zaczynając od liczby 2. Wewnątrz pętli for sprawdzamy, czy wartość zapisana pod indeksem i przechowuje wartość true. Jeżeli warunek jest prawdziwy, wówczas liczymy wartość zmiennej k zgodnie z wcześniej przedstawionym wzorem i drukujemy na standardowym wyjściu.

Linia 1. kratka include otwórz nawias ostrokątny iostream zamknij nawias ostrokątny. Linia 3. using namespace std średnik. Linia 6. int main otwórz nawias okrągły zamknij nawias okrągły otwórz nawias klamrowy. Linia 7. int n znak równości 0 średnik. Linia 8. cin zamknij nawias ostrokątny zamknij nawias ostrokątny n średnik. Linia 10. if otwórz nawias okrągły n procent 2 znak równości znak równości 1 zamknij nawias okrągły otwórz nawias klamrowy. Linia 11. n plus znak równości 1 średnik. Linia 12. zamknij nawias klamrowy. Linia 14. int polowa znak równości n prawy ukośnik 2 średnik. Linia 15. bool tab otwórz nawias kwadratowy polowa zamknij nawias kwadratowy średnik. Linia 17. for otwórz nawias okrągły int x znak równości 0 średnik x otwórz nawias ostrokątny polowa średnik x plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 18. tab otwórz nawias kwadratowy x zamknij nawias kwadratowy znak równości true średnik. Linia 19. zamknij nawias klamrowy. Linia 21. int i znak równości 1 średnik. Linia 22. int d znak równości 3 średnik. Linia 23. int a znak równości 4 średnik. Linia 25. while otwórz nawias okrągły a otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 26. if otwórz nawias okrągły tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości znak równości true zamknij nawias okrągły otwórz nawias klamrowy. Linia 27. int z znak równości a średnik. Linia 28. while otwórz nawias okrągły z otwórz nawias ostrokątny polowa zamknij nawias okrągły otwórz nawias klamrowy. Linia 29. tab otwórz nawias kwadratowy z zamknij nawias kwadratowy znak równości false średnik. Linia 30. z plus znak równości d średnik. Linia 31. zamknij nawias klamrowy. Linia 32. zamknij nawias klamrowy. Linia 33. i plus plus średnik. Linia 34. d plus znak równości 2 średnik. Linia 35. a znak równości a plus 2 asterysk otwórz nawias okrągły d minus 1 zamknij nawias okrągły średnik. Linia 36. zamknij nawias klamrowy. Linia 38. cout otwórz nawias ostrokątny otwórz nawias ostrokątny 2 otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów cudzysłów średnik. Linia 39. for otwórz nawias okrągły int i znak równości 1 średnik i otwórz nawias ostrokątny polowa średnik i plus plus zamknij nawias okrągły otwórz nawias klamrowy. Linia 40. if otwórz nawias okrągły tab otwórz nawias kwadratowy i zamknij nawias kwadratowy znak równości znak równości true zamknij nawias okrągły otwórz nawias klamrowy. Linia 41. int k znak równości 2 asterysk i plus 1 średnik. Linia 42. cout otwórz nawias ostrokątny otwórz nawias ostrokątny k otwórz nawias ostrokątny otwórz nawias ostrokątny cudzysłów cudzysłów średnik. Linia 43. zamknij nawias klamrowy. Linia 44. zamknij nawias klamrowy. Linia 45. zamknij nawias klamrowy.

Przykład działania  kodu dla .

R7VP8i7pXT4c6
Wybierz jedno nowe słowo poznane podczas dzisiejszej lekcji i ułóż z nim zdanie.

Słownik

liczba pierwsza
liczba pierwsza

liczba naturalna większa od 1, która dzieli się tylko przez jeden i przez samą siebie

sito Eratostenesa
sito Eratostenesa

algorytm, który przesiewa liczby z określonego zakresu w taki sposób, że zostają w nim tylko liczby pierwsze; służy on do znajdywania wszystkich liczb pierwszych w podanym przedziale

wielokrotność liczby
wielokrotność liczby

wielokrotność liczby a to taka liczba b, która powstaje przez pomnożenie liczby a przez liczbę naturalną n