素数筛法

素数筛法是数论的入门,当然也非常重要。所谓素数(也叫质数),就是因数只有1和它本身的数。

今天讲一下怎么筛素数。

第一种算法,就是最朴素最暴力的算法,是人的都会。就是对于每一个数n,从 i = 2开始,依次判断n能否被i整除。

 1 bool judge(int x)
 2 {
 3     for(int i = 2; i * i <= x; ++i)        //只用判断到sqrt(n)就行 
 4         if(x % i == 0) return false;
 5     return true;
 6 }
 7 void pusushaifa(int n)
 8 {
 9     for(int i = 2; i <= n; ++i)
10         if(judge(i)) printf("%d ", i);
11     printf("\n");    
12 }

好想的当然也就慢,这个算法复杂度是O(nlogn),当n为1e7 时就过不了了。

 

第二种算法,Eratosthenes 筛法,简称埃氏筛法。这个算法也很好理解:对于不超过 n 的非负整数 p,删除2p, 3p, 4p……,当处理完所有数据后,没被删除的就是素数。

用 vis[i] = 1 表示 i 被删除,代码就可以写出来了

 1 const int maxn = 1e7 + 5;
 2 int vis[maxn];
 3 void erato1(int n)
 4 {
 5     memset(vis, 0, sizeof(vis));
 6     for(int i = 2; i <= n; ++i)
 7         for(int j = 2 * i; j <= n; j += i) vis[j] = 1;
 8     for(int i = 2; i <= n; ++i) 
 9         if(!vis[i]) printf("%d ", i); 
10     printf("\n");
11 }

我们分析一下复杂度:内层循环的次数是 (floor) n / i - 1 < n / i,所以内层循环的总次数一定小于 n / 2 + n / 3 + n / 4 +……+ n / n。因为 n / 2 + n / 3 < n,n / 4 + n / 5 + n / 6 + n / 7 < n,…… ,所以时间复杂度小于 O(nlogn)。

这个算法还可以优化,“对于不超过 n 的非负整数 p”,p可以限定为素数,而且内层循环必不从 i * 2 开始,因为他已在 i = 2 时就筛掉了。

 1 const int maxn = 1e7 + 5;
 2 int vis[maxn];
 3 void erato2(int n)
 4 {
 5     memset(vis, 0, sizeof(vis));
 6     for(int i = 2; i * i <= n; ++i) if(!vis[i])      //是素数
 7         for(int j = i * i; j <= n; j += i) vis[j] = 1;
 8     for(int i = 2; i <= n; ++i) 
 9         if(!vis[i]) printf("%d ", i); 
10     printf("\n");
11 }

改进后的复杂度就变成了 O(nloglogn)。

 

还有第三种一个很牛的算法,线性筛法。我们先回顾一下埃氏,埃氏算法的一个弊端就是一个数 n 被它的所有素因子筛了一遍,重复筛导致浪费时间。

我们如何保证每一个数只被筛一遍呢?有一种想法,只要让每一个数只被他的最小素因子筛去就行。

写代码时,开一个数组标记,和上面的埃氏筛法的数组相同,再开一个数组记录素数,用来表示某一个数的最小素因子。代码如下

 1 int notPrime[maxn], prime[maxn / 10];
 2 void xianxingshai(int n)
 3 {
 4     for(int i = 2; i <= n; ++i)
 5     {
 6         if(!notPrime[i]) {prime[++prime[0]] = i; printf("%d ", i);}    //如果是素数,就存下来 
 7         for(int j = 1; j <= prime[0] && prime[j] * i <= n; ++j)    //prime[j] * i就表示要筛的那个数 
 8         {
 9             notPrime[prime[j] * i] = prime[j];    //标记 
10             if(i % prime[j] == 0) break;         
11         }
12     }
13     printf("\n");
14 }

第9行拿prime[j]标记,因为素数队列是严格递增的,因此notPrime[j] 表示的也是 j 的最小素因子。

特别强调的是第10行,若 i 能被prime[j] 整除,则 i = prime[j] * x,所以 i * prime[j + m] = prime[j] * x * prime[j + m],因此 i * prime[j + m] 一定被 prime[j] 筛去,就不必再重复筛了。

还有的就是,这个 i 一定是一个素数,因为如果是一个合数的话,那么他一定等于一个小于他的素数乘以另一个数,而这个素数一定在他之前就被遍历到了。

因为每一个数只被筛去一次,所以时间复杂度就是 O(n)。

 

特别鸣谢:http://www.cnblogs.com/suno/archive/2008/02/04/1064368.html,线性筛法这讲得很透。

 

posted @ 2018-02-25 19:51  mrclr  阅读(348)  评论(0编辑  收藏  举报