数值算法——查找素因子,查找素数,素数检验

素数,是大于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,则有n^{p-1}%p=1

值得注意的是,p为素数是结论成立的充分不必要条件,即如果p为非素数,公式仍可能成立。

当p为非素数:

公式成立时,n被称为费马谎言者

公式不成立时,n被称为费马证人

经证明,我们有,对于自然数p,1到p之间的数中,至少有一半为费马证人。

由此,在随机抽取n的情况下

运用1次费马小定理进行检验时,判断错误的概率为0.5

运用2次时,错误的概率为0.25

运用k次时,错误的概率为2^{-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;
}
  • 38
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值