web-dev-qa-db-ja.com

エラトステネスのセグメント化されたふるい?

簡単なふるいを作るのは簡単です:

for (int i=2; i<=N; i++){
    if (sieve[i]==0){
        cout << i << " is prime" << endl;
        for (int j = i; j<=N; j+=i){
            sieve[j]=1;
        }
    }
    cout << i << " has " << sieve[i] << " distinct prime factors\n";
}

しかし、Nが非常に大きく、その種の配列をメモリに保持できない場合はどうでしょうか。私はセグメント化されたふるいのアプローチを調べましたが、sqrt(N)まで素数を見つけることを伴うようですが、それがどのように機能するのか分かりません。 Nが非常に大きい場合(たとえば10 ^ 18)はどうなりますか?

35
John Smith

セグメント化されたふるいの基本的な考え方は、nの平方根よりも小さいふるい素数を選択し、それでもメモリに収まる合理的に大きなセグメントサイズを選択してから、セグメントを順番に、最小のものから始めます。最初のセグメントでは、セグメント内にある各ふるい素数の最小倍数が計算され、ふるい素数の倍数は通常の方法で複合としてマークされます。すべてのふるい素数が使用されると、セグメント内の残りのマークされていない数は素数になります。次に、次のセグメントについて、各ふるい素数について、現在のセグメントの最初の倍数(前のセグメントでその素数のふるいを終了した倍数である)を既に知っているので、各ふるい素数をふるいにかけます。終わるまで。

nのサイズは重要ではありませんが、大きいnは小さいn;重要なサイズはセグメントのサイズで、便利な限り大きくする必要があります(たとえば、マシン上のプライマリメモリキャッシュのサイズ)。

セグメント化されたふるいの簡単な実装を見ることができます こちら 。セグメント化されたふるいは、別の回答で言及されたオニールの優先度キューふるいよりも非常に高速であることに注意してください。興味があれば、実装があります here

EDIT:これを別の目的のために書きましたが、役に立つかもしれないのでここに示します:

エラトステネスのふるいは非常に高速ですが、O(n)スペースが必要です。これは、ふるい分けのためにO(sqrt(n))に減らすことができます。 primes plus O(1)連続するセグメントでふるい分けを実行することにより、ビット配列に対して。最初のセグメントで、セグメント内にある各ふるい分けプライムの最小の倍数が計算され、次にふるい分け素数は通常の方法で複合マークが付けられます;ふるい分け素数がすべて使用されると、セグメント内の残りのマークされていない数は素数になります。前のセグメントであるため、ふるいは終了するまで続きます。

20のセグメントで100から200のふるいの例を考えてみましょう。5つのふるい素数は3、5、7、11、13です。100から120の最初のセグメントでは、ビット配列は10個のスロットを持ち、スロット0は101 、スロットkは100 + 2k + 1に対応し、スロット9は119に対応します。セグメント内の3の最小倍数は105で、スロット2に対応します。スロット2 + 3 = 5および5 + 3 = 8も3の倍数です。5の最小の倍数はスロット2で105であり、スロット2 + 5 = 7も5の倍数です。7の最小の倍数は105です。スロット2で、スロット2 + 7 = 9も7の倍数です。

関数primesRangeは、引数lo、hi、およびdeltaを取ります。 loとhiは偶数でなければならず、lo <hiであり、loはsqrt(hi)より大きくなければなりません。セグメントサイズは2倍のデルタです。 Psは、sqrt(hi)より小さいふるい素数を含むリンクリストです。偶数は無視されるため、2は削除されます。 Qsは、対応するふるい素数の現在のセグメントの最小の倍数のSieveビット配列へのoffestを含むリンクリストです。各セグメントの後、loはデルタの2倍進むため、sieve bitarrayのインデックスiに対応する数値はlo + 2i + 1です。

function primesRange(lo, hi, delta)
    function qInit(p)
        return (-1/2 * (lo + p + 1)) % p
    function qReset(p, q)
        return (q - delta) % p
    sieve := makeArray(0..delta-1)
    ps := tail(primes(sqrt(hi)))
    qs := map(qInit, ps)
    while lo < hi
        for i from 0 to delta-1
            sieve[i] := True
        for p,q in ps,qs
            for i from q to delta step p
                sieve[i] := False
        qs := map(qReset, ps, qs)
        for i,t from 0,lo+1 to delta-1,hi step 1,2
            if sieve[i]
                output t
        lo := lo + 2 * delta

PrimesRange(100、200、10)として呼び出された場合、ふるい分け素数psは[3、5、7、11、13]です。 qsは最初は最小の倍数105、105、105、121、117に対応する[2、2、2、10、8]であり、2番目のセグメントでは最小に対応する[1、2、6、0、11]にリセットされます。 123、125、133、121、143の倍数。

このプログラムの動作は http://ideone.com/iHYr1f で確認できます。また、上記のリンクに加えて、素数を使用したプログラミングに興味がある場合は、ブログで エッセイ をお勧めします。

49
user448810

私たちが持っているふるいでセグメント化されているだけです。基本的な考え方は、85〜100の間の素数を見つける必要があるとしましょう。従来のふるいを適用する必要がありますが、以下に説明する方法で行います。

したがって、最初の素数2を取得し、開始数を2(85/2)で除算し、より小さな数に丸めてp = 42を取得し、再び2を乗算してpを取得します= 84、ここから先から最後の数字まで2の追加を開始します。そのため、範囲内の2(86,88,90,92,94,96,98,100)のすべての要因を削除しました。

次の素数3を取得し、開始数を3(85/3)で除算し、より小さい数に丸めてp = 28を取得し、3を再び乗算してp =を取得します84、ここから最後の数字まで3を追加し始めます。そのため、範囲内の3(87,90,93,96,99)のすべての要素を削除しました。

次の素数= 5などを取得します..................上記の手順を続けてください。素数(2,3,5,7、 ...)従来のふるいをsqrt(n)まで使用し、それをセグメント化されたふるいに使用します。

4
OneWhoKnocks

優先度キューに基づいたバージョンのSieveには、上限までのすべてではなく、要求された数の素数が生成されます。古典的な論文 "エラトステネスの本物のふるい" で議論されており、「エラトステネスのふるい優先キュー」のグーグルは、さまざまなプログラミング言語でかなりの実装を見つけます。

4
Fred Foo

誰かがC++の実装を見たいと思うなら、これが私のものです:

void sito_delta( int delta, std::vector<int> &res)
{

std::unique_ptr<int[]> results(new int[delta+1]);
for(int i = 0; i <= delta; ++i)
    results[i] = 1;

int pierw = sqrt(delta);
for (int j = 2; j <= pierw; ++j)
{
    if(results[j])
    {
        for (int k = 2*j; k <= delta; k+=j)
        {
            results[k]=0;
        }
    }
}

for (int m = 2; m <= delta; ++m)
    if (results[m])
    {
        res.Push_back(m);
        std::cout<<","<<m;
    }
};
void sito_segment(int n,std::vector<int> &fiPri)
{
int delta = sqrt(n);

if (delta>10)
{
    sito_segment(delta,fiPri);
   // COmpute using fiPri as primes
   // n=n,prime = fiPri;
      std::vector<int> prime=fiPri;
      int offset = delta;
      int low = offset;
      int high = offset * 2;
      while (low < n)
      {
          if (high >=n ) high = n;
          int mark[offset+1];
          for (int s=0;s<=offset;++s)
              mark[s]=1;

          for(int j = 0; j< prime.size(); ++j)
          {
            int lowMinimum = (low/prime[j]) * prime[j];
            if(lowMinimum < low)
                lowMinimum += prime[j];

            for(int k = lowMinimum; k<=high;k+=prime[j])
                mark[k-low]=0;
          }

          for(int i = low; i <= high; i++)
              if(mark[i-low])
              {
                fiPri.Push_back(i);
                std::cout<<","<<i;
              }
          low=low+offset;
          high=high+offset;
      }
}
else
{

std::vector<int> prime;
sito_delta(delta, prime);
//
fiPri = prime;
//
int offset = delta;
int low = offset;
int high = offset * 2;
// Process segments one by one 
while (low < n)
{
    if (high >= n) high = n;
    int  mark[offset+1];
    for (int s = 0; s <= offset; ++s)
        mark[s] = 1;

    for (int j = 0; j < prime.size(); ++j)
    {
        // find the minimum number in [low..high] that is
        // multiple of prime[i] (divisible by prime[j])
        int lowMinimum = (low/prime[j]) * prime[j];
        if(lowMinimum < low)
            lowMinimum += prime[j];

        //Mark multiples of prime[i] in [low..high]
        for (int k = lowMinimum; k <= high; k+=prime[j])
            mark[k-low] = 0;
    }

    for (int i = low; i <= high; i++)
        if(mark[i-low])
        {
            fiPri.Push_back(i);
            std::cout<<","<<i;
        }
    low = low + offset;
    high = high + offset;
}
}
};

int main()
{
std::vector<int> fiPri;
sito_segment(1013,fiPri);
}
0
Tomasz Andel

Swapnil Kumarの回答に基づいて、Cで次のアルゴリズムを実行しました。これはmingw32-make.exeで構築されました。

#include<math.h>
#include<stdio.h>
#include<stdlib.h>

int main()
{
    const int MAX_PRIME_NUMBERS = 5000000;//The number of prime numbers we are looking for
    long long *prime_numbers = malloc(sizeof(long long) * MAX_PRIME_NUMBERS);
    prime_numbers[0] = 2;
    prime_numbers[1] = 3;
    prime_numbers[2] = 5;
    prime_numbers[3] = 7;
    prime_numbers[4] = 11;
    prime_numbers[5] = 13;
    prime_numbers[6] = 17;
    prime_numbers[7] = 19;
    prime_numbers[8] = 23;
    prime_numbers[9] = 29;
    const int BUFFER_POSSIBLE_PRIMES = 29 * 29;//Because the greatest prime number we have is 29 in the 10th position so I started with a block of 841 numbers
    int qt_calculated_primes = 10;//10 because we initialized the array with the ten first primes
    int possible_primes[BUFFER_POSSIBLE_PRIMES];//Will store the booleans to check valid primes
    long long iteration = 0;//Used as multiplier to the range of the buffer possible_primes
    int i;//Simple counter for loops
    while(qt_calculated_primes < MAX_PRIME_NUMBERS)
    {
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            possible_primes[i] = 1;//set the number as prime

        int biggest_possible_prime = sqrt((iteration + 1) * BUFFER_POSSIBLE_PRIMES);

        int k = 0;

        long long prime = prime_numbers[k];//First prime to be used in the check

        while (prime <= biggest_possible_prime)//We don't need to check primes bigger than the square root
        {
            for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
                if ((iteration * BUFFER_POSSIBLE_PRIMES + i) % prime == 0)
                    possible_primes[i] = 0;

            if (++k == qt_calculated_primes)
                break;

            prime = prime_numbers[k];
        }
        for (i = 0; i < BUFFER_POSSIBLE_PRIMES; i++)
            if (possible_primes[i])
            {
                if ((qt_calculated_primes < MAX_PRIME_NUMBERS) && ((iteration * BUFFER_POSSIBLE_PRIMES + i) != 1))
                {
                    prime_numbers[qt_calculated_primes] = iteration * BUFFER_POSSIBLE_PRIMES + i;
                    printf("%d\n", prime_numbers[qt_calculated_primes]);
                    qt_calculated_primes++;
                } else if (!(qt_calculated_primes < MAX_PRIME_NUMBERS))
                    break;
            }

        iteration++;
    }

    return 0;
}

見つかった最大数の素数を設定し、2、3、5 ... 29のような既知の素数で配列を初期化します。したがって、可能な素数のセグメントを格納するバッファを作成します。このバッファは、この場合は29である最大の初期素数のパワーより大きくすることはできません。

セグメント分析プロセスの並列化や、2、3、5の倍数をスキップするなど、パフォーマンスを改善するために行うことができる最適化がたくさんあると確信していますが、これは低メモリ消費の例として役立ちます。

0