目录
一、质数的概念:
质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数,也叫素数。 例如,2、3、5、7、11等都是质数,它们都是只能被 1 和它本身整除的正整数。
二、什么是质数筛:
质数筛是一种有效判断一定范围内所有数字是否为质数的算法,常用于信息学竞赛和数论入门等领域。常见的质数筛有埃氏筛、欧拉筛和线性筛等方法。
三、几种常用求质数的方法:
1.暴力枚举
暴力枚举法是一种用于求解素数的常用策略,其基本思路是:从2开始,逐一检查2到n-1之间的每个数,判断这些数是否有因子能整除n。如果存在一个数可以整除n,那么n就不是一个素数;反之,若没有这样的数,则n就是一个素数。
暴力枚举法求素数的时间复杂度是O(n)n^2。这是因为对于每个待检测的数n,我们需要从2到n-1遍历一次,检查这些数中是否有能够整除n的因子。在最坏的情况下,例如当n为一个合数时,我们需要执行n-1次操作来找到其最小的质因数。因此,总的操作次数将达到n*(n-1)/2,即O(n^2)。
暴力枚举的优化是我们初学C语言时必学的一种方法之一,其基本思路与上面一致,不过检查的数从2~n-1变成了2~sqrt(n)。例如在我们判断37是不是质数时,我们在程序执行过程中分别验证了2、3、4、6、9、12、18这几个数是否能被37整除,而37/2=18,37/18=2(这是舍去小数后的结果),我们在执行37除2操作时就已经把37除18这步操作了包括了,这说明上面从2~n-1逐一检验我们做了一半的无用功,实际上我们只需要检验2~sqrt(n)就行了。这种优化方法,降低了查询次数,求质数的时间复杂度降为了O(n)n*sqrt(n)。
代码模板如下:
int a[10001]={1,1};//全局变量定义时没初始化默认为0
//假设a[i]除去0,1外全部都是质数等于0;
void book()
{
int i,j;
for(i=2;i<10001;i++){
for(j=2;j<=sqrt(i);j++){
if(i%j==0){
break;
}
}
if(j>sqrt(i)){
a[i]=1;//当a[i]为1表示该点为质数
}
}
}
运行结果如下:
2.埃氏筛
埃拉托斯特尼筛法(Sieve of Eratosthenes)是一种用于求解一定范围内所有素数的经典算法。其基本思想是:首先列出从2到n的所有整数,然后将2的倍数剔除掉;接着找到下一个还未被剔除的数,将它的倍数也剔除掉;以此类推,最后剩下的就是素数。
例如:如果2是质数,那么2的倍数4、6、8、12.....都不是质数
如果3是质数,那么3的倍数6、9、12、15.....都不是质数
埃氏筛法的时间复杂度为O(n log log n)。这是因为首先需要生成一个从2到n的整数数组,然后从小到大遍历数组中的每个数,如果当前数是素数,则把数组中它的倍数标记为合数(即把它们标记为非素数)。在这个过程中,每次只对素数i,把i*pri[j]筛一遍,因此总的操作次数与素数的分布有关。具体来说,时间复杂度主要由两部分决定:一部分是生成数组的时间复杂度为O(n);另一部分是筛选操作的时间复杂度为O(n log log n)。由于后者的贡献更大,因此总的时间复杂度为O(n log log n)。
代码模板如下:
int a[10001]={1,1};//全局变量定义时没初始化默认为0
//假设a[i]除去0,1外全部都是质数等于0;
void book()
{
int i,j;
for(i=2;i<10001;i++)
{
if(a[i]==1){
continue;
}
for(j=2;j*i<10001;j++){
a[i*j]=1;//a[i*j]不是质数
}
}
}
运行结果如下:
3.欧拉筛
欧拉筛又叫线性筛,是求范围内素数最好用的算法欧拉筛法是一种用于求解小于等于n的所有素数的高效方法,其核心思想是利用合数的唯一性质:每个合数都可以表示为两个因数的乘积。如果将这两个因数分别存储在数组的两个位置上,那么这个合数就可以被它的最小质因子筛掉。
例如:
12,它有质因数2、3,那么它只被2标记
15,它有质因数3、5,那么它只被3标记
30,它有质因数2、3、5,那么它只被2标记
需要注意的是,由于合数总是被其最小的质因子筛掉,因此在使用欧拉筛法时需要额外开辟一个大小为n的数组来存储每个数的最小质因子。
埃氏和线性的区别在于埃氏会重复删除合数但是线性不会删除重复合数,因此线性筛比埃筛快!
欧拉筛的基本步骤:(这里参考的旧林墨烟大佬的题解)
第一步:初始化数组a[],book[],其中数组 book[] 把找到的质数存下来。
第二步:找到最大因数x,标记以x为最大因数的质数的倍数为非负数
第三步:处理一个数时我们遍历每一个已经有的质数,当该质数是该数的因数时退出,因为对于后面的数来说,该数不是最大的因数了。
例如:
当x=9,那么此时已经找出了质数2、3、5、7
遍历这些质数,那么9*2=18不是质数,9*3=27不是质数,9*5=45不是质数,9*7=63也不是质数,但实际上我们在更新完 27 之后就应该退出了,而不去更新45和63。
因为9并不是45的最大因数,15才是,即这个数在处理15的时候会被标记的,如果现在标记就会出现无用的标记了。
63也是一样的道理,它的最大因数是21,即这个数在处理21的时候会被标记。
欧拉筛法的时间复杂度为O(n),这是因为只需要遍历一次给定范围内的所有整数即可。这使得欧拉筛法在处理大规模数据时具有较高的效率和实用性。
代码模板如下:
int a[10001]={1,1};//全局变量定义时没初始化默认为0
//假设a[i]除去0,1外全部都是质数等于0;
int book[10001];//用来存放质数
void Book()
{
int num=0;
int i,j;
for(i=2;i<10001;i++)
{
if(a[i]==0){//a[i]=0没被标记过,那么i是质数
book[num++]=i;
}
for(j=0;j<num;j++)
{
if(book[j]*i<10001){//标记以i为最大因数的数不是素数
a[book[j]*i]=1;
}
else{
break;
}
if(i%book[j]==0){//如果book[j]是i的因数,那么后面的数都不是以i为最大因数
break;
}
}
}
}
运行结果如下:
四、小结
暴力枚举法、埃氏筛法和欧拉筛法都是求解一定范围内所有素数的经典算法,它们各自有着不同的特点和应用场景。其中,暴力枚举法的时间复杂度为O(n^2);埃氏筛法的时间复杂度为O(n log log n);而欧拉筛法的时间复杂度为O(n),是三种方法中时间复杂度最小的。
暴力枚举法的优点是实现简单,易于理解,但其效率较低,只适用于处理较小的数据。相比之下,埃氏筛法虽然时间复杂度较高,但由于其采用了一些优化策略(如只考虑奇数、记录已经判断过的数等),因此在处理中等规模的数据时仍然具有较高的效率。
欧拉筛法则是一种更为高效的求解素数的算法。其主要思想是利用合数的唯一性质:每个合数都可以表示为两个因数的乘积。如果将这两个因数分别存储在数组的两个位置上,那么这个合数就可以被它的最小质因子筛掉。这种方法的优点在于只需要遍历一次给定范围内的所有整数即可。因此,欧拉筛法在处理大规模数据时具有较高的效率和实用性。