最初は基本的な実装を行い、徐々に高速化していきます。最終的には『エラトステネスの篩』を実装します。
また、それぞれのアルゴリズムのオーダーを見積り、実際に実行速度を計測します。
まずは、基本的な実装から。これ。
void getPrime1(int n) {
VI prime;
FOR (i, 2, n) {
bool isPrime = true;
FOR (j, 2, i) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime)
prime.PB(i);
}
printf("%d\n", prime.size());
}
まー、だれでも思いつくような方法。
少し考えてみると、整数iが素数か否かを判定するのにi-1までの割り算をしなくても、sqrt(i)までの割り算をすればよいことに気付く。これをふまえて、改良するとこうなる。
void getPrime2(int n) {
VI prime;
FOR (i, 2, n) {
bool isPrime = true;
FOR (j, 2, (int)sqrt(i) + 1) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime)
prime.PB(i);
}
printf("%d\n", prime.size());
}
ちょっと変わっただけですが、かなり速くなりそうです。
次に、「エラトステネスの篩」を用いた実装。(アルゴリズムの考え方については、参考ページ参照のこと。)ソースを見てもらえば何をしているか分かると思います。
void getPrime3(int n) {
bool *isPrime = new bool[n];
VI prime;
memset(isPrime, 1, sizeof(bool) * n);
for (int i = 2; i < sqrt(n) + 1; i++) {
if (!isPrime[i])
continue;
for (int j = i + i; j < n; j += i)
isPrime[j] = 0;
}
FOR(i, 2, n)
if (isPrime[i])
prime.PB(i);
delete [] isPrime;
printf("%d\n", prime.size());
}
ここまで、3つの方法を記述しました。それぞれの計算量を見積ってみましょう。そして実際にn=1,000,000のときの実行速度を計測してみましょう。
#include <sys/time.h>
inline double gettimeofday_sec() {
struct timeval t;
gettimeofday(&t, NULL);
return (double) t.tv_sec + (double) t.tv_usec * 1e-6;
}
int main() {
double s = gettimeofday_sec();
getPrime1(1000000);
printf("%lf\n", gettimeofday_sec() - s);
s = gettimeofday_sec();
getPrime2(1000000);
printf("%lf\n", gettimeofday_sec() - s);
s = gettimeofday_sec();
getPrime3(1000000);
printf("%lf\n", gettimeofday_sec() - s);
return 0;
}
結果は以下のとおりになりました。
アルゴリズム | オーダー | 実行時間[s] |
getPrime1 | n^2 | 157.9 |
getPrime2 | n^1.5 | 6.943 |
getPrime3 | n | 0.016 |
やっぱりエラトステネス速いですね。。getPrime1, getPrime2のオーダーは見てのとおりですが、getPrime3(エラトステネス)のオーダーは少し分かり難いので少し補足します。
まず、一番外側のループは見てのとおりO(n^0.5)です。次に内側のループですが、n/i回まわります。
iによって値が違うので平均値を考えます。i は 2 から n^0.5 までなので、内側のループの回数は、それぞれのiに対して、n/2, n/3, n/4, ...... n/n^0.5となります。この平均は、およそn/{(2+n^0.5)/2}なので内側のループの回数はO(n^0.5)です。よって2重ループ部分のオーダーは、O(n^0.5) * O (n^0.5) = O(n)です。最後にisPrimeを線形探索する部分もO(n)なので、最終的にエラトステネスのオーダーは、O(n)となります。
参考ページ:
0 件のコメント:
コメントを投稿