“计数质数”问题的常规思路和Sieve of Eratosthenes算法分析
题目描述
题目来源于 LeetCode 204.计数质数,简单来讲就是求“不超过整数 n 的所有素数个数”。
常规思路
一般来讲,我们会先写一个判断 a 是否为素数的 isPrim(int a) 函数:
bool isPrim(int a){
for (int i = 2; i < a; i++)
if (a % i == 0)//存在其它整数因子
return false;
return true;
}
然后我们会写一个 countIsPrim(int n) 来计算不超过 n 的所有素数个数:
int countPrimes(int n) {
int ans = 0;
for (int i = 2; i < n; i++)
if (isPrim(i)) ans++;
return ans;
}
显然这两个嵌套的 for 循环时间复杂度是 \(O(n^2)\) ,但是这样写有两个主要的问题:
isPrim()函数的计算冗余。首先,举个例子引入一下因子的对称性:12 = 2 × 6
12 = 3 × 4
12 = sqrt(12) × sqrt(12)
12 = 4 × 3
12 = 6 × 2
所以当循环判断一个数 \(a\) 是否有除了 1 和它本身之外其余的因子时,我们只需要将循环变量终止在 \(\sqrt a\) 的位置,而非
[2, a)的所有数。countPrimes(int n)函数的计算冗余。例如,一旦我们判断2为质数那么所有2的倍数一定都是质数,如果我们知道3是质数,那么所有3的倍数也一定都是质数。所以如果我们将[2, n)的数都进行一次isPrim(),那么将带来巨大的时间浪费。
我们在这里,先将第一个问题解决,即优化 isPrim() 函数:
bool isPrim(int n){
//根据因子对称性
for (int i = 2; i * i <= n; i++){
if (n % i == 0)//存在其它整数因子
return false;
}
return true;
}
采用 Sieve of Eratosthenes 算法高效实现
这个算法的中文叫作“埃拉托斯特尼筛法”,听起来很复杂,但是并不难理解,本质上就是把常规思路反过来,如下面动图所示:

下面我们逐渐引出该算法的全貌:
常规思路就是将区间为 [2, n) 的数都遍历一遍,在过程中累加素数的个数。上述问题二已经说明了其低效性,根据“如果 i 是质数,那么所有 i 的倍数都不是质数”,我们做出优化:
int countPrimes(int n) {
vector<int> IsPrim(n + 1, true);
for (int i = 2; i < n; i++){
if (isPrim[i]){
//如果i是质数,那么所有i的倍数都不是质数
for (int j = 2 * i; j < n; j += i){
IsPrim[j] = false;
}
}
}
//遍历一遍计算结果
int ans = 0;
for (int i = 2; i < n; i++){
if (IsPrim[i]) ans++;
}
return ans;
}
这段代码展现了该算法的整体思路,但是还有两个细节可以优化:
- 由于因子的对称性,我们可以将外层 for 循环改为:
for (int i = 2; i * i < n; i++)。 - 将内层循环改为:
for (int j = i * i; j < n; j += i)。举个例子,n = 25,当i = 4时算法会标记 4 × 2 = 8,4 × 3 = 12 等等数字,但是这两个数字已经被i = 2和i = 3的 2 × 4 和 3 × 4 标记了。所以我们可以从平方项开始遍历。
到这里,Sieve of Eratosthenes 算法就已经实现了,下面给出完整的代码:
int countPrimes(int n) {
vector<int> prims(n + 1, 1);
for (int i = 2; i * i < n; i++){
if (isPrim[i]){
//如果i是质数,那么所有i的倍数都不是质数
for (int j = i * i; j < n; j += i){
prims[j] = 0;
}
}
}
//遍历一遍计算结果
int ans = 0;
for (int i = 2; i < n; i++){
if(prims[i]) ans++;
}
return ans;
}
Sieve of Eratosthenes 算法的证明
该算法的时间复杂度为 \(O(nloglogn)\) ,下面给出三个公式和证明:
\(Prerequisite\).
调和级数(Harmonic series)是一个发散的无穷级数,当 \(n\) 趋近于无穷大时,有一个近似公式:
\]
其中 \(\gamma\) 为欧拉常数,\(\gamma \approx 0.57721\)
泰勒级数(Taylor series)是1715年英国数学家布鲁克·泰勒提出的,在零点的导数求得的泰勒级数又叫麦克劳林级数,一个常用的泰勒级数如下:
\]
对任意 \(x \in [-1,1)\) 都成立。
欧拉乘积公式(Euler product)是著名的瑞士数学家欧拉于1737年在俄罗斯的圣彼得堡科学院发表的重要公式,为数学家研究素数的分布奠定了基础,即:
\]
其中 \(n\) 是自然数,\(p\) 为素数。
\(Prove.\)
该算法的运行时间可以看作筛除的次数之和:
\]
显然我们需要想办法处理后面的质数倒数和。我们拿出欧拉乘机公式,将所有的 \(s\) 用1来代替:
\]
两侧同时取对数:
\]
由于 \(-1< p^{-1} < 1\) ,所以对上面右侧求和的每一项进行泰勒展开得到:
\]
故得到:
\begin{equation}
\begin{split}
ln(\sum_{n}^{}\frac{1}{n}) &= \sum_{p}^{}\frac{1}{p} + \sum_{p}^{}\frac{1}{p^2}(\frac{1}{2} + \frac{1}{3p} + \frac{1}{4p^2}+\cdot\cdot\cdot)\\
&< \sum_{p}^{}\frac{1}{p} + \sum_p\frac{1}{p^2}(1+\frac{1}{p}+\frac{1}{p^2}+\cdot\cdot\cdot)\\
&= \sum_{p}^{}\frac{1}{p}+\sum_p\frac{1}{p(p-1)}\\
&= \sum_p\frac{1}{p} + C
\end{split}
\end{equation}
\]
上式左侧带入调和级数,当 \(n\) 趋向于无穷时得到:
\]
到这里就成功处理掉了质数倒数和,所以时间复杂度为 \(O(nloglogn)\) 。
耗时比较
未改进版 \(isPrim()\) 、改进版 \(isPrim()\) 、高效算法三者耗时对比如下,可以看出来差距还是很大的:



参考资料
“计数质数”问题的常规思路和Sieve of Eratosthenes算法分析的更多相关文章
- [LeetCode] 204. Count Primes 计数质数
Description: Count the number of prime numbers less than a non-negative number, n click to show more ...
- Leecode刷题之旅-C语言/python-204计数质数
/* * @lc app=leetcode.cn id=204 lang=c * * [204] 计数质数 * * https://leetcode-cn.com/problems/count-pri ...
- Leetcode 204计数质数
计数质数 统计所有小于非负整数 n 的质数的数量. 示例: 输入: 10 输出: 4 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 . 比计算少n中素数的个数. 素数又称质 ...
- Java实现 LeetCode 204 计数质数
204. 计数质数 统计所有小于非负整数 n 的质数的数量. 示例: 输入: 10 输出: 4 解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 . class Solutio ...
- [原]素数筛法【Sieve Of Eratosthenes + Sieve Of Euler】
拖了有段时间,今天来总结下两个常用的素数筛法: 1.sieve of Eratosthenes[埃氏筛法] 这是最简单朴素的素数筛法了,根据wikipedia,时间复杂度为 ,空间复杂度为O(n). ...
- 埃拉托色尼筛法(Sieve of Eratosthenes)求素数。
埃拉托色尼筛法(Sieve of Eratosthenes)是一种用来求所有小于N的素数的方法.从建立一个整数2~N的表着手,寻找i? 的整数,编程实现此算法,并讨论运算时间. 由于是通过删除来实现, ...
- algorithm@ Sieve of Eratosthenes (素数筛选算法) & Related Problem (Return two prime numbers )
Sieve of Eratosthenes (素数筛选算法) Given a number n, print all primes smaller than or equal to n. It is ...
- 使用埃拉托色尼筛选法(the Sieve of Eratosthenes)在一定范围内求素数及反素数(Emirp)
Programming 1.3 In this problem, you'll be asked to find all the prime numbers from 1 to 1000. Prime ...
- Sieve of Eratosthenes时间复杂度的感性证明
上代码. #include<cstdio> #include<cstdlib> #include<cstring> #define reg register con ...
随机推荐
- PHP var_export() 函数
var_export() 函数用于输出或返回一个变量,以字符串形式表示.高佣联盟 www.cgewang.com高佣联盟 www.cgewang.com var_export() 函数返回关于传递给该 ...
- Fragment为什么须要无参构造方法
日前在项目代码里遇到偷懒使用重写Fragment带参构造方法来传参的做法,顿生好奇,继承android.support.v4.app.Fragment而又不写无参构造方法不是会出现lint错误编译不通 ...
- intel:spectre&Meltdown侧信道攻击(五)—— DRAM address mapping
前面介绍了row hammer,理论上很完美,实际操作的时候会面临很尴尬的问题:内存存储数据最小的单位是cell(就是个电容,充电是1,放电是0),无数个横着的cell组成row,无数个竖着的cell ...
- LeetCode 164. Maximum Gap[翻译]
164. Maximum Gap 164. 最大间隔 Given an unsorted array, find the maximum difference between the successi ...
- 提交项目到码云上git的使用
git clone .. cd 到项目目录 git branch 查看当前的所有分支 git branch shanshan 创建一个属于自己的分支 git checkout shansha ...
- cocos2d-x_下载游戏引擎并创建第一个项目
我是一名小白. 下载并创建游戏项目 第一步:去官网下载cocos2d-x http://www.cocos.com/download 第二步:将安装包里边的 setup.py 拖进命令行点击回车键 , ...
- Navicat15安装教程
本文内容皆为作者原创,如需转载,请注明出处:https://www.cnblogs.com/xuexianqi/p/12797170.html 一:简介 Navicat是一套快速.可靠的数据库管理工具 ...
- three.js 着色器材质之变量(一)
上一篇说顶点着色器和片元着色器的皮毛,这篇郭先生说一说着色器变量,通过变量可以设置材质.先看看今天要做的如下图.在线案例请点击博客原文. 在这个案例之前,我们先复习一下着色器变量 Uniforms是所 ...
- CF1349F 【Slime and Sequences】part1
由于本文过长,\(\LaTeX\) 炸了,分两篇,part2 题目描述 定义一个正整数序列为好序列,当且仅当如果某个数 \(k\) 出现过,那么一定有 \(k-1\) 在最后一个 \(k\) 的前面出 ...
- JS 窗口加载与定时器笔记
bom浏览器对象模型 bom由一系列相关的对象构成并且每个对象都提供了很多方法属性 bom顶级对象是window bom是浏览器产商在各自浏览器上定义的,兼容性差 wi ...