基础算法——筛法求素数

Posted thejacoblu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础算法——筛法求素数相关的知识,希望对你有一定的参考价值。

以下来自http://blog.csdn.net/stack_queue/article/details/53560887

求素数是程序设计比赛中经常遇到的问题,最基本的方法是通过素数的定义直接判断,只能被1和它本身整除的数就是素数了。这种方法适合判断单个数是否为素数,当要求一个范围内素数而这个范围又比较大时,这种方法就不太使用了,甚至程序要运行几分钟才能算出结果。

筛法的思想是去除要求范围内所有的合数,剩下的就是素数了,而任何合数都可以表示为素数的乘积,因此如果已知一个数为素数,则它的倍数都为合数。

以下来自http://blog.csdn.net/morewindows/article/details/7347459

最简单的筛素数法方法就是从2开始,将所以2的倍数去掉,然后从3开始,将3的倍数去掉。根据这样很容易写出代码,下面代码就是是筛素数法得到100以内的素数并保存到primes[]数组中。

//by MoreWindows( http://blog.csdn.net/MoreWindows )  
const int MAXN = 100;  
bool flag[MAXN];  
int primes[MAXN / 3], pi;  
void GetPrime_1()  
{  
    int i, j;  
    pi = 0;  
    memset(flag, false, sizeof(flag));  
    for (i = 2; i < MAXN; i++)  
        if (!flag[i])  
        {  
            primes[pi++] = i;  
            for (j = i; j < MAXN; j += i)  
                flag[j] = true;  
        }  
}  

可以看出这种会有很多重复访问,如在访问flag[2]和flag[5]时会各访问flag[10]一次。因此最好想方法来减少这种重复访问,让flag[]数组的每个元素只被访问一次。

可以这样考虑——简单的筛素数法是利用一个素数的倍数必须不是素数,同样任何一个数与其它所有素数的乘积必然也不是素数(这是因为每个合数必有一个最小素因子)。

为了试验这种想法,先用2到10之间的数来验证下。

    2,3,4,5,6,7,8,9,10      初始时所以flag都是无标记的。

第一步 访问2,flag[2]无标记所以将2加入素数表中,然后将2与素数表中的所有数相乘得到的数必定不是素数,2*2=4因此标记flag[4]。

   2,3,4,5,6,7,8,9,10

第二步 访问3,flag[3]无标记所以将3加入素数表中,将3与素数表中的所有数相乘得到的数必定不是素数,3*2=6,3*3=9因此标记flag[6]和flag[9]。

   2,3,4,5,6,7,8,9,10

第三步 访问4,flag[4]有标记所以4不加入素数表中,将4与素数表中的所有数相乘得到的数必定不是素数, 4*2=8,4*3=12因此标记flag[8]。

   2,3,4,5,6,7,89,10

第四步 访问5,flag[5]无标记所以将5加入素数表中,将5与素数表中的所有数相乘得到的数必定不是素数,5*2=10,5*3=15因此标记flag[10]。

   2,3,4,5,6,7,8910

第五步 访问6,flag[6]有标记所以6不加入素数表中,将6与素数表中的所有数相乘得到的数必定不是素数, 6*2=12,6*3=18,6*5=30。

   2,3,4,5,6,7,8910

 

 1 //by MoreWindows( http://blog.csdn.net/MoreWindows )  
 2 const int MAXN = 100;  
 3 bool flag[MAXN];  
 4 int primes[MAXN / 3], pi;  
 5 void GetPrime_2()  
 6 {  
 7     int i, j;  
 8     pi = 0;  
 9     memset(flag, false, sizeof(flag));  
10     for (i = 2; i < MAXN; i++)  
11     {  
12         if (!flag[i])  
13             primes[pi++] = i;  
14         for (j = 0; (j < pi)  && (i * primes[j] < MAXN); j++)  
15             flag[i * primes[j]] = true;  
16     }  
17 }  

 

这份代码对不对了?仔细回顾下分析过程,可以发现有些数据还是被访问多次了,这当然不是我们希望的结果,我们的要求是让每个合数仅被它的最小素因子筛去一次。比如12,它的最小素因子是2,所以就只应该被在计算6*2时去访问,而且不应该在计算4*3时去访问,同理18也只应该被在计算9*2时去访问,而且不应该在计算6*3时去访问。

    找到原因后,再来思考如何解决。6*3不行而9*2可以了,是因为6是2的倍数,所以在计算6*2之后就不能再将6与比2大的素数相乘,这些相乘的结果必定会导致重复计算。因此对于任何数来说,如果它如果是该素数的倍数那么它就不能再与素数表中该素数之后的素数相乘了,如9是3的倍数,所以在9*3之后就不能再去用计算9*5了。因此在代码中再增加一行判断语句:

 1 //by MoreWindows( http://blog.csdn.net/MoreWindows )  
 2 const int MAXN = 100;  
 3 bool flag[MAXN];  
 4 int primes[MAXN / 3], pi;  
 5 void GetPrime_2()  
 6 {  
 7     int i, j;  
 8     pi = 0;  
 9     memset(flag, false, sizeof(flag));  
10     for (i = 2; i < MAXN; i++)  
11     {  
12         if (!flag[i])  
13             primes[pi++] = i;  
14         for (j = 0; (j < pi)  && (i * primes[j] < MAXN); j++)  
15         {  
16             flag[i * primes[j]] = true;  
17             if (i % primes[j] == 0) //这句保证每个非素数只被筛去一次  
18                 break;  
19 }  
20     }  
21 } 

 

 

以上是关于基础算法——筛法求素数的主要内容,如果未能解决你的问题,请参考以下文章

题目 1084: 用筛法求之N内的素数

筛法求素数模板1

筛法求素数

c语言中用筛选法求素数

欧拉筛法求素数

一般筛法求素数+快速线性筛法求素数