Sieb des Erastosthenes Frage zum Quellcode

Gast #4244925
Lesenswert?

Hallo zusammen, ich arbeite gerade ein C++ Buch durch (will die 
Programmiersprache erlernen) und da wird u.a. die Aufgabe gestellt das 
"Sieb des Erastosthenes" nachzuprogrammieren um damit zB Primzahlen 
zwischen 0...100 zu finden.

Ich würde gern meinen Code posten, vielleicht erbarmt sich der Ein oder 
Andere und sagt mir ob er zu kompliziert ist und was man einfacher 
machen kann :-) Ich bin froh das hinbekommen zu haben, es ist quasi 
eines meiner ersten Erfolgserlebnisse bei komplizierteren 
Aufgabenstellungen :-)
1
int main()
2
{
3
    vector<int> primes;
4
    for(int i=0; i<=100; ++i)
5
        primes.push_back(i);
6
    primes[1]=0;                // 1 ist keine Primzahl
7

8
    // Sieb des Erastosthenes
9
    int step = 2;
10
    int startval = step*2;
11
    while(step < 100) {
12
        for(int j=startval; j<primes.size(); j+=step)
13
            primes[j]=0;
14
        ++step;
15
        startval=step*2;
16
    }
17

18
    // Gefundene Primzahlen zur kontrolle ausgeben
19
    for(int k=0; k<primes.size(); ++k)
20
        cout << primes[k] << endl;
21

22
    return 0;
23
}
Gast #4245118
Lesenswert?

Ja funktioniert tadellos. Wollte wissen ob man das auf andere weise 
besser machen kann.
Ich habe das Sieb des Erasto... so verstanden dass man da nichts 
berechnen braucht, sondern systematisch die nichtprimzahlen 
rausstreicht.
#4245134
Lesenswert?

Sieht ganz gut aus, wie ich finde.

Kleiner Tipp: Auch wenn du nach if/for/while nur ein einziges Statement 
hast, setze es IMMER in geschweifte Klammern. Alles andere kann zu 
mühsam auffindbaren Bugs führen, wenn mal die Formatierung verrutscht.
Gast #4245146
Lesenswert?

Hano Glahr schrieb:
> cout << primes[k] << endl;

Ich würde 0-en nicht ausgeben.

Auch muss in den Primzahlelementen ja keine Zahl stehen, die kennt man 
schon durch die Position, sondern es reicht eine 0 oder 1 (true/false).

Das spart zwar erst Speicher wenn man es in Assembler programmiert, aber 
vielleicht möchte man doch mal die ersten 100 Milliarden Primzahlen 
kennen lernen, das geht mit dem blöden PC unterm Tisch heute schon 
(6.25GB).

Gerade Zahlen muss man sowieso nicht speichern.
Moderator #4245196
Lesenswert?

Das Programm sieht bis auf ein paar Kleinigkeiten ganz gut aus:

1. Die Konstante 100 taucht zweimal auf. Wenn du die Primzahlen bis 1000
   berechnen möchtest, musst du das Programm deswegen an zwei Stellen
   ändern und wehe, du vergisst eine davon. Besser ist es, der Konstante
   einen Namen (bspw. n) zu geben.

2. Da der Vektor für jedes Element nur die Information "ist prim" bzw.
   "ist nicht prim" enthalten muss, genügen Bool-Werte. Das spart bei
   großen n viel Speicher, vor allem dann, wenn die einzelnen Bool-Werte
   als gepackte Bits gespeichert werden, wie es bei den meisten neueren
   Implementationen der C++-Standardbibliothek der Fall ist.

3. Da in diesem Fall alle Elemente bis auf die ersten beiden den
   gleichen Initialisierungwert (true) bekommen, kann man die
   Initialisierung gleich im Konstruktor von vector vornehmen.

4. Es müssen nicht die Vielfachen von jeder Zahl, sondern nur die
   Vielfachen der Primzahlen herausgestrichen werden. Die jeweils
   nächste Primzahl findet man leicht durch lineare Suche in primes.

5. Mit dem Herausstreichen der Zahlen muss man nicht beim Doppelten,
   sondern kann beim Quadrat der aktuellen Primzahl beginnen. Alle
   kleineren Vielfachen wurden bereits in früheren Schleifendurchläufen
   herausgestrichen.

6. Ist das Quadrat der aktuellen Primzahl größer als der größte Index in
   primes, ist der Siebalgorithmus abgeschlossen.

7. endl unterscheidet sich von '\n' in einem ostream (cout) dadurch,
   dass es den Stream zusätzlich flusht. Insbesondere beim Schreiben von
   Dateien kostet der Flush nicht unerheblich Zeit, weswegen dort
   i.Allg. '\n' vorzuziehen ist. Bei der Bildschirmausgabe spielt das
   keine Rolle, aber vielleicht möchtest du die Ausgabe Primzahlen auch
   einmal in eine Datei umleiten. Deswegen nehme ich standardmäßig immer
   '\n' statt endl, es sei denn, es gibt spezielle Gründe für den Flush.

8. Du verwendest vector, cout und endl ohne die Angabe des Namespace
   (std::). Falls du dafür ein "using namespace std;" machst, ist das
   bei diesem Mini- und Wegschmeißprogramm in Ordnung, aber lies dir
   dieses durch:

     http://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

   Besser ist es, jedem Symbol auf der Standardbibliothek den Präfix
   std:: voranzustellen (was leider Sch... aussieht) oder die
   tatsächlich benutzten Symbole einzeln mit "using std::vector;",
   "using std::cout;" usw. bekannt zu machen (was leider viel
   Schreibarbeit ist).

Hier ist eine Modifikation deines Programms mit Berücksichtigung der
obigen Punkte:

1
#include <iostream>
2
#include <vector>
3

4
int main() {
5
  const int n = 100;
6
  std::vector<bool> primes(n, true);
7
  primes[0] = primes[1] = false;
8

9
  // Sieb des Erastosthenes
10
  int step = 0;
11
  while(true) {
12
    while(!primes[++step]);   // nächste Primzahl suchen
13
    int startval = step * step;
14
    if(startval >= n)
15
      break;
16
    for(int i=startval; i<n; i+=step)
17
      primes[i] = false;
18
  }
19

20
  // Gefundene Primzahlen zur kontrolle ausgeben
21
  for(int i=0; i<n; ++i) {
22
    if(primes[i])
23
      std::cout << i << '\n';
24
  }
25

26
  return 0;
27
}

Dank der oben beschriebenen Optimierungen des Algorithmus läuft das
geänderte Programm für n=10000000 (10⁷) ohne die Ausgabe der Ergebnisse
etwa um den Faktor 80 schneller.
Gast #4247508
Lesenswert?

Yalu X. schrieb:
> 2. Da der Vektor für jedes Element nur die Information "ist prim" bzw.
>    "ist nicht prim" enthalten muss, genügen Bool-Werte.

Übrigens ergibt sich bei mir durch bool auch eine um den Faktor 2.3 
grössere Geschwindigkeit, im Vergleich zu char. Das hatte ich so nicht 
erwartet. Wird wohl daran liegen, dass  der Cache besser genutzt werden 
kann.
Gast #4247572
Lesenswert?

Hano G. schrieb:
> Wie wird das gemessen?

Na ja, auf die Ausgabe der Werte verzichten, und dann unter Linux in der 
Shell "time ./sieve". Ich hatte mit n=1e7 getestet. Für char statt bool 
braucht man ja fast nichts ändern, nur z.B. statt primes[i] = false dann 
eben primes[i] = 0. Das ist mal ein Fall, wo dann C++ schneller ist als 
C oder Nim.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren