どこかおかしいエラトステネスの篩
・1の倍数から消していく— よりもいはいいぞ(Ge|しとお) (@sitositositoo) 2018年11月19日
お久しぶりです。若干伸びたので作ってみました(これは罠で、もともと書いてたやつをちょこっと変えただけ)
コード
#include <vector> using namespace std; vector<int> SOE(int n) { vector<bool> isp(n + 1, true); int mp = 0; while (mp * mp <= n) { for (int i = mp + 1; i <= n + 1; i++) { if (i == n + 1) mp = n; //次の素数がない場合 if (isp[i]) { mp = i; break; } } for (int i = mp * 2; i <= n; i += mp) { isp[i] = false; } } vector<int> ret; for (int i = 1; i <= n; i++) { if (isp[i]) ret.push_back(i); } return ret; }
著作権は放棄します。
使用例
サンプルコード:
#include <iostream> #include <vector> using namespace std; vector<int> SOE(int n) { //省略 } int main() { int n = 3141592; vector<int> prime = SOE(n); for (int i : prime) cout << i; return 0; }
実行結果:
1
このコードのどこがいけないのか
N以下の素数のリストを得られる「エラトステネスの篩」とは、次のようなアルゴリズムです(詳しくはエラトステネスの篩 – Wikipedia等を参照のこと)。
- 2~Nの自然数をリストアップする
- 2を素数認定し、2の倍数をリストから全て消していく
- {消されてない or 素数認定されてない}自然数の中で最小のものを素数認定(それ未満の素数では割り切れない)し、その倍数を消していく
- 3を繰り返し、すべての自然数が消された、または素数認定された状態になるまで続ける(具体的には、√N以下の自然数を試せば必ず終わります)
今回のコードでは自然数のリストとしてbool(真偽値)型の配列ispを使い、isp[K]をtrueからfalseにすることで「Kを消す」を表現しています。また、while文が4の操作、while内の1つ目のfor文が素数認定、2つ目のfor文が倍数消しを行っています。詳しくはコメント欄かtwitterのDMにでも。
このように対応付けすると、おかしい部分が見えてきますね。
- mpの初期値を0にしている(最初の素数としてmp+1=1を認定してしまっている)
1を素数認定してしまうと、2以降のすべての自然数が倍数消しで消されてしまいますね。mpは1で初期化すべきです。
- 出力のfor文で、消されたかどうかの判定を1から行っている
isp[0]~isp[N]までの範囲をすべてtrueで初期化しているので、1個めの修正点を直した後ここも直さないと、出力結果に1が含まれてしまいます。もちろん、isp[0]とisp[1]を最初からfalseにしておくという修正も有効です。
これらの点を修正すれば、このコードは正しく動作します。
まとめ
端っこには気をつけよう!