素数求解(暴力,罗宾米勒,素数筛,线性筛及相关改进)

简单思路

思路:

简单思路就是直接根据素数的定义实现的,如果一个数是素数那他只能1和他本身整除

优化:

这里有个优化点,就是枚举的时候我们的判断条件是i * i <= x而不用是i < x

如果一个数是素数那么他至少两个因子,而且因子是成对出现的,并且一个成对的因子一个大于等于sqrt(n),一个小于等于sqrt(n),举例子如果两个因子都小于sqrt(n)那乘机一定小于n,反之大于

代码实现
int is_prime(int x){
  if (x < 2) return 0;
  for(int i = 2; i * i <= x; i++){
    if (x % i == 0){
      return 0;
    }
  }
  return 1;
}

素数筛

思路

素数筛的思想也是比较简单的,如果一个数是素数,那么他的倍数一定不是素数,所以就用这个数去给他的倍数染色,最后没有染色的就都是素数.

证明

我们一定会想有没有一个没有被染色且不是素数的数,如果这个数不是素数,那么他一定是一个合数,那么他的素因子一定比他小,如果我们拿到了他的素因子,我们完全可以给他染色,所以这个思路是正确的

代码实现
int pirme[MAX_N + 5] = {0};
void init() {
    for (int i = 2; i *i <= MAX_N; i++) {
        if (prime[i]) continue;
        for (int i = 2 * i; j <= MAX_N; j += i) {
            prime[j] = 1;
        }
    }
    for (int i = 2; i <= MAX_N; i++) {
        if (prime[i]) continue;
        prime[++prime[0]] = i;
    }
    return ;
}
改良版素数筛

我们来看prime[6]被2标记过,当我们拿到3时,3又回去标记一遍,是不是有重复的标记,其实当我们拿到一个素数时,我们没有必要从他的2倍开始标记,而是直接从i*i开始

因为如果一个数是一个合数,且素因子中有这个因子,有两种情况1,如果他是不是最小的素因子,那么在上一次素数进行染色的时候一定染色了这个合数,如果他是最下的素因子,那么之前一定没有素数对他进行染色,所以我们就从i *i开始,他在这个数中i是最小的素数因子,且有两个,是一个合数,如果一个合数有这个素因子,且比这个数小,那么他一定被更小的素数染过色

代码实现
int pirme[MAX_N + 5] = {0};
void init() {
    for (int i = 2; i *i <= MAX_N; i++) {
        if (prime[i]) continue;
        for (int j = i * i; j <= MAX_N; j += i) {
            prime[j] = 1;
        }
    }
    for (int i = 2; i <= MAX_N; i++) {
        if (prime[i]) continue;
        prime[++prime[0]] = i;
    }
    return ;
}

线性筛

思路:

线性筛的思路是用最小素因子和另一个数去消去合数,且达到不重复消除的算法,算法枚举的是从2开始的每一个数

  • 这里有一个点需要细细品味一下就是我们通过什么方式才能去达到每个合数只染色一遍

    他在在筛去合数的循环中有一个特殊的跳出判断if (i % prime[j] == 0) break

    解释来说就是如果i里面有这个因子的时候,染完这个数,就该跳出循环了,因为如果我们不跳出循环的化,下一个i* prime[j]所表示的数,里最小素因子一定不是prime[j],因为i里有更小的i素因子,拆分素因子,再重新组合的组合的化,他应该被另一个枚举的更大数,用最小素因子筛去.(例如,25 * 7 = 5 * 5 * 7 = 35 * 5)

代码实现
int is_prime[MAX_N + 5] = {0};
int pirme[MAX_N + 5] = {0};
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (!is_prime[i]) {
            prime[++prime[0]] = i;
        }
        for (int j = 1;j <= prime[0]; j++) {
            if (i * prime[j] > MAX_N) break;//越界
            is_prime[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;//细细品味
        }
    }
    return ;
}
改进版线性筛

对空间进行了改进,因为我们所标记的任何数组元素i * prime[j]一定大与已知的素数的数量,简单证明可得.

int pirme[MAX_N + 5] = {0};
void init() {
    for (int i = 2; i <= MAX_N; i++) {
        if (!prime[i]) {
            prime[++prime[0]] = i;
        }
        for (int j = 1;j <= prime[0]; j++) {
            if (i * prime[j] > MAX_N) break;//越界
            prime[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;//
        }
    }
    return ;
}

罗宾米勒测试

费马小定理

当且尽当p为素数时,下式成立

a(p - 1) mod P ≡ 1

其中a的取值是[1,P-1]中的任意值

说明

Miller-Rabin是随机算法 如果对这个过程重复100次,每次都没说它是合数,那这个数是素数的概率只有(1/2)^5100可能不是素数

代码演示
bool R_M_TEST(int32_t x) {//测试发
    if (x <= 1) return false;//如果小于等于1不用测试
    int64_t a, m, ans;
    for (int32_t i = 0; i < R_M_TEST_ROUND; i++) {//测试轮数
        a = (rand() % (x - 1)) + 1;//生成随机数
        ans = 1;//初始化值
        m = x - 1;//初始化值
        while (m) {//a^(x - 1)mod x == 1
            if (m % 2) ans = (ans * a) % x;//快速幂
            a = (a * a) % x;
            m /= 2;
        }
        if (ans != 1) return false;//如果不等于1返回0
    }
    return true;//等于返回失败
}
扩展阅读

引用:https://www.cnblogs.com/gfvod/p/5548317.html

维基:https://en.wikipedia.org/wiki/Miller–Rabin_primality_test

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值