Liczby pierwsze – generacja przez sprawdzanie podzielności


Tematy pokrewne   Podrozdziały
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
  Rozwiązanie 1
Rozwiązanie 2

 

Problem

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

 

 

Liczba naturalna p jest liczbą pierwszą (ang. prime number) posiadającą dokładnie dwa różne podzielniki: 1 i siebie samą.

W informatyce liczby pierwsze posiadają olbrzymie zastosowanie – np. w kryptografii, czyli przy szyfrowaniu i rozszyfrowywaniu informacji. Jak jest to ważne dla handlu i bankowości w sieci, chyba nie trzeba nikogo przekonywać. Dlatego znajomość sposobów generacji liczb pierwszych jest obowiązkowa dla każdego informatyka.

 

Rozwiązanie 1

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

 

Algorytm wyznaczania liczb pierwszych przez sprawdzanie podzielności

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

n kolejnych liczb pierwszych

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
K04:     Dla d = 2,3,...,p -1, wykonuj krok 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:     lplp + 1 ; zwiększamy licznik wygenerowanych liczb pierwszych
K08:     pp + 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;

  cin >> n;
  lp = 0;
  p  = 2;
  while(lp < n)
  {
    t = true;
    for(d = 2; d < p; d++)
      if(p % d == 0)
      {
        t = false;
        break;
      }
    if(t)
    {
      cout << p << " ";
      lp++;
    }
    p++;
  }
  cout << endl;
  return 0;
}
Free Basic
' Liczby pierwsze
' Data   : 15.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------

Dim As Uinteger n,lp,p,d,t

Input n
lp = 0
p  = 2
While lp < n
  t = 1
  For d = 2 To p - 1
    If p Mod d = 0 Then
      t = 0
      Exit For
    End If
  Next
  If t = 1 Then
    Print p;" ";
    lp += 1
  End If
  p += 1
Wend
Print
End
Wynik
25
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

 

Rozwiązanie 2

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

Jeśli liczba p jest złożona, to rozkłada się na iloczyn czynników pierwszych:

 

p = d1 × d2 × ... × dk

 

Czynników tych musi być przynajmniej 2 i muszą one być różne od 1 i p (dlaczego?). Prosta analiza pokazuje nam, iż w przedziale od pierwiastka z p do p - 1 może leżeć co najwyżej jeden czynnik. Gdyby było ich więcej, to ich iloczyn przekroczyłby wartość liczby p. Skoro drugi czynnik nie może być większy od pierwiastka z p, to musi być od niego mniejszy lub równy. Z kolei przy teście na pierwszość liczby p wystarczy nam, iż znajdziemy pierwszą podzielność, aby wyeliminować p. Wynika z tego prosty wniosek – podzielność liczby p wystarczy sprawdzić dla dzielników z przedziału od 2 do pierwiastka całkowitego z p. Jeśli żaden podzielnik z tego przedziału nie dzieli p, to p jest pierwsze, ponieważ w pozostałej części przedziału <2, p-1>nie mogą już wystąpić czynniki p.

Drugie ulepszenie będzie polegało na eliminacji niektórych wartości p. Na przykład jedyną liczbą pierwszą parzystą jest 2. Wszystkie inne są już nieparzyste. Możemy pójść dalej tym tropem i dokonać dalszych eliminacji. Dwoma początkowymi liczbami pierwszymi są liczby 2 i 3. Zatem z ciągu dalszych kandydatów na liczby pierwsze możemy wyeliminować wszystkie wielokrotności liczb 2 i 3, takie jak 6, 8, 9, 10, 12, 14,15... Liczby te mają postać 6k, 6k±2 i 6k±3, dla k = 1,2,.... Pozostają jedynie do sprawdzenia liczby postaci 6k±1, a tych jest już zdecydowanie mniej.

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

 

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

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

n kolejnych liczb pierwszych. Wygenerowane liczby pierwsze znajdują się również w kolejnych n elementach tablicy tlp. Indeksy rozpoczynają się od 0

Zmienne pomocnicze
lp  –  zlicza kolejno wygenerowane liczby pierwsze. lp N
p   kolejno testowane liczby naturalne. p N
g  – zawiera pierwiastek całkowity z p. g N
k  – używane do generacji liczb p, k N
d  – wraz z k używane do generacji liczb p, d Z
tlp  – tablica liczb pierwszych. tlp[i] N, dla i = 0,1,...,n-1
i  – wykorzystywane przy numeracji podzielników z tlp. i N
Lista kroków:
K01: lp ← 0 ; ustawiamy licznik liczb pierwszych
K02: k ← 1;   d ← 1; p ← 2 ; oraz zmienne do generacji p = 6k±1
K03: Dopóki lp < n, wykonuj kroki K04...K16 ; w pętli znajdujemy kolejne liczby pierwsze
K04:     Jeśli lp < 3, to pp + 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;  kk + 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 pętli K03 ; sprawdzamy podzielność p przez podzielniki z tlp
K13:         ii + 1 ; indeks następnego podzielnika
K14     tlp[lp] ← p ; liczbę pierwszą zapamiętujemy w tlp
K15:     Pisz p  
K16:     lplp + 1  
K17: Zakończ  

 

Program

Ważne:

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

 

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

 

Lazarus Code::Blocks Free Basic
// Liczby pierwsze
// Data   : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------

program prg;

type
  Tlwarray = array of longword;

var i,k,g,n,lp,p : longword;
    d   : integer;
    t   : boolean;
    tlp : Tlwarray;

begin
  readln(n);
  setlength(tlp,n);
  lp := 0; k  := 1; d  := 1; p := 2;
  while lp < n do
  begin
    t := true;
    if lp < 3 then inc(p,lp)
    else
    begin
      p := 6 * k + d;
      if d = 1 then
      begin
        d := -1; inc(k)
      end
      else d := 1;
      g := round(sqrt(p));
      i := 2;
      while tlp[i] <= g do
      begin
        if p mod tlp[i] = 0 then
        begin
          t := false;
          break
        end;
        inc(i)
      end
    end;
    if t then
    begin
      tlp[lp] := p;
      write(p,' ');
      inc(lp)
    end
  end;
  writeln;
  SetLength(tlp,0);
end.
// Liczby pierwsze
// Data   : 15.03.2008
// (C)2012 mgr Jerzy Wałaszek
//----------------------------

#include <iostream>
#include <cmath>

using namespace std;

int main()
{
  unsigned int i,k,g,n,lp,p, * tlp;
  int d;
  bool t;

  cin >> n;
  tlp = new unsigned int [n];
  lp = 0; k  = 1; d  = 1; p = 2;
  while(lp < n)
  {
    t = true;
    if(lp < 3) p += lp;
    else
    {
      p = 6 * k + d;
      if(d == 1)
      {
        d = -1; k++;
      }
      else d = 1;
      g = (unsigned int)sqrt(p);
      for(i = 2; tlp[i] <= g; i++)
        if(!(p % tlp[i]))
        {
          t = false;
          break;
        }
    }
    if(t)
    {
      tlp[lp++] = p;
      cout << p << " ";
    }
  }
  cout << endl;
  delete [] tlp;
  return 0;
}
' Liczby pierwsze
' Data   : 15.03.2008
' (C)2012 mgr Jerzy Wałaszek
'----------------------------

Dim As Uinteger i,k,g,n,lp,p,t
Dim As Integer d
  
Input n

Dim As Uinteger tlp(n)

lp = 0: k  = 1: d  = 1: p = 2
While lp < n
  t = 1
  If lp < 3 Then
    p += lp
  Else
    p = 6 * k + d
    If d = 1 Then
      d = -1: k += 1
    Else
      d = 1
    End If
    g = Cuint(Sqr(p))
    i = 2
    While tlp(i) <= g
      If p Mod tlp(i) = 0 Then
        t = 0: Exit While
      End If  
      i += 1
    Wend
  End If
  If t = 1 Then
    tlp(lp) = p
    Print p;" ";
    lp += 1
  End If
Wend
Print
End

 

 

Generacja liczb pierwszych
(C)2012 mgr Jerzy Wałaszek

n =


...

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

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

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

Liczba znaków do wykorzystania: 2048

 

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   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.