制作一个简单的筛子很容易:
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)怎么办?
答案 0 :(得分:48)
分段筛的基本思想是选择小于 n 的平方根的筛分质数,选择一个相当大的段大小但仍然适合存储器,然后筛选每个段反过来,从最小的开始。在第一段,计算在该段内的每个筛分素数的最小倍数,然后以正常方式将筛分素数的倍数标记为复合物;当所有筛选质数都被使用时,该段中剩余的未标记数字是素数。然后,对于下一个段,对于每个筛分素数,您已经知道当前片段中的第一个倍数(它是在前一个片段中结束筛选的倍数),因此您筛选每个筛分素数,依此类推直到你完成。
n 的大小并不重要,除了较大的 n 比较小的 n 需要更长的时间来筛选;重要的大小是段的大小,它应该是方便的(例如,机器上主存储器高速缓存的大小)。
您可以看到分段筛here的简单实现。请注意,分段筛网将比另一个答案中提到的O'Neill优先排队筛网快得多;如果您有兴趣,可以使用here。
编辑:我为了不同的目的写了这个,但我会在这里展示,因为它可能有用:
虽然Eratosthenes的筛子非常快,但它需要O(n)空间。通过在连续的段中进行筛分,可以将筛分质子的O(sqrt(n))减少到比特阵列的O(1)。在第一段,计算该段内每个筛分素数的最小倍数,然后以正常方式将筛分素数的倍数标记为复合物;当所有筛选质数都被使用时,该段中剩余的未标记数字是素数。然后,对于下一个段,每个筛分素数的最小倍数是结束前一个段中筛分的倍数,因此筛分一直持续到完成。
考虑从20到200的筛子中的筛子的例子。五个筛分质数是3,5,7,11和13.在100到120的第一段中,比特阵列有10个槽,槽0对应于101,对应于100 + 2k + 1的时隙k,以及对应于119的时隙9.该段中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&lt;嗨,和lo必须大于sqrt(hi)。分段大小是两倍增量。 Ps是一个链表,其中包含小于sqrt(hi)的筛分素数,由于偶数被忽略,因此删除了2。 Qs是链接列表,其包含进入相应筛分素数的当前片段中的最小倍数的筛子阵列的offest。在每个段之后,lo前进两次delta,因此对应于sier 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最初是[2,2,2,10,8],对应于最小的倍数105,105,105,121和117,并且对于第二段重置为[1,2,6,0,11],对应于最小的倍数123,125,133,121和143。
您可以在 http://ideone.com/iHYr1f看到此计划的实际效果。除了上面显示的链接之外,如果您对素数编程感兴趣,我可以在我的博客上谦虚地推荐这个essay。
答案 1 :(得分:4)
基于优先级队列的Sieve版本可以根据您的请求生成尽可能多的素数,而不是所有素数都达到上限。它在经典论文"The Genuine Sieve of Eratosthenes"中进行了讨论,谷歌搜索“eratosthenes优先队列筛选”在各种编程语言中引入了不少实现。
答案 2 :(得分:3)
只是我们正在用我们的筛子进行分割。 基本的想法是,我们必须找出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,依此类推.................. 继续进行上述步骤。您可以使用传统筛子达到sqrt(n)来获得素数(2,3,5,7,...)。然后将其用于分段筛。
答案 3 :(得分:0)
基于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的倍数,但它可以作为低内存消耗的一个例子。
答案 4 :(得分:0)
如果有人想看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);
}
答案 5 :(得分:0)
一个数是素数,如果没有一个较小的素数能整除它。由于我们按顺序迭代素数,因此我们已经将所有可被至少一个素数整除的数标记为可整除。因此,如果我们到达一个单元格并且它没有被标记,那么它就不能被任何较小的素数整除,因此必须是素数。
记住以下几点:-
// Generating all prime number up to R
// creating an array of size (R-L-1) set all elements to be true: prime && false: composite
#include<bits/stdc++.h>
using namespace std;
#define MAX 100001
vector<int>* sieve(){
bool isPrime[MAX];
for(int i=0;i<MAX;i++){
isPrime[i]=true;
}
for(int i=2;i*i<MAX;i++){
if(isPrime[i]){
for(int j=i*i;j<MAX;j+=i){
isPrime[j]=false;
}
}
}
vector<int>* primes = new vector<int>();
primes->push_back(2);
for(int i=3;i<MAX;i+=2){
if(isPrime[i]){
primes->push_back(i);
}
}
return primes;
}
void printPrimes(long long l, long long r, vector<int>*&primes){
bool isprimes[r-l+1];
for(int i=0;i<=r-l;i++){
isprimes[i]=true;
}
for(int i=0;primes->at(i)*(long long)primes->at(i)<=r;i++){
int currPrimes=primes->at(i);
//just smaller or equal value to l
long long base =(l/(currPrimes))*(currPrimes);
if(base<l){
base=base+currPrimes;
}
//mark all multiplies within L to R as false
for(long long j=base;j<=r;j+=currPrimes){
isprimes[j-l]=false;
}
//there may be a case where base is itself a prime number
if(base==currPrimes){
isprimes[base-l]= true;
}
}
for(int i=0;i<=r-l;i++){
if(isprimes[i]==true){
cout<<i+l<<endl;
}
}
}
int main(){
vector<int>* primes=sieve();
int t;
cin>>t;
while(t--){
long long l,r;
cin>>l>>r;
printPrimes(l,r,primes);
}
return 0;
}
答案 6 :(得分:-1)
我们使用分段筛的主要原因是输入数字大。
这个想法很简单。
假设我的输入n很大。
第1步:查找您提到的简单筛子,输入为sqrt(n),其中sqrt是平方根。将为此输入找到的素数保存在列表中。
第2步:分段筛/块筛的概念非常直观,顾名思义,我们为每块sqrt(n)尺寸应用简单的筛。在此步骤中,您需要将元素范围更改为( sqrt(n),2 * sqrt(n))
步骤3:
伪代码
low=sqrt(n)
high=2*sqrt(n)
while(low<n)
{
if(high>=n)
high=n
for ele in list //list is mentioned in step 1
{
//find the first multiple of ele in the range mentioned in step2
//let l be that integer
for i=l to high
{
if(i is not a multiple of ele)
list.add(i)
}
}
low+=sqrt(n)
high+=sqrt(n)
}
第4步:最终列表包含所有素数。
参考文献1:Segmented Sieve by GfG
参考资料2:cp-algorithms
在Java中的实现:
import java.util.*;
import java.lang.*;
import java.io.*;
class ExtendedSieve
{
static void simpleSieve(int n,ArrayList<Integer> list)
{
boolean prime[]=new boolean[n+1];
for(int i=2;i*i<=n;i++)
{
if(!prime[i])
{
for(int j=i*i;j<=n;j+=i)
{
prime[j]=true;
}
}
}
for(int i=2;i<=n;i++)
{
if(!prime[i]){
list.add(i);
System.out.print(i+" ");}
}
}
static void segmentedSieve(int n)
{
int fss=(int)(Math.sqrt(n))+1;
ArrayList<Integer> list=new ArrayList<>();
simpleSieve(fss,list);
int low=fss;
int high=2*fss;
while(low<n)
{
if(high>n)
high=n;
boolean mark[]=new boolean[fss+1];
int size=list.size();
for(int i=0;i<size;i++)
{
int ele=list.get(i);
int llow=(int)Math.floor(low/ele)*ele;
if(llow<low)
llow+=list.get(i);
for(int j=llow;j<=high;j+=ele)
{
mark[j-low]=true;
}
}
for(int i=low;i<=high;i++)
{
if(!mark[i-low])
System.out.print(i+" ");
}
low+=fss;
high+=fss;
}
}
public static void main (String[] args) throws Exception
{
int n=35;
segmentedSieve(n);
System.out.println();
}
}
输出:
2 3 5 7 11 13 17 19 23 29 31