素数,是大于1的自然数,其唯一的因子是1及其本身。相对的,合数是大于1的非素数的自然数。
素数的作用:由于很难将两个素数的乘积重新分解为因子,因此,某些类型的密码可以使用两个大素数的乘积来提供安全性。因此,下面将学习关于素数的一系列算法。
一、查找素数因子
素因子的定义:给定一个自然数N,我们可以将其分解为n个素数相乘的形式,这其中的素数就被称为N的素因子(不计重复元素)。如12可以拆分为2*2*3*3,则12的素因子为2和3;
由定义我们不难想到,只需要将i从1到N进行迭代,当i能够整除N时,保存i的值,并把N除以i的值赋给N,直到i无法再整除N,i的值再加1,进行下一轮检验。
此算法的时间复杂度为O(n),那么我们是否能够对其进行优化?
经过学习,我们有三个优化的关键点:
1.无需测试除了2以外的偶数因子
因为其他偶数可以分解为2*j(j>1)的形式,即其一定包含2这个因数。因此,我们只需检查该数是否可以被2整除,然后再检查奇数因子即可。这样我们可以将运行时间减少大约一半。
2.只需检查sqrt(N)以内的因子
我们假定N=p*q,则总有p或q<=sqrt(N) (如果p,q均大于sqrt(N),则p*q大于N)。我们假定p>=q,则q<=sqrt(N),当我们将i从1迭代到sqrt(N)时,一定能够检测到q这个素因子,并用q整除N从而得到p,从而找到p,q这两个素因子。
3.在2的基础上,我们每将N除以一个因子时,可以更新需要检查因子的范围上限。
实现代码如下:
//n:整数N
//*returnSize:素因子个数
int* FindFactors(int n,int* returnSize){
int* ans=(int*)malloc(sizeof(int)*n);
memset(ans,0,sizeof(int)*n);
int* p=ans; /指针p指向答案数组,填写素因子
*returnSize=0;
while(n%2==0){
*p=2;
n/=2; //除以所有偶数的因数2
}
if(*p==2){
p++;
(*returnSize)++;
}
for(int i=3;i<=sqrt(n*1.0);i+=2){
while(n%i==0){
*p=i;
n/=i;
}
if(*p==i){
p++;
(*returnSize)++;
}
}
if(n>1){
*p=n; //当n还有剩余时,此也为N的一个素因子
(*returnSize)++;
}
return ans;
}
该算法运行时间为O(sqrt(N)),当N很小时,运算速度很快;但当N很大时(如100位,则sqrt(N)为50位),运算时间仍很长。
二、查找素数
程序设计经常碰到的一道题目就是找出一定范围内的所有素数。我们有几种方法来实现。
第一种方法就是才用上述的算法,当N只有一个素因子时,N为素数。但当N很大时,运算速度会很慢。
第二种方法是埃拉托斯特尼筛法
该法通过构建一张表,来实现牺牲空间换取时间的策略。具体如下:
我们创建一个表,该表包含从2到N的每一个数字。
首先我们将所有2的倍数删去(不包括2本身)
然后从2开始,查找下一个未被删去的数字(这里是3),并删去其所有的倍数(不包括该数字本身)
重复以上步骤,直到查找到sqrt(N)
实现代码如下:
void FindPrimes(long n){
long* nums=(long*)malloc(sizeof(long)*n);//开表
for(int i=0;i<n;i++){
nums[i]=i+1;
}
for(int i=3;i<n;i++){
if((i+1)%2==0){
nums[i]=0; //删去元素——置零
}
}
for(int i=2;i<sqrt(n*1.0);i++){
if(nums[i]!=0){
for(int j=i+1;j<n;j++){
if(nums[j]%nums[i]==0){
nums[j]=0;
}
}
}
}
for (int i = 0; i < n; i++) {
if (nums[i] > 1) {
printf("%d\n", i + 1); //找到非零元素,即为素因子,并打印
}
}
return 0;
}
三、素性检验
对于一个自然数,我们如何检验其是否为素数呢?
最简单的方法就是采用上述的算法,查找其所有的素因子,若其只有一个素因子,即其为素数。
但这样的算法对于较大的素数而言,运算速度还是太慢了。
在各种检验素数的算法中,这里学习的较为简单的一种——费马素性检验
我们引入费马小定理:
如果p为素数,且1<=n<p,则有%p=1
值得注意的是,p为素数是结论成立的充分不必要条件,即如果p为非素数,公式仍可能成立。
当p为非素数:
公式成立时,n被称为费马谎言者
公式不成立时,n被称为费马证人
经证明,我们有,对于自然数p,1到p之间的数中,至少有一半为费马证人。
由此,在随机抽取n的情况下
运用1次费马小定理进行检验时,判断错误的概率为0.5
运用2次时,错误的概率为0.25
运用k次时,错误的概率为
当k越大,检验错误的概率越小
因此,我们只需增加检验的次数,只要错误的概率足够小,当所有检验都通过后,我们则有极大的把握认为被检验数为素数。
实现代码如下:
bool Check(int p){
bool ans=true;
srand((unsigned)time(NULL));
int n=1+rand()%(p-1);
for(int i=0;i<100;i++){ //检验100次
if(pow(n,p-1)%p!=1){
ans=false;
}
}
return ans;
}