どこかおかしいアルゴリズム「エラトステネスの篩」

お久しぶりです。若干伸びたので作ってみました(これは罠で、もともと書いてたやつをちょこっと変えただけ)

コード

#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等を参照のこと)。

  1. 2~Nの自然数をリストアップする
  2. 2を素数認定し、2の倍数をリストから全て消していく
  3. {消されてない or 素数認定されてない}自然数の中で最小のものを素数認定(それ未満の素数では割り切れない)し、その倍数を消していく
  4. 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にしておくという修正も有効です。

これらの点を修正すれば、このコードは正しく動作します。

まとめ

端っこには気をつけよう!