求素数(质数)

试除法

  质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。如果一个数 z z z 为非素数,则有 x × y = z x\times y = z x×y=z,不妨设 x ≤ y x\le y xy,则有 x ∈ [ 2 , z ] x \in [2, \sqrt{z}] x[2,z ]。因此要验证一个数是否为素数,只需看 [ 2 , z ] [2, \sqrt{z}] [2,z ] 范围内有没有其因数即可。

#include <bits/stdc++.h>
using namespace std;
bool is_prime(int number){
    if(number<2)return false;
    for(int i=2;i<=(int)sqrt(number);i++){
        if(number%i==0)return false;
    }
    return true;
}
int main(){
    int n;
    cin >> n;
    cout << is_prime(n) << endl;
    return 0;
}

筛法

  在 [ 2 , n ] [2,n] [2,n] 的正整数序列中筛除其中的非素数,这是筛法要解决的问题。

埃氏筛法(Eratosthenes筛法)

  不是任一素数的倍数的数一定是素数,这是埃氏筛法基于的思想。假设有 { x ∣ 2 ≤ x ≤ n , x ∈ N } \{x|2\le x\le n,x\in N\} {x2xn,xN} 的正整数序列 A A A,埃氏筛法的算法如下:

  1. 取出当前序列 A i A_i Ai 中的最小数 a i a_i ai a i a_i ai 必为素数)且不再放回序列。
  2. 除去当前序列 A i A_i Ai 中所有 a i a_i ai 的倍数,得到序列 A i + 1 A_{i+1} Ai+1
  3. A i + 1 A_{i+1} Ai+1 为空则算法结束,否则将 A i + 1 A_{i+1} Ai+1 当作当前序列返回第一步。

  接下来使用第二数学归纳法简要证明 a i a_i ai 必为素数:

  1. i = 1 i=1 i=1 ,有 a 1 = 2 a_1=2 a1=2 为素数。
  2. i = k i=k i=k { a 1 , a 2 , ⋯   , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,,ak1} 皆为素数。
  3. 则对于序列 A k A_{k} Ak 中的最小数 a k a_{k} ak,有 { a 1 , a 2 , ⋯   , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,,ak1} 是小于 a k + 1 a_{k+1} ak+1 的全部的素数。因为 A k A_{k} Ak { a 1 , a 2 , ⋯   , a k − 1 } \{a_1,a_2, \cdots, a_{k-1}\} {a1,a2,,ak1} 的倍数皆已被除去, 所以 a k + 1 a_{k+1} ak+1 不是 { a 1 , a 2 , ⋯   , a k } \{a_1,a_2, \cdots, a_k\} {a1,a2,,ak} 的倍数。一个数如果不是小于它的所有素数的倍数,这个数一定是素数。故可知 a k a_{k} ak 是素数。
  4. 故根据第二数学归纳法可知 a i a_i ai 必为素数。

  代码如下:

#include <iostream>
using namespace std;
void filter_prime(bool *const is_prime,size_t range){
    is_prime[0] = is_prime[1] = false;
    for(size_t i=2;i<=range;i++){///假定所有数都为素数
        is_prime[i] = true;
    }
    for(size_t i=2;i<=range;i++){///查找范围内的素数
        if(is_prime[i]){
            for(size_t j=2*i;j<=range;j+=i){///将所有素数的倍数全部标记为假
                is_prime[j] = false;
            }
        }
    }
}
int main(){
    size_t n;
    cin >> n;
    bool *is_prime = new bool[n+10];
    filter_prime(is_prime,n);
    size_t c = 0;
    for(size_t i=0;i<=n;i++){
        c += is_prime[i];
    }
    cout << c << endl;
    delete is_prime;
    return 0;
}

改进的埃氏筛法

  通过埃氏算法我们可以推出它的一个致命缺点,那就是对某些数会进行重复的筛除操作,例如:6 会被素数 2 筛除一次又会被 3 再筛除一次。对埃氏算法的改进就是尽可能的减少重复的筛除操作,改进的地方主要有以下三处:

  1. 初始化
      除去 2 以外的所有偶数都不是素数,因此在初始化时便将其全部筛除。
  2. 倍数的起点
      原本倍数的起点是2,即原本的筛除操作是从素数 a a a 的 2 倍开始的,即 s = 2 a s=2 a s=2a,改进的方法是将起点设为 s = a 2 s= a^2 s=a2
    下证其合理性:
      设 ∀ k ∈ [ 2 , a ) ∈ N \forall k \in[2,a) \in N k[2,a)N
      则必有 k = a ′ ⋅ b k=a^{'} \cdot b k=ab,其中 a ′ a^{'} a 为素数、 b b b 为整数
      则 k ⋅ a = a ′ ⋅ b ⋅ a = c ⋅ a ′ k \cdot a = a^{'} \cdot b \cdot a = c \cdot a^{'} ka=aba=ca,将 b ⋅ a b \cdot a ba 记作 c c c
      由 a ′ < k < a a^{'}<k<a a<k<a
      得 b ⋅ a = c > a ′ b \cdot a=c> a^{'} ba=c>a
      则 k ⋅ a = c ⋅ a ′ k \cdot a = c \cdot a^{'} ka=ca c > a ′ c> a^{'} c>a a > a ′ a> a^{'} a>a
      那么 [ 2 a , a 2 ) [2 a,a^2) [2a,a2) a a a 倍数都可以被一个小于 a a a 的质数 a ′ a^{'} a 乘以一个大于 a ′ a^{'} a 的整数来表示。即这些数已经被较小的质数筛除过了,无需再进行筛除。
      算法是筛除 [ 2 , n ] [2,n] [2,n] 中的非素数,使用本条改进策略后,筛除工作不必在达到 n n n 时才截止,达到 n \sqrt{n} n 时即可截止。因为 [ n , n ] [\sqrt{n},n] [n ,n] 中的素数筛除操作的起点 s = a 2 s= a^2 s=a2 已经大于 n n n
  3. 倍数的增量
      已知素数筛除操作的起点 s = a 2 s= a^2 s=a2,若接下来的筛除操作倍数的增量为奇数,即依次筛除 a 2 , ( a + 1 ) a , ( a + 3 ) a , ( a + 5 ) a , ⋯   , ( a + 2 k + 1 ) a a^2,(a+1)a,(a+3)a,(a+5)a,\cdots,(a+2k+1)a a2,(a+1)a,(a+3)a,(a+5)a,,(a+2k+1)a,是一种重复的筛除。因为质数一定是奇数,两个奇数的和一定是偶数,所以一个质数加一个奇数等于一个偶数,即 a + 2 k + 1 a+2k+1 a+2k+1 一定是偶数。因为偶数乘以任何整数都是偶数,所以 ( a + 2 k + 1 ) a (a+2k+1)a (a+2k+1)a 一定是偶数。因为偶数在初始化时已经筛除,所以该筛除操作是重复的。因此,筛除操作倍数的增量应为偶数,即依次筛除 a 2 , ( a + 2 ) a , ( a + 4 ) a , ( a + 6 ) a , ⋯   , ( a + 2 k ) a a^2,(a+2)a,(a+4)a,(a+6)a,\cdots,(a+2k)a a2,(a+2)a,(a+4)a,(a+6)a,,(a+2k)a

  代码如下:

#include <iostream>
#include <cmath>
using namespace std;
void filter_prime(bool *const is_prime,size_t range){
    is_prime[0] = is_prime[1] = false;
    is_prime[2] = true;
    for(size_t i=3;i<=range;i++){///大于等于三的奇数标为true,偶数标为false
        is_prime[i] = true;
        if(++i>range)break;
        is_prime[i] = false;
    }
    for(size_t i=3;i<=sqrt(range);i+=2){
        if(is_prime[i]){
            for(size_t j=i*i;j<=range;j+=2*i){
                is_prime[j] = false;
            }
        }
    }
}
int main(){
    size_t n;
    cin >> n;
    bool *is_prime = new bool[n+10];
    filter_prime(is_prime,n);
    size_t c = 0;
    for(size_t i=0;i<=n;i++){
        c += is_prime[i];
    }
    cout << c << endl;
    delete is_prime;
    return 0;
}

线性筛法(Eural筛法)

#include <iostream>
#include <cmath>
using namespace std;
size_t filter_prime(bool *const is_prime,size_t range,size_t *const prime){
    is_prime[0] = is_prime[1] = false;
    for(size_t i=2;i<=range;i++){///假定所有数都为素数
        is_prime[i] = true;
    }
    size_t counter = 0;
    for(size_t i=2;i<=range;i++){
        if(is_prime[i]){
            prime[counter++] = i;
        }
        for(size_t j=0;j<counter&&i*prime[j]<=range;j++){
            is_prime[i*prime[j]] = false;
            if(i%prime[j]==0)break;
        }
    }
    return counter;
}
int main(){
    size_t n;
    cin >> n;
    bool *is_prime = new bool[n+10];
    size_t *prime = new size_t[n];
    cout << filter_prime(is_prime,n,prime) << endl;
    delete[] is_prime;
    delete[] prime;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值