素数筛选的方法很多,这里的方法和其他方法有相似的地方,都是标记合数,然后使用前面找到的素数进行再次的筛选,但是这个方法有它自己的特殊的地方,对于每一个合数,它只会标记一次,所以这样的话就能达到哦O(n)的效果。在这里,我们先贴上代码,然后进行证明。
int prime_len;
int primes[100000];
bool gash[100000];
void initPrimes(int n) {
memset(gash , 0 , sizeof(gash));
prime_len = 0;
for (int i=2 ; i<=n ; i++) {
if (!gash[i]) {
primes[prime_len++] = i;
}
for (int j=0 ; j<prime_len && ((i * primes[j]) <= n) ; j++) {
gash[i * primes[j]] = true;
if (i % primes[j] == 0) break;
}
}
}
从上面我们可以看到,这个方法的筛选方式有这样两步:
1. 如果这个数没有被标记过,则这个数是一个素数,我们保存起来
2. 不管这个数是不是素数,使用这个数和前面找到的素数进行筛选,设i为当前的数,当i*p[j]<= n 并且p[j]不是i的因子,则筛选i*p[j]。
下面我们证明为什么这样是对的:
1. 首先我们证明这个算法是对的,及对于每一个合数,我们都能做上标记,对于每一个素数,我们都能找到
a) 对于任何一个素数p,我们都不可能使用两个数乘积进行标记,所以对于每一个素数,我们都是能找到的。
b) 对于任何一个合数m = p1a1p2a2…pmam,这里p1< p2 < … <pm,我们都能使用p1a1-1p2a2…pmam* p1进行筛选,所以对于每一个合数,我们都能筛选到做上标记。
2. 我们证明为什么每一个合数都只会标记一次
a) m = x * qx , n = y * qy,这里 qx qy都是素数,且分别是m和n中的最小素数。当并且m != n,当我们使用两个不同的数进行筛选时,看看会不会标记相同的合数。使用m能找到的合数 x * qx* p[i],p[i]<qx,使用n能找到的合数y * qy * p[j],p[j] < qy,这里p是一个当前找到的素数序列。假设能找到相同的合数, 则x * qx * p[i] = y * qy * p[j]。因为m != n,则p[i] != p[j]。又因为qx, p[i] , qy, p[j]都是素数,且qx > p[i], qy > p[j],当qx = qy时,m中包含p[j]的因子,这样qx就不是m中最小的因子,则假设qx=qy不成立。当qx != qy,则当qx > qy,则m中包含了p[j]的因子,则假设qx >qy不成立。同样假设qx< qy,同样假设不成立,所以假设x* qx * p[i] = y * qy * p[j]不成立,所以只能标记一次。