Versuchen Sie nicht zu laufen, bevor Sie die Kunst des Gehens gelernt haben.
Verwenden Sie keine Arrays mit fester Größe wie int primes[100]
, es sei denn, Sie müssen.
Ein Grund dafür ist, dass dies erfordert, dass Sie, der Programmierer, die erforderliche Größe bestimmen vorne - zum Beispiel durch in die nth Prime Page gehen und herauszufinden, dass es 148.933 Primzahlen unter 2000000
Es auch erfordert, der Programmierer, um zusätzliche Prüfungen zu Ihrem Code hinzuzufügen, um sicherzustellen, dass die Array-Zugriffe die Grenzen des Arrays nicht überschreiten (es sei denn, Sie verwenden eine Sprache, die dies für Sie tut, wie Java oder C#). Ein weiterer Grund besteht darin, dass Sie Code zur Buchführung hinzufügen müssen, d. H. Um zu verfolgen, wie viele Zellen des Arrays gegenwärtig belegt sind.
Zu guter Letzt führt das Zuweisen eines Arrays aus 148933 Ganzzahlen als eine automatische Variable (d. H. Auf dem Stapel) wahrscheinlich zu einem Absturz, weil es den Stapel bläst.
Wenn Sie std::vector<>
verwenden, verschwinden alle diese Kopfschmerzen sofort und Ihr Code wird viel einfacher.
Beginnen Sie mit einem einfachen Plan und implementieren Sie die Schritte mit separaten Code. Es ist leichter, die Übersicht zu behalten, wenn jeder Code eine einfache, klar definierte Verantwortung hat. Schwieriger wird es, wenn man alles in einen großen, schlechten Klumpen verwickelt. Wenn Sie zum Beispiel die Primzahlen speichern, die Sie in einem Vektor gefunden haben, können Sie sich dort die Zahlen ansehen, um zu sehen, ob alles in Ordnung ist, und sie vielleicht mit bekannten Listen von Primzahlen vergleichen (wie The First 10,000 Primes oder die Primzahlen) bis zu 1.000.000.000.000 bei primos.mat.br). Du kannst schauen, aber du musst nicht. Wenn Sie alles mit Ausgabecode durchsetzen, dann müssen Sie immer alle ansehen. Wenn Sie sie nur zur Summe hinzufügen, können Sie sie nicht sehen, es sei denn, Sie debuggen Ihr Programm und folgen jedem einzelnen Schritt.
Formulieren Sie Ihren Plan als Pseudocode, damit Sie ihn auf einen Blick erfassen und vollständig verstehen können. Wenn Sie keinen Plan haben oder ihn nicht verstehen, ist das Ergebnis sehr wahrscheinlich cr * p.
for each candidate n between 2 and 2000000
for each known prime p up to sqrt(n)
if p divides n
break
if no divisor p was found // must be a new prime
add n to the list of found primes
Offensichtlich ist das Kriterium ‚wenn kein Teiler p gefunden wurde‘ erfordert, dass Sie eine Flagge zu verwenden, wie divisor_found
, die zu false
vor der inneren Schleife initialisiert werden. Daher die erste Verfeinerung:
for each candidate n between 2 and 2000000
divisor_found := false
for each known prime p up to sqrt(n)
if p divides n
divisor_found := true
break
if not divisor_found // must be a new prime
add n to the list of found primes
Dies kann ohne weiteres implementiert werden. Die Aufzählung der Kandidaten kann durch Überspringen einiger Zahlen verbessert werden, die möglicherweise nicht prim sein kann, wie ein Vielfaches von zwei:
add 2 to the list of found primes
for each odd number between 3 and 2000000
...
Dies sofort schneidet Ihre Arbeit in der Hälfte, und es ist das einfachste Beispiel eines 'wheel'. Für Probleme wie dieses ist es sehr praktisch, auch ein Vielfaches von 3 zu überspringen (mod 6 Rad), indem man mit 5 beginnt und abwechselnd um 2 und 4 erhöht.
add 2 and 3 to the list of found primes
for n = 5 to 2000000 step 6 // 6 = 2 * 3
try n
try n + 2
Hier wird die Probedivision in try
getan braucht nicht 2 oder 3 als potentielle Teilern zu betrachten, weil Ihre Methode der Aufzählen Kandidaten bereits alle Multiples ausschließt. Die Erweiterung auf das Auslassen von Vielfachen von 5 ist ebenfalls ziemlich einfach.
Wenn Sie während jeder Iteration der innersten Schleife eine teure Berechnung wie sqrt(n)
durchführen, wird Ihr Code auf Crawl verlangsamt. n
ändert sich nicht während der Lebensdauer der inneren Schleife, also berechnen Sie den Wert einmal im Schleifenkopf, anstatt unnötigerweise die Berechnung zu wiederholen.
Wie dem auch sei, das zufällige Ausprobieren verschiedener ganzzahliger Datentypen wird Sie nirgendwo hinbringen. Wenn Werte nicht negativ werden können - wie hier - dann sollte unsigned
Ihre erste Wahl sein. Auf aktuellen Systemen entspricht dies normalerweise uint32_t
, was mehr als genug ist für die kleinen Zahlen, die an dieser Euler-Aufgabe beteiligt sind. Sie können sich ein wenig Ärger ersparen, indem Sie einen geeigneten Typedef einführen; entstehen müssen auf diese Weise müssen Sie nur eine einzige Definition ändern sollte:
typedef std::uint32_t num_t;
typedef std::uint64_t sum_t;
num_t const N = 2000000;
...
std::vector<num_t> primes;
...
for (num_t n = 3; n <= N; n += 2)
...
sum_t sum = 0;
for (num_t p: primes)
sum += p;
Ich habe eine separate sum_t
auch hinzugefügt, da eine grobe Schätzung der Summe es weit über die Kapazität eines uint32_t
setzt.
In jedem Fall sollten Sie ernsthaft die Sieve of Eratosthenes hier verwenden. Es ist einfacher als eine fahrbare Versuchsaufteilung und um mehrere Größenordnungen schneller - selbst das einfachste Rendering sollte diese Euler-Aufgabe in wenigen Millisekunden lösen.
Können Sie hinzufügen, was Sie bekommen und was Sie bekommen möchten? – roadrunner66