三十三、数学知识——质数(朴素筛法 + 埃氏筛法 + 线性筛法)

一、基本思路

1、质数

  • 定义:
    • 在大于 1 的整数中,如果只包含 1 和本身这两个约数,就被称为质数,或者叫素数,与之相反的是合数。
质数的判定——试除法(复杂度固定位 O(sqrt(n)) )
  • 概述:
    • 实际是一个找因数的过程,只不过通过条件判定之后,确定只有 1 与本身外,才可以确定为质数。
  • 原理:
    1. 定义一个符号:“||” 称为整除, a||b:即a为b的约数之一,b除以a可以得到一个整数。
    1. 例:若d||n,则(n||d)|| n,我们只需要取得比较小的那个约数就好了。
    1. 用if(n%i == 0)来判断是否是质数,若满足,则不是质数——>实际上也是在遍历因数。
    1. 原先根据定义,我们需要将约数遍历至d,但是根据上述理论,我们只需要遍历至d<=(n/d),即为 d.^2 <=n;
    1. for(int i = 2; i <= n/i; i++)是为了遍历寻找因数
    1. 注意:
      • 1)不用sqrt(n),是因为sqrt比较慢。
      • 2)不用d.^2 <= n,是怕 d.^2太大而溢出。

2、分解质因数——试除法(最坏是O(sqrt(n)))

  • 时间复杂度:
    • 最好情况是logn(2的k次方),最坏是sqrt(n)
  • 目的:
    • 找出所有的因数中的质数/找出约数集合中的所有质数
  • 原理:
    • 正整数唯一分解定理(质因数分解定理)

在这里插入图片描述

  • 算法步骤:
for(int i = 2; i <= n/i; i++){
        // 问题:我们不应该÷的是质因子嘛?为什么是遍历全部?
        // 我们从质因子2开始由小变大,由于n/i的存在,
        // 所有由质因子构成的非质因子,在已经被质因子除掉,无须再考虑,因此此处遍历始终是质因子。
        
        // 定理:质因数分解定理
        // 任何一个大于1的正整数都可以唯一的分解成质数的乘积。
            if(n % i == 0){
                int s = 0;
                while(n % i ==0){
                    n /= i;             // n多次÷i,则由质数构成的合数被干掉了
                    s++;                // 记录质因子的质数(次数)
                }
                System.out.println(i + " " + s);
            }
        }
        
        if(n > 1){      
            // 此处判断是因为上面遍历到最后的n/i的时候,可能没有除尽,还剩下最后一个质数,需要判断输出
            System.out.println(n + " " + 1);
        }
  • 由 性质:n中最多只包含一个大于sqrt(n)的质因子,则优化为 for(int i = 2; i <= n/i; i++),只不过需要最后加一个:最后一步的 n/=i 之后的n值,输出最后一个质因子(if(n > 1) cout)

3、朴素筛法——筛的是倍数

  • 目的:
    • 求出 1~n中质数的个数
  • 基本思路:O(nlogn)
    • 从2开始进行遍历,遍历至n,将每一个遍历数的倍数删掉,剩下的即为质数。

在这里插入图片描述

4、埃氏筛法——朴素筛法优化

  • 优化:
    • 只筛掉其中质数的倍数即可,即 2~p-1之中的质数
    • 具体操作就是在质数判断中加入倍数筛除

5、线性筛法——n(遍历值)只会被最小质因子筛掉

  • 原理:
    • 使用最小质因子进行筛除
for(int i = 2; i <= n; i++){
   if(!st[i]){
       primes[count++] = i;            // 记录遍历过程中的质数
   }

   for(int j = 0; j <= n/i; j++){      // 筛掉其中质数构成的合数

      st[primes[j] * i] = true;       // 使用质因数prime来进行筛选
      // 当下面条件满足的时候,pjU一定是最小的质因子
      // 并且 pj 一定是 pj * i 的最小质因子
      if(i % primes[j] == 0){         
         // 当此处满足的时候,也就意味prime是i最小的质因数
         // 1、prime是从小到大存储的,因此一定是最小的
         // 2、当满足条件的时候,直接i的继续筛的过程结束,找到第一个质因数,则跳过,始终满足最小
            break;
       }
    }
}

  • 注意:
    1. i % prime[j] == 0 ,prime[j] 一定是 i 的最小质因子,pj一定是 pj * i 的最小质因子。
    1. i % prime[j] == 0,pj 一定小于所有质因子(break的存在,找到则跳出),pj也一定是 pj*i的最小质因子。
    1. 对于一个合数x,假设pj是x的最小质因子,当i美剧导 x/pj的时候,已经被筛掉
    1. 在10的7次方范围内,线性筛法比埃氏筛法快一倍。
    1. **综合1、2可知,在内层for循环里面,无论何时,prime[j]都是prime[j]*i的最小质因子,因此st[prime[j]*i] = true.

二、Java、C语言模板实现

// java 模板
// 1、质数判定
    static boolean isPrime(int n){

        if(n < 2){                      // 按照定义质数是大于 1 的整数
            return false;
        }

        for(int i = 2; i <= n/i; i ++){     
        // 此处之所以是 i <= n/i ,是因为我们只需要遍历最小的部分约数即可,其他可以由最小的一部分约数构成
            if(n % i == 0){         // 存在除了 1 和本身之外的约数
                return false;
            }
        }

        return true;
    }


// 2、朴素筛法/埃氏筛法

    static int N = 1000010;
    static boolean[] st = new boolean[N];
    static int primeNum;

    static void getPrimeNum(int n){

        for(int i = 2; i <= n; i++){                // 从2开始一直遍历到n-1的倍数
            if(st[i] == false){                     // 未被删掉,说明是质数
                primeNum++;
                for(int j = i + i; j <=n; j += i){  
                // 我们只需遍历质数的倍数就可以了,因为其他数都可以分解成质数
                    st[j] = true;
                }
            }
        }

        System.out.println(primeNum);

    }

// 3、线性筛法
    static void getPrime(int n){

        for(int i = 2; i <= n; i++){
            if(!st[i]){
                primes[count++] = i;            // 记录遍历过程中的质数
            }

            for(int j = 0; j <= n/i; j++){      // 筛掉其中质数构成的合数

                st[primes[j] * i] = true;       // 使用质因数prime来进行筛选
                // 当下面条件满足的时候,pjU一定是最小的质因子
                // 并且 pj 一定是 pj * i 的最小质因子
                if(i % primes[j] == 0){         
                // 当此处满足的时候,也就意味prime是i最小的质因数
                // 1、prime是从小到大存储的,因此一定是最小的
                // 2、当满足条件的时候,直接i的继续筛的过程结束,找到第一个质因数,则跳过,始终满足最小
                    break;
                }
            }
        }

        System.out.println(count);
    }



// C++ 模板,由yxc实现
// 1、试除法判定质数 —— 模板题 AcWing 866. 试除法判定质数
bool is_prime(int x)
{
    if (x < 2) return false;
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
            return false;
    return true;
}


// 2、试除法分解质因数 —— 模板题 AcWing 867. 分解质因数
void divide(int x)
{
    for (int i = 2; i <= x / i; i ++ )
        if (x % i == 0)
        {
            int s = 0;
            while (x % i == 0) x /= i, s ++ ;
            cout << i << ' ' << s << endl;
        }
    if (x > 1) cout << x << ' ' << 1 << endl;
    cout << endl;
}


// 3、朴素筛法求素数 —— 模板题 AcWing 868. 筛质数
int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (st[i]) continue;
        primes[cnt ++ ] = i;
        for (int j = i + i; j <= n; j += i)
            st[j] = true;
    }
}

// 4、线性筛法求素数 —— 模板题 AcWing 868. 筛质数
int primes[N], cnt;     // primes[]存储所有素数
bool st[N];         // st[x]存储x是否被筛掉

void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

三、例题题解

1. 质数判定

在这里插入图片描述

// java题解实现
import java.util.*;
import java.io.*;

public class Main{

    static boolean isPrime(int n){

        if(n < 2){                      // 按照定义质数是大于 1 的整数
            return false;
        }

        for(int i = 2; i <= n/i; i ++){     
        // 此处之所以是 i <= n/i ,是因为我们只需要遍历最小的部分约数即可,其他可以由最小的一部分约数构成
            if(n % i == 0){         // 存在除了 1 和本身之外的约数
                return false;
            }
        }

        return true;
    }


    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String[] str1 = reader.readLine().split(" ");

        int n = Integer.parseInt(str1[0]);

        for(int i = 0; i < n; i++){
            String str2 = reader.readLine();
            int ai = Integer.parseInt(str2);
            if(isPrime(ai)){
                System.out.println("Yes");
            }else{
                System.out.println("No");
            }
        }

    }

}
2.分解质因数

在这里插入图片描述

import java.util.*;
import java.io.*;


public class Main{
    
    static void getPrime(int n){
        
        for(int i = 2; i <= n/i; i++){
        // 问题:我们不应该÷的是质因子嘛?为什么是遍历全部?
        // 我们从质因子2开始由小变大,由于n/i的存在,
        // 所有由质因子构成的非质因子,在已经被质因子除掉,无须再考虑,因此此处遍历始终是质因子。
        
        // 定理:质因数分解定理
        // 任何一个大于1的正整数都可以唯一的分解成质数的乘积。
            if(n % i == 0){
                int s = 0;
                while(n % i ==0){
                    n /= i;             // n多次÷i,则由质数构成的合数被干掉了
                    s++;                // 记录质因子的质数(次数)
                }
                System.out.println(i + " " + s);
            }
        }
        
        if(n > 1){      
            // 此处判断是因为上面遍历到最后的n/i的时候,可能没有除尽,还剩下最后一个质数,需要判断输出
            System.out.println(n + " " + 1);
        }
        System.out.println();
    }
    
    public static void main(String[] args) throws IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String str1 = reader.readLine();
        
        int n = Integer.parseInt(str1);
        
        for(int i = 0; i < n; i++){
            String str2 = reader.readLine();
            int ai = Integer.parseInt(str2);
            getPrime(ai);
        }
    }
}
3. 筛法——筛出1~n个质数(埃氏筛法与线性筛法)

在这里插入图片描述

// 埃氏筛法
import java.util.*;

// 原理:假定有一个数p未被2~(p-1)中的数标记过,那么说明,不存在2~(p-1)中的任何一个数的倍数是p,
// 也就是说p不是2~(p-1)中的任何数的倍数,也就是说2~(p-1)中不存在p的约数,因此,根据质数的定义可知:
// p是质数.
public class Main{
    static int N = 1000010;
    static boolean[] st = new boolean[N];
    static int primeNum;

    static void getPrimeNum(int n){

        for(int i = 2; i <= n; i++){                // 从2开始一直遍历到n-1的倍数
            if(st[i] == false){                     // 未被删掉,说明是质数
                primeNum++;
                for(int j = i + i; j <=n; j += i){  
                // 我们只需遍历质数的倍数就可以了,因为其他数都可以分解成质数
                    st[j] = true;
                }
            }
        }

        System.out.println(primeNum);

    }
    public static void main(String[] args){
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        getPrimeNum(n);

    }
}


// 线性筛法
import java.util.*;

public class Main{
    static int N = 10000010;
    static int[] primes = new int[N];
    static boolean[] st = new boolean[N];
    static int count = 0;

    static void getPrime(int n){

        for(int i = 2; i <= n; i++){
            if(!st[i]){
                primes[count++] = i;            // 记录遍历过程中的质数
            }

            for(int j = 0; j <= n/i; j++){      // 筛掉其中质数构成的合数

                st[primes[j] * i] = true;       // 使用质因数prime来进行筛选
                // 当下面条件满足的时候,pjU一定是最小的质因子
                // 并且 pj 一定是 pj * i 的最小质因子
                if(i % primes[j] == 0){         
                // 当此处满足的时候,也就意味prime是i最小的质因数
                // 1、prime是从小到大存储的,因此一定是最小的
                // 2、当满足条件的时候,直接i的继续筛的过程结束,找到第一个质因数,则跳过,始终满足最小
                    break;
                }
            }
        }

        System.out.println(count);
    }


    public static void main(String[] args){
        Scanner in = new Scanner(System.in);

        int n = in.nextInt();

        getPrime(n);
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
埃氏筛法(Eratosthenes筛法)和线性筛(Linear Sieve)都是用于求解素数的算法。它们的主要区别在于筛选过程中的操作不同。 1. 埃氏筛法埃氏筛法是一种古老的求解素数的算法,由古希腊数学家埃拉托斯特尼(Eratosthenes)提出。它的基本思想是从2开始,将所有2的倍数标记为非素数,然后找到下一个未被标记的数(即3),将所有3的倍数标记为非素数,依此类推。最后留下的未被标记的数就是素数。 C++实现埃氏筛法的代码如下: ```cpp #include <iostream> #include <vector> using namespace std; const int N = 1000000;vector<int> is_prime(N, true); void sieve_of_eratosthenes() { is_prime = is_prime = false; for (int i = 2; i * i < N; ++i) { if (is_prime[i]) { for (int j = i * i; j < N; j += i) { is_prime[j] = false; } } } } int main() { sieve_of_eratosthenes(); for (int i = 2; i < N; ++i) { if (is_prime[i]) { cout << i << " "; } } return 0; } ``` 2. 线性筛线性筛是一种改进的埃氏筛法,它将筛选过程从平方根优化到线性时间。线性筛的基本思想是对于每个素数p,筛选出所有小于等于p^2的合数。这样可以减少筛选的次数,提高效率。 C++实现线性筛的代码如下: ```cpp #include <iostream> #include <vector> using namespace std; const int N = 1000000; vector<int> is_prime(N, true); vector<int> primes; void linear_sieve() { is_prime = is_prime = false; for (int p = 2; p * p < N; ++p) { if (is_prime[p]) { for (int i = p * p; i < N; i += p) { is_prime[i] = false; } } } for (int p = 2; p < N; ++p) { if (is_prime[p]) { primes.push_back(p); } } } int main() { linear_sieve(); for (int i = 0; i < primes.size(); ++i) { cout << primes[i] << " "; } return 0; } ``` 这两种算法都可以有效地求解素数,但线性筛相对于埃氏筛法在筛选次数上有很大优势。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牙否

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值