判断n以内的素数个数的五种方法+时间对比

目录

方法一:暴力法

复杂度

方法二:跨度为6的倍数的优化

复杂度

方法三:埃氏筛法

复杂度

方法四:埃氏筛法的改良

复杂度

方法五:线性筛

复杂度

性能对比测试

练习


方法一:暴力法

就是写一个函数isprime(int x),依次判断(2,根号x)之内的数能否被x整除。然后依次调用isprime(2,n-1)。

class Solution_1 {//暴力法
public:
    bool isprime_(int x) {
        for (int i = 2; i * i <= x; i++)
            if (x % i == 0) return false;
        return true;
    }
    int countPrimes(int n) {
        int ans = 0;
        for (int i = 2; i < n; i++)
            if (isprime_(i)) ans++;
        return ans;
    }
};

复杂度

时间 O( n*\left ( n^{1/2} \right ) )

空间 O(1)

方法二:跨度为6的倍数的优化

除了2,3之外,其它所有的素数(假设为x)余n后,要么是5,要么是1。

为什么呢?我们假设一个数为x

x%6==0 --> x是0或6的倍数,全都不是素数

x%6==2--> x是2的倍数,其中只有2是素数

x%6==3 --> x是3的倍数,其中只有3是素数

x%6==4 --> x是2的倍数且x>=4,全都不是素数

x%6==1 或 ||x%6==5 --> x有可能是素数

所以我们在isprime(x)函数中,在判断完特例2,3之后,从5开始,每次+6,判断能否被x整除。

同时,调用isprime函数时,也可以跨六步长。

class Solution_2 {//跨度为6的倍数的优化
public:
    bool isprime(int x) {
        if(x<=3) return x>1;
        if (x % 2 == 0 || x % 3 == 0) return false;
        for (int i = 5; i * i <= x; i += 6)
            if (x % i == 0 || x % (i + 2) == 0)
                return false;
        return true;
    }
    int countPrimes(int n) {
        if (n <= 2) return 0;
        if (n == 3) return 1;
        if (n == 4) return 2;
        int ans = 2;
        for (int i = 5; i < n; i += 6) {
            if (isprime(i)) ans++;
            if (i + 2 < n && isprime(i + 2)) ans++;
        }
        return ans;
    }
};

复杂度

时间 O(\frac{1}{6}*n*\frac{1}{6}*n^{1/2})

空间 O(1)

方法三:埃氏筛法

核心思想就是:1、如果x是质数,那么x的倍数肯定不是质数。

                        2、如果x是合数,那么x一定是某个小于x的质数y的倍数。(这就当个结论吧)

class Solution_3 {//埃氏筛法
public:
    int countPrimes(int n) {
        vector<bool> isprime(n, true);
        int ans = 0;
        for (int i = 2; i < n; i++) {
            if (isprime[i] == true) {
                ans++;
                for (int j = 2*i; j < n; j += i)
                    isprime[j] = false;
            }
        }
        return ans;
    }
};

复杂度

时间:O(n*\log \log n

空间:O(n)

方法四:埃氏筛法的改良

观察上面的式子会发现,有些数会重复被标记。

比如说代码中的i==11时,我们从isprime[11]开始标记,isprime[11],isprime[22],……,isprime[121],……都被标记为false。

但是对于isprime[11*2],……,isprime[11*10]这9个位置来说,分别在i==2,3,4,5,6,7,8,9,10的时候就已经被标记了。有的位置甚至被重复标记不止一次,比如说isprime[11*6],在i==2,i==3的时候都被标记了一遍,现在i==11还要被标记一遍。

为了防止以上i*i之前被重复标记的情况,在判断一个数是素数后。我们从i*i开始标记,而不是从2*i开始标记。

注意:i*i可能会造成isprime的数组越界,所以要转换成long long。

class Solution_4 {//埃氏筛法的优化
public:
    int countPrimes(int n) {
        int ans = 0;
        vector<bool> isprime(n, true);
        for (int i = 2; i < n; i++) {
            if (isprime[i]) {
                ans++;
                long long square = (long long)i * i;
                if (square < n) {
                    for (long long j = square; j < n; j += i)
                        isprime[j] = false;
                }
            }
        }
        return ans;
    }
};

复杂度

时间:O(n*\log \log n

空间:O(n)

方法五:线性筛

尽管埃氏筛的改良减少了一些重复标记的情况,但是还会有重复标记的情况。

埃氏筛的改良只能减少i*i前面的数的重复标记的情况,却不能减少i*i后面的数的重复标记的情况。

比如说isprime[45]这个位置,即使用埃氏筛的改良,还是会在i==3和i==5的时候都被标记。

因此我们要想一种方法让每一个数都只被标记一次。

算法的原理可以看力扣上第204题的题解

class Solution_5 {//线性筛法
public:
    int countPrimes(int n) {
        vector<int> primes;
        vector<int> isPrime(n, 1);
        for (int i = 2; i < n; ++i) {
            if (isPrime[i]) {
                primes.push_back(i);
            }
            for (int j = 0; j < primes.size() && i * primes[j] < n; ++j) {
                isPrime[i * primes[j]] = 0;
                if (i % primes[j] == 0) {
                    break;
                }
            }
        }
        return primes.size();
    }
};

复杂度

时间:O(n)

空间:O(n)

性能对比测试

#include <iostream>
#include<vector>
#include<chrono>
using namespace std;

class Solution_1 {//暴力法
public:
    bool isprime_(int x) {
        for (int i = 2; i * i <= x; i++)
            if (x % i == 0) return false;
        return true;
    }
    int countPrimes(int n) {
        int ans = 0;
        for (int i = 2; i < n; i++)
            if (isprime_(i)) ans++;
        return ans;
    }
};

class Solution_2 {//跨度为6的倍数的优化
public:
    bool isprime(int x) {
        if(x<=3) return x>1;
        if (x % 2 == 0 || x % 3 == 0) return false;
        for (int i = 5; i * i <= x; i += 6)
            if (x % i == 0 || x % (i + 2) == 0)
                return false;
        return true;
    }
    int countPrimes(int n) {
        if (n <= 2) return 0;
        if (n == 3) return 1;
        if (n == 4) return 2;
        int ans = 2;
        for (int i = 5; i < n; i += 6) {
            if (isprime(i)) ans++;
            if (i + 2 < n && isprime(i + 2)) ans++;
        }
        return ans;
    }
};

class Solution_3 {//埃氏筛法
public:
    int countPrimes(int n) {
        vector<bool> isprime(n, true);
        int ans = 0;
        for (int i = 2; i < n; i++) {
            if (isprime[i] == true) {
                ans++;
                for (int j = 2*i; j < n; j += i)
                    isprime[j] = false;
            }
        }
        return ans;
    }
};

class Solution_4 {//埃氏筛法的优化
public:
    int countPrimes(int n) {
        int ans = 0;
        vector<bool> isprime(n, true);
        for (int i = 2; i < n; i++) {
            if (isprime[i]) {
                ans++;
                long long square = (long long)i * i;
                if (square < n) {
                    for (long long j = square; j < n; j += i)
                        isprime[j] = false;
                }
            }
        }
        return ans;
    }
};

class Solution_5 {//线性筛法
public:
    int countPrimes(int n) {
        vector<int> primes;
        vector<int> isPrime(n, 1);
        for (int i = 2; i < n; ++i) {
            if (isPrime[i]) {
                primes.push_back(i);
            }
            for (int j = 0; j < primes.size() && i * primes[j] < n; ++j) {
                isPrime[i * primes[j]] = 0;
                if (i % primes[j] == 0) {
                    break;
                }
            }
        }
        return primes.size();
    }
};
int main()
{
    int n=867896;
    cout<<"测试数据:n="<<n<<endl;

    cout << "暴力法\t";
    auto start1 = chrono::system_clock::now();
    Solution_1 s1;
    cout << s1.countPrimes(n) << endl;
    auto end1 = chrono::system_clock::now();
    auto duration1 = chrono::duration_cast<chrono::microseconds>(end1 - start1);
    cout<<"暴力法耗时:\t"<<duration1.count()<<"微秒"<<endl;

    cout << "跨度为6的倍数的优化\t";
    auto start2 = chrono::system_clock::now();
    Solution_2 s2;
    cout << s2.countPrimes(n) << endl;
    auto end2 = chrono::system_clock::now();
    auto duration2 = chrono::duration_cast<chrono::microseconds>(end2 - start2);
    cout<<"跨度为6的倍数的优化耗时:\t"<<duration2.count()<<"微秒"<<endl;
    
    cout << "埃氏筛法\t";
    auto start3 = chrono::system_clock::now();
    Solution_3 s3;
    cout << s3.countPrimes(n) << endl;
    auto end3 = chrono::system_clock::now();
    auto duration3 = chrono::duration_cast<chrono::microseconds>(end3 - start3);
    cout<<"埃氏筛法耗时:\t"<<duration3.count()<<"微秒"<<endl;
    
    
    cout << "埃氏筛法的优化\t";
    auto start4 = chrono::system_clock::now();
    Solution_4 s4;
    cout << s4.countPrimes(n) << endl;
    auto end4 = chrono::system_clock::now();
    auto duration4 = chrono::duration_cast<chrono::microseconds>(end4 - start4);
    cout<<"埃氏筛法的优化耗时:\t"<<duration4.count()<<"微秒"<<endl;
    
    cout << "线性筛法\t";
    auto start5 = chrono::system_clock::now();
    Solution_5 s5;
    cout << s5.countPrimes(n) << endl;
    auto end5 = chrono::system_clock::now();
    auto duration5 = chrono::duration_cast<chrono::microseconds>(end5 - start5);
    cout<<"线性筛法耗时:\t"<<duration5.count()<<"微秒"<<endl;
    
    
    
    return 0;

}

测试结果

测试数据:n=867896
暴力法  68937
暴力法耗时:    84030微秒
跨度为6的倍数的优化     68937
跨度为6的倍数的优化耗时:       29138微秒
埃氏筛法        68937
埃氏筛法耗时:  739268微秒
埃氏筛法的优化  68937
埃氏筛法的优化耗时:    595306微秒
线性筛法        68937
线性筛法耗时:  39351微秒

练习

https://leetcode.cn/problems/count-primes/

相关推荐

  1. 判断素数方法

    2024-04-28 12:26:04       5 阅读
  2. C语言判断一个是否为素数方法(详细)

    2024-04-28 12:26:04       20 阅读
  3. 判断素数方法大全

    2024-04-28 12:26:04       42 阅读
  4. 用c语言统计m~n之间素数,并求素数和。

    2024-04-28 12:26:04       17 阅读
  5. (c语言)素数判断方法

    2024-04-28 12:26:04       44 阅读
  6. Python代码实现求n以内最大k素数

    2024-04-28 12:26:04       12 阅读
  7. 题目 1022: [编程入门]筛选N以内素数

    2024-04-28 12:26:04       32 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-28 12:26:04       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-28 12:26:04       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-28 12:26:04       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-28 12:26:04       18 阅读

热门阅读

  1. 笔记:Python 注释(练习题)

    2024-04-28 12:26:04       13 阅读
  2. milvus indexcoord启动源码分析

    2024-04-28 12:26:04       30 阅读
  3. C++可调用对象的绑定器和包装器

    2024-04-28 12:26:04       10 阅读
  4. 探索Kotlin:最佳学习实践和资源指南

    2024-04-28 12:26:04       10 阅读
  5. XSS攻击

    XSS攻击

    2024-04-28 12:26:04      10 阅读
  6. 墨子时事周报

    2024-04-28 12:26:04       11 阅读
  7. C# 字符串左不足位数时补充0

    2024-04-28 12:26:04       12 阅读
  8. transformers - 预测中间词

    2024-04-28 12:26:04       11 阅读
  9. opencv动态识别人脸

    2024-04-28 12:26:04       10 阅读
  10. L2-052 吉利矩阵

    2024-04-28 12:26:04       11 阅读
  11. Centos编译安装python3.9

    2024-04-28 12:26:04       13 阅读
  12. 生成对抗网络(GAN)

    2024-04-28 12:26:04       11 阅读