lasciva blog

開発して得た知見やwebビジネスのストック

素数の一覧を求めるアルゴリズムのメモ

素数の一覧を求めるアルゴリズムの問題を出されて、良い計算量のアルゴリズムがわからなかったときに調べたメモです。

素数の一覧を求めるアルゴリズム

素数の定義

学術的には不正確かもしれないですが、今回はwikipediaで十分なので、こちらで進めます。

1 より大きい自然数で、正の約数が 1 と自分自身のみであるもの

素数の一覧 - Wikipedia

普通の方法

約数が1以外は自身のみなので、自身より小さい素数で割り切れるものが一つも無ければ、自身は素数となります。

def normal(max_number)
  (2..max_number).each_with_object([]) do |i, primes|
    primes << i if primes.detect {|p| i % p == 0 }.blank?
  end
end

この方法では、計算量がO(N2)になるので効率が悪いです。

エラトステネスの篩(ふるい)

というアルゴリズムがあるそうです。

ステップ 1 探索リストに2からxまでの整数を昇順で入れる。

ステップ 2 探索リストの先頭の数を素数リストに移動し、その倍数を探索リストから篩い落とす。

ステップ 3 上記の篩い落とし操作を探索リストの先頭値がxの平方根に達するまで行う。

ステップ 4 探索リストに残った数を素数リストに移動して処理終了。

エラトステネスの篩 - Wikipedia

例はwikipediaにも詳しく載ってるので割愛します。

アルゴリズムのイメージ

上に示したステップだけだとよくわからなかったので、腹落ちするように流れを分解して解釈しました。

  • 数字のリストから、素数の小さいものから順にその素数の倍数(ex. 3の場合は 9, 12, 15...)を取り除いていけば、リストに残るのは素数のみになる。
  • また除外する際には、自身の2乗以上の倍数のみでよい。
    • 自身より小さい値との組み合わせは既に取り除かれているため(ex. 3の場合は 6は2の倍数なので、取り除かれている)
  • その倍数として取り除いていく素数の必要最小限の値は、リストの最大値の平方根になる。
    • 平方根の値より大きい値との倍数は、リストの最大値を超えるため
実装
def eratosthenes(max_number)
  primes = []
  max_sqrt = Math.sqrt(max_number).to_i
  options = (2..max_number).to_a

  prime = 1
  while prime <= max_sqrt
    prime = options.shift
    primes << prime
    multiples = (max_number / prime - (prime - 1) ).times.map {|m| prime * (m + prime) }
    options = options - multiples
  end
  primes + options
end

結果の比較

MAX_NUMBER = 10000

Benchmark.bm 10 do |r|
  r.report 'normal' do
    normal(MAX_NUMBER)
  end
  r.report 'Eratosthenes' do
    eratosthenes(MAX_NUMBER)
  end
end
MAX_NUMBER normal Eratosthenes
1000 0.001585 0.000413
10000 0.070549 0.004512
100000 3.807569 0.059691
1000000 248.255773 1.362347