第一种方法:枚举
从素数的定义中,我们可以知道,一个整数m要被判断为素数,需要判断n是否能被2、3…n-1中的一个整除,只有2,3,…,n-1都不能整除n,n才能判定为素数,而只要有一个能整除n的数出现,n就可以判定为非素数。
#include<iostream> #include<cmath> using namespace std; bool isprime(int x){//定义判断函数isprime,返回值类型为bool if(x<=1)return false;//如果这个数≤1,那么它肯定不是素数,返回false if(x==2)return true;//如果这个数=2,也需要特判,2是素数,返回true for(int i=2;i<=x-1;i++){//枚举2到x-1 if(x%i==0)return false; /*如果这其中x能被任意一个i整除,说明x有除了1和它本身之外的其他因数, 所以它不是素数,返回false*/ } return true;//如果这个数不能被除了1和它本身之外的其他数整除,那么他就是素数,返回true } int main(){ int n;//定义一个n,表示要输出的是1-n之间的素数 cin>>n;//输入表示范围的n for(int i=1;i<=n;i++) {//枚举1-n之间的每一个数 if(isprime(i))cout<<i<<' ';//调用isprime函数,判断i是否为素数,如果返回true则输出 } return 0;//结束程序 }
这样的判定方法没什么问题,时间复杂度为 O(n²),但可以优化,因为如果k是n的一个约数,那么n/k也是n的一个约数,k 和 n/k 必然满足一个小于等于 sqrt(n) 另一个大于等于 sqrt(n),其中sqrt(n)为根号 n 。从2到sqrt(n)减少枚举次数所以只要判断n是否能被 2,3…sqrt(n) 中的一个数整除,即可判定n是否为素数,时间复杂度为O(nlogn),第一个程序修改一点点即可得到新的代码。
#include<iostream> #include<cmath> using namespace std; bool isprime(int x){ if(x<=1)return false; if(x==2)return true; for(int i=2;i*i<=x;i++){//等于int y=sqrt(x);for(int i=1;i<=y;i++)这种形式只是减少了函数的调用,实际的复杂度还是相同的 if(x%i==0)return false; } return true; } int main(){ int n; cin>>n; for(int i=1;i<=n;i++) { if(isprime(i))cout<<i<<' '; } return 0; }
第二种方法:埃拉托斯特尼筛法(埃氏筛)
埃氏筛也叫素数筛法,其关键在一个“筛”字。算法从小到大枚举所有数,对于每一个素数,筛去它的所有倍数,剩下的就都是素数了。埃氏筛的时间复杂度只有O(n*log(log(n)))
下面是一个例子:求1~15中的所有素数。
根据所学知识得知1不是素数直接筛去
2是素数,因此筛去所有2的倍数,即4、6、8、10、12、14。
3没有被前面的步骤筛去,因此3是素数,筛去所有3的倍数,即6,9,12,15
4已经被筛去,因此4不是素数。
5没有被前面的步骤筛去,因此5是素数,筛去所有5的倍数,即10,15
6已经被筛去,因此6不是素数。
7没有被前面的步骤筛去,因此7是素数,筛去所有7的倍数 ,即14
9已经被筛去,因此9不是素数.
10已经被筛去,因此10不是素数.
11没有被前面的步骤筛去,因此11素数,但15以内没有11的倍数。
12已经被筛去,因此12不是素数.
13没有被前面的步骤筛去,因此13素数,但15以内没有13的倍数。
14已经被筛去,因此14不是素数.
15已经被筛去,因此15不是素数.
至此,1~15以内的所有素数已全部得到,即2、3、5、7、11、13。
由这个例子很形象的展示了埃氏筛的过程,那么接下来就直接上代码:
#include<iostream> #include<cmath> using namespace std; bool isprime[1005];//标记是否为素数,如果i为素数,则isprime[i]为0;否则为1 int n;//定义一个范围,表示从1-n void Aiprime(int x){ isprime[1]=1;//1不是素数 for(int i=2;i<=x;i++){ if(isprime[i]==0){//如果i是素数,开始筛除i的倍数 for(int j=i*2;j<=x;j+=i) //从i的2倍(防止i被筛除)开始筛除合数,直到i比x大时停止,每次增加一倍 isprime[j]=1; //筛去所有i的倍数 } } } int main(){ cin>>n; Aiprime(n);//调用埃氏筛选出从1-n的所有素数 for(int i=1;i<=n;i++)//从1枚举到n if(isprime[i]==0)cout<<i<<' ';//如果isprime[i]为0,说明i为素数,输出 return 0; }
第三种方法:线性筛(欧拉筛)
在上面的埃氏筛中,2从4开始标记合数,3从9开始标记合数。我们会发现虽然免去了重复标记6,但最后这两个数还是免不了重复标记一个数:12 。究其根本原因是2是12的质因子但同样3也是12的质因子,那么有没有一种方法让每个数都被他的最小质因子标记。这就是接下来的线性筛。
思路就是将所有素数放入一个数组里。i从2遍历,将i与每个小于i的素数相乘筛去他们的乘积,直到i等于那个素数或那个素数是i的质因子的时候停下,这样子必然满足合数是由最小质因子筛去的。
假设要筛出n以内的素数。我们先把所有数标记为素数。枚举i从2到n,所以因为i是从小到大的,如果i没有被前面的数(比它小的数)标记为合数,那i就是素数,加入素数列表。现在用i来筛后面的数,枚举已经筛出来的素数prime[j](j=1~cnt),标记i * prime[j]为合数,当i是prime[j]的倍数时退出循环,i++。线性筛的时间复杂度为O(n),比较快速。
#include<iostream> #include<cmath> using namespace std; bool isprime[50005];//标记是否为素数,如果i为素数,则isprime[i]为0;否则为1 int prime[50005];//用来存素数 int cnt;//素数的个数 int n;//定义一个范围,表示从1-n void Qprime(int x){ isprime[1]=1;//1不是素数 for(int i=2;i<=x;i++){ if(isprime[i]==0)prime[++cnt]=i; //如果i是素数,素数个数+1,把i存在prime数组的相应位置中 for(int j=1;j<=cnt&&i*prime[j]<=x;j++){ isprime[i*prime[j]]=1;//将i的素数倍标记为合数 if(i%prime[j]==0)break; //如果i能被素数prime[j]整除,那i*prime[j+1]这个合数一定也能被prime[j]整除,终止循环 //保证了一个合数只能被筛除一次,可以保证线性的时间复杂度 } } } int main(){ cin>>n; Qprime(n);//调用线性筛选出从1-n的所有素数 for(int i=1;i<=cnt;i++)cout<<prime[i]<<' '; //从1枚举到素数的个数cnt,输出prime数组里的所有素数 return 0; }
欧拉筛法正确性的证明:假设我们要筛掉合数a,且a的最小质因数为p,令 a=p*b,b先被外层循环碰到。现在b要筛掉它的倍数。因为p是 a的最小质因数,所以b的最小质因数必不小于p,这样就保证 b筛掉a前不会在if(i%prime[j]==0)break;处跳出循环。即使 b的最小质因数等于p,也会先筛掉a后再退出循环。这样就保证了每个合数都会被筛掉。
//我的素数判断解题报告到此结束,感谢大家的观看和学习!