用Monte Carlo算法实现素数判定

一、问题描述
判定一个给定的整数是否为素数

二、Monte Carlo算法
1.预备知识
(1)费马小定理
在这里插入图片描述
其逆否命题:
在这里插入图片描述
利用该逆否命题进行素数判定(代码如下):

//利用费马小定理(正确的概率较低)
void Fermat(int n) {
    bool flag = true;
    srand((unsigned)time(0));
    int a = uniform(1, n); //[1, n-1]
    if(Pow(a, n - 1) % n != 1)
        flag = false;
    Print(flag, n);  //flag = true未必是素数
}

缺陷
即使任意的a属于[1, n-1],使得Pow(a, n - 1) % n = =1 也不能保证n为素数。(费马小定理的逆命题不成立)

结论
不能通过验证Pow(a, n - 1) % n = =1来判定n是否为素数。

2.对Fermat测试改进
(1)理论知识与对应代码
设n是一个大于4的奇整数,s和t是使得 n − 1 = 2 s ∗ t n - 1 = 2^s * t n1=2st的正整数,其中t为奇数。
当且仅当 a ∈ [ 2 , n − 2 ] a\in[2, n - 2] a[2,n2]且满足下述2个条件之一:
a t    %    n = = 1 a^t \;\%\; n == 1 at%n==1
∃ 整 数 i ∈ [ 0 , s − 1 ] {\exists}整数i\in[0, s - 1] i[0,s1],满足 a 2 i ∗ t    %    n = = n − 1 a^{2^i * t}\;\%\;n==n-1 a2it%n==n1
则称n为一个以a为底的强伪素数,称a为n素性的强伪证据,即 a ∈ B ( n ) a\in B(n) aB(n)

代码如下

//对Fermat的改进
//判定a是否为n素性的强伪证据
bool Btest(int a, int n) {  //n为奇数
    int s = 0, t = n - 1;
    do
    {
        s++;
        t /= 2;
    } while (t % 2 != 1); //满足n - 1 = 2^s * t的最大奇数t;
    //int x = Pow(a, t) % n;  Pow(a, t)溢出了。
    int x = 1;
    while(t--) {
        x = (x * (a % n)) % n;  //(a * b) % p = (a % p * b % p) % p
    }
    if(x == 1 || x == n - 1)
        return true;
    for (int i = 1; i < s; i++) {
        x = (x * x) % n;
        if(x == n - 1)
            return true;
    }
    return false;  //一定是合数
}

bool MillRab(int n) {
    //srand((unsigned)time(0));   //放这误差很大
    int a = uniform(2, n - 1); //[2, n-2]
    return Btest(a, n);  //测试n是否为强伪素数
}

在这里插入图片描述
分析:1次调用MillRab函数,若返回false,一定为合数;若返回true,则判定为合数的概率< 1 4 \frac{1}{4} 41。为缩小误判概率,调用k次MillRab函数,若每次都返回true,则判定为合数的概率< 1 4 k \frac{1}{4}^k 41k,当k取10时,误判概率低于百万分之一。

代码如下

bool RepeatMillRab(int n, int k) {
    for (int i = 1; i <= k; i++) {
        if(MillRab(n) == false)
            return false;
    }
    return true;
}

三、完整代码及运行结果
(1)完整代码

//素数的判定
#include <cstdio>
#include <cmath>
#include <random>
#include <ctime>
using namespace std;
typedef long long LL;

void Print(bool flag, int n) {
    if(flag)
        printf("%d is a prime.\n", n);
    else
        printf("%d is not a prime.\n", n);
}

//传统方法
void TraditionalPrime(int n) {
    bool flag = true; //假设一开始为素数
    int upper = floor(sqrt(n));
    for (int i = 2; i <= upper; i++) {
        if(n % i == 0)
            flag = false;
    }
    Print(flag, n);
}

bool TraditionalMethod(int n) {
    int upper = floor(sqrt(n));
    for (int i = 2; i <= upper; i++) {
        if(n % i == 0)
            return false;
    }
    return true;
}

//[a, b-1] (rand() % (b-a))+ a
int uniform(int a, int b) {
    return (rand() % (b - a)) + a;
}

//整数幂
LL Pow(int a, int n) {
    LL sum = 1;
    for (int i = 1; i <= n; i++) {
        sum *= a;
    }
    return sum;
}

//利用费马小定理(正确的概率较低)
void Fermat(int n) {
    bool flag = true;
    srand((unsigned)time(0));
    int a = uniform(1, n); //[1, n-1]
    if(Pow(a, n - 1) % n != 1)
        flag = false;
    Print(flag, n);  //flag = true未必是素数
}

//对Fermat的改进
//判定a是否为n素性的强伪证据
bool Btest(int a, int n) {  //n为奇数
    int s = 0, t = n - 1;
    do
    {
        s++;
        t /= 2;
    } while (t % 2 != 1); //满足n - 1 = 2^s * t的最大奇数t;
    //int x = Pow(a, t) % n;  Pow(a, t)溢出了。
    int x = 1;
    while(t--) {
        x = (x * (a % n)) % n;  //(a * b) % p = (a % p * b % p) % p
    }
    if(x == 1 || x == n - 1)
        return true;
    for (int i = 1; i < s; i++) {
        x = (x * x) % n;
        if(x == n - 1)
            return true;
    }
    return false;  //一定是合数
}

bool MillRab(int n) {
    //srand((unsigned)time(0));   //放这误差很大
    int a = uniform(2, n - 1); //[2, n-2]
    return Btest(a, n);  //测试n是否为强伪素数
}

bool RepeatMillRab(int n, int k) {
    for (int i = 1; i <= k; i++) {
        if(MillRab(n) == false)
            return false;
    }
    return true;
}

//打印1万以内的素数
void PrintPrimes() {
    int pn = 2, count = 2; //pn表示真素数的个数, count表示判定为素数的个数(含假阳性);
    printf("2 3 ");
    int n = 5;
    do{
        if(RepeatMillRab(n, floor(log10(n)))) {
            if(TraditionalMethod(n))
                pn++;
            count++;
            printf("%d", n);
            if(count % 20 == 0)
                printf("\n");
            else
                printf(" ");
        }
        n += 2;
    } while (n < 10000);
    printf("误判个数:%d", count - pn);
    double correct = (double)(pn) / count * 100;
    printf("\n正确比例:%f%%\n", correct);
}

int main() {
    //传统方法
    //int n = 2643;
    //TraditionalPrime(n);
    //Fermat(n);
    srand((unsigned)time(0));      
    PrintPrimes();
    return 0;
}

(2)运行结果
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值