C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で素数を見つける最速のアルゴリズムとは?エラトステネスの篩を徹底解説

nがおよそ1000万以下の規模である場合、n未満の素数を高速に求める方法として、最も効率的なアルゴリズムのひとつが「エラトステネスの篩(ふるい)」です。この手法は計算量がO(n log log n)と非常に効率的で、競技プログラミングから実務まで幅広く活用されています。

エラトステネスの篩とは

エラトステネスの篩は、古代ギリシャの数学者エラトステネスによって考案された古典的な素数列挙アルゴリズムです。2からnまでの整数を順に走査し、それぞれの素数の倍数を順次「ふるい落とす」ことで、最終的に残った数だけを素数として抽出します。

サンプルプログラム

以下は、エラトステネスの篩をC++で実装したプログラムの例です。

#include <bits/stdc++.h>
using namespace std;

void SieveOfEratosthenes(int num) {
    bool pno[num + 1];
    memset(pno, true, sizeof(pno));
    for (int i = 2; i * i <= num; i++) {
        if (pno[i] == true) {
            for (int j = i * 2; j <= num; j += i)
                pno[j] = false;
        }
    }
    for (int i = 2; i <= num; i++)
        if (pno[i])
            cout << i << " ";
}

int main() {
    int num = 15;
    cout << "The prime numbers smaller or equal to " << num << " are: ";
    SieveOfEratosthenes(num);
    return 0;
}

実行結果

上記プログラムの出力は以下のとおりです。

The prime numbers smaller or equal to 15 are: 2 3 5 7 11 13

プログラムの解説

それでは、上記プログラムの動作を詳しく見ていきましょう。

SieveOfEratosthenes() 関数

SieveOfEratosthenes() 関数は、引数として受け取った num 未満のすべての素数を求めます。まず、num+1 のサイズを持つブール型配列 pno を用意し、memset() ですべての要素を true で初期化します。その後、2から順に走査し、i が素数であれば i の倍数をすべて false に設定していきます。

ここで注目すべきは、外側のループ条件を i*i <= num としている点です。任意の合成数の最小の素因数は必ず √num 以下になるため、√num まで調べれば十分だからです。最後に、true のまま残っているインデックス、すなわち素数のみを出力します。

void SieveOfEratosthenes(int num) {
    bool pno[num + 1];
    memset(pno, true, sizeof(pno));
    for (int i = 2; i * i <= num; i++) {
        if (pno[i] == true) {
            for (int j = i * 2; j <= num; j += i)
                pno[j] = false;
        }
    }
    for (int i = 2; i <= num; i++)
        if (pno[i])
            cout << i << " ";
}

main() 関数

main() 関数では、変数 num に上限値を設定し、SieveOfEratosthenes() を呼び出すことで、num 以下のすべての素数を出力しています。

int main() {
    int num = 15;
    cout << "The prime numbers smaller or equal to " << num << " are: ";
    SieveOfEratosthenes(num);
    return 0;
}

まとめ

エラトステネスの篩は、nが約1000万程度までの範囲であれば、非常に高速に素数を列挙できる優れたアルゴリズムです。シンプルな実装ながら計算量 O(n log log n) を実現できるため、「指定された範囲内の素数をすべて求めたい」という場面では第一の選択肢となります。なお、さらに大きな数の素数判定が必要な場合には、Miller–Rabin法などの確率的素数判定法や、必要な区間だけを処理する「区間ふるい」の利用も検討するとよいでしょう。

  1. C++で文字列の部分文字列の総数を求める方法を解説

    この記事では、与えられた文字列から作成できる空でない部分文字列の個数を求める方法について解説します。入力 : string = "moon" 出力 : 10 説明 : 部分文字列は m、o、o、n、mo、oo、on、moo、oon、moon の 10 個です。 入力 : string = "yellow" 出力 : 21解法のアプローチ文字列の長さを n とします。上の例からも分かるように、考えられるすべての部分文字列の個数を求めるには、長さ n、(n-1)、(n-2)、(n-3)、……2、1 の部分文字列の個数を順に加算していく必要があります。部分文

  2. C++で列車の停車駅の組み合わせ数を求める方法

    地点XとYの間にはn個の中間駅があるとします。ここで、「どの2つの停車駅も隣り合わない」という条件のもとで、s個の駅に停車する列車の配置方法が何通りあるかを求める問題を考えてみましょう。この記事では、停車駅の組み合わせ数を求めるためのアプローチを段階的に詳しく解説します。この問題は、本質的には組合せ論の問題であり、s個の停車駅の選び方の総数を求めることになります。 問題を解くアプローチ まず具体例として、中間駅が8個あり、そのうち3個の駅に停車させたい場合を考えてみます。 n = 8, s = 3 このとき、列車が停車できない駅は(n − s)、つまり5個残ることになります。 停車できない