Forum: PC-Programmierung Sieb des Erastosthenes Frage zum Quellcode


von Hano Glahr (Gast)


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
}

von Der Pfnott (Gast)


Lesenswert?

Und erfuellt er die funktion ?

von Schoof (Gast)


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.

von P. M. (o-o)


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.

von MaWin (Gast)


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.

von Yalu X. (yalu) (Moderator)


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.

von Salewski, Stefan (Gast)


Lesenswert?

Yalu X. schrieb:
> Dank der oben beschriebenen Optimierungen

Du hättest (ergäntend) natürlich auch auf Rosetta-Code verweisen können

http://rosettacode.org/wiki/Sieve_of_Eratosthenes#C.2B.2B

von Hano Glahr (Gast)


Lesenswert?

Danke euch für die Ratschläge, da steckt wirklich noch deutlich 
optimierungspotential drin.

von Operator S. (smkr)


Lesenswert?

Schau dir in dem Zusammenhang auch noch iteratoren zu std::vector an, 
insbesondere bei for Schlaufen.

von Simpel (Gast)


Lesenswert?

Alle Primzahlen >3 liegen auf 6n(+-1).
Damit müssen nur 2/3 aller ungeraden Zahlen getestet werden.

von Salewski, Stefan (Gast)


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.

von Hano G. (Gast)


Lesenswert?

> Übrigens ergibt sich bei mir durch bool auch eine um den Faktor 2.3
> grössere Geschwindigkeit, im Vergleich zu char.

Wie wird das gemessen?

von Salewski, Stefan (Gast)


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.