这是两种简单的素数筛法, 好不容易理解了以后写篇博客加深下记忆

首先, 这两种算法用于解决的问题是 :

求小于n的所有素数 ( 个数 )

比如 这道题

在不了解这两个素数筛算法的同学, 可能会这么写一个isPrime, 然后遍历每一个数, 挨个判断 :

  1. 从2判断到n-1
bool isPrime(int n) {
for (int i = 2; i < n; i++)
if (n % i == 0)
return false;
return true;
}
  1. 从2判断到\(\sqrt{n}\)
bool isPrime(int n) {
for (int i = 2; i <= sqrt(n); i++)
if (n % i == 0)
return false;
return true;
}

然后再加上个count函数 :

int countPrimes2(int n) {
int cnt = 0;
for(int i = 2; i < n; ++i) {
if(isPrime(i)) cnt++;
}
return cnt;
}

这两种算法, 一个时间复杂度$ O(n^2) $ , 另一个 $ O(n \sqrt{n}) $ , 但凡出现这样类型的题, 这么写一般都超时了

下面介绍第一种算法

Eratosthenes 筛法 (厄拉多塞筛法)

核心思想 : 对于每一个素数, 它的倍数必定不是素数

我们通过直接标记, 可以大大减少操作量

比如从2开始遍历, 则4, 6, 8, 10, 12, 14....都不是素数,

然后是3, 则3, 6, 9, 12, 15.....都不是素数

如图更加直观 :

我们可以在判断小于等于数字n的时候增加一个vis数组, 他的大小等于n, 默认vis初始化为全零, 假设2-n都是素数, 接下来每遇到一个素数i, 把2-n中vis[i的倍数] = 1, 在接下来遇到的时候直接跳过就可以.

时间复杂度为 : $ O(nloglogn) $

代码 :

 int countPrimes(int n) {
vector<int> vis(n, 0); // all number : 0 means prime, 1 means not prime
vector<int> prime(n, 0);
int cnt = 0;
for (int i = 2; i < n; ++i) {
if (!vis[i]) { // 如果vis[i]未被标记, 表明i是素数
prime[++cnt] = i;
for (int j = i*2; j < n; j += i) {
vis[j] = 1; // 标记所有i的倍数
}
}
}
return cnt;
}

线性筛作为对Eratosthenes筛的改进, 能更大程度的减少时间复杂度:

O(n)的筛法----线性筛 ( 欧拉筛 )

在讲这个筛法之前, 明确一个概念 :

每一个合数 ( 除了1和它本身以外,还能被其他正整数整除 ), 都可以表示成n个素数的乘积

\[\boldsymbol{X}=\boldsymbol{p}_1\times \boldsymbol{p}_2\times \boldsymbol{p}_3\times \boldsymbol{p}_4\times ...\times \boldsymbol{p}_{\boldsymbol{i}}\,\,, \boldsymbol{X}为\text{合数}, \boldsymbol{p}为\text{素数}
\]

在Eratosthenes筛中, 我们可以发现, 在排除每一个质数的倍数时, 会有很多重复的操作 :

比如30 = 2x3x5, 那么30将在对2,3,5倍数标记的时候反复被处理3次

我们可以思考, 是否有一种方法, 可以仅仅将一个合数标记一次, 就可以打到筛选的目的呢 ?

可以设立一种规则 :

一个合数只能被他的最小素数因子筛去

比如30, 虽然他有2,3,5三个素因子, 我们只让在对2的倍数作标记时标记30

这样做, 可以保证我们对每一个数都仅仅只操作了一次, 时间复杂度也就变成了喜闻乐见的O(n) !

具体怎样实现 ?

我们在遍历2-n的过程中, 对每一个数i , 从当前已经找到的素数集中从2开始列出prime[j], 当什么时候有:

\[i\,\,\% \,prime\left[ j \right] ==\,\,0
\]

那么可以说明i含有prime[j]这个素因数, 也就是说, 对于比目前 i x prime[j]更大的数, prime[j]将不再是它的最小素因数, 因此到此这一轮筛可以停止了.

举个例子 :

当 i = 9 时 :

我们可以筛出 :

9 x 2 = 18 ---- 然后进行判断 9%2 != 0, 因此继续筛

9 x 3 = 27 ---- 然后进行判断 9%3 ==0, 因此break

假设我们继续筛 9 x 5, 会发生什么?

由于9 % 3 == 0了, 则可以知道 9含有3这个素因数, 如果继续筛9x5, 可以知道5并不是9x5的最小素因数, 因此不应当在这一轮被筛去.

代码 :

int countPrimes(int n) {
vector<int> vis(n, 0); // all number : 0 means prime, 1 means not prime
vector<int> prime(n, 0);
int cnt = 0;
for (int i = 2; i < n; ++i) {
if (!vis[i])
prime[cnt++] = i;
for(int j = 0; j < cnt && i * prime[j] < n; ++j)
{
vis[i*prime[j]] = 1;
if (i % prime[j] == 0) // if prime[j] is i*prime[j]'s minimum prime
break;
}
}
return cnt;
}

时间比较

写了个cpp计时, 来比较一下这几种算法的时间吧~

可以看到, Eratosthenes和线性筛的时间稍有区别, 加大数据量会放大这个区别, 但是麻瓜筛\(O(n^2)\) 在n=7000000的时候已经10s+了, 再加大数据量估计得等到明天了

在写题的这段日子, 每天都在看别人代码的卧槽声中度过


ref :

OI - wiki 筛法 : https://oi-wiki.org/math/sieve/

线性筛法求素数的原理与实现 : https://wenku.baidu.com/view/4881881daaea998fcc220e99.html

leetcode题解 : https://leetcode-cn.com/problems/count-primes/solution/

素数筛 : Eratosthenes 筛法, 线性筛法的更多相关文章

  1. [算法]素数筛法(埃氏筛法&线性筛法)

    目录 一.素数筛的定义 二.埃氏筛法(Eratosthenes筛法) 三.线性筛法 四.一个性质 一.素数筛的定义 给定一个整数n,求出[1,n]之间的所有质数(素数),这样的问题为素数筛(素数的筛选 ...

  2. 素数筛总结篇___Eratosthenes筛法和欧拉筛法(*【模板】使用 )

    求素数 题目描述 求小于n的所有素数的数量. 输入 多组输入,输入整数n(n<1000000),以0结束. 输出 输出n以内所有素数的个数. 示例输入 10 0 示例输出 4 提示 以这道题目为 ...

  3. ACM_哥德巴赫猜想(素数筛)

    哥德巴赫猜想 Time Limit: 2000/1000ms (Java/Others) Problem Description: 哥德巴赫猜想大概是这么一回事:“偶数(>=4) == 两个质数 ...

  4. 『素数 Prime判定和线性欧拉筛法 The sieve of Euler』

    素数(Prime)及判定 定义 素数又称质数,一个大于1的自然数,除了1和它自身外,不能整除其他自然数的数叫做质数,否则称为合数. 1既不是素数也不是合数. 判定 如何判定一个数是否是素数呢?显然,我 ...

  5. <转载>一般筛法和快速线性筛法求素数

    素数总是一个比较常涉及到的内容,掌握求素数的方法是一项基本功. 基本原则就是题目如果只需要判断少量数字是否为素数,直接枚举因子2 ..N^(0.5) ,看看能否整除N. 如果需要判断的次数较多,则先用 ...

  6. noip知识点总结之--线性筛法及其拓展

    一.线性筛法 众所周知...线性筛就是在O(n)的时间里找出所有素数的方法 code: void get_prime(int N){ int i, j, k; memset(Flag, ); ; i ...

  7. Project Euler 46 Goldbach's other conjecture( 线性筛法 )

    题意: 克里斯蒂安·哥德巴赫曾经猜想,每个奇合数可以写成一个素数和一个平方的两倍之和 9 = 7 + 2×1215 = 7 + 2×2221 = 3 + 2×3225 = 7 + 2×3227 = 1 ...

  8. 线性素数筛(欧拉筛)(超级好的MuBan)

    Problem:找出小于等于n的所有素数的个数. #include <bits/stdc++.h> using namespace std; const int maxn = 1e6; i ...

  9. POJ 3126 Prime Path (bfs+欧拉线性素数筛)

    Description The ministers of the cabinet were quite upset by the message from the Chief of Security ...

随机推荐

  1. BZOJ 1028 BZOJ 1029 //贪心

    1028: [JSOI2007]麻将 Time Limit: 1 Sec  Memory Limit: 162 MBSubmit: 2197  Solved: 989[Submit][Status][ ...

  2. Docker搭建VS Code Server ,设置访问密码随时随地写代码

    今天在N1盒子上安装了 VS Code Server,简单的记录一下. 安装docker Docker一键安装脚本 $ sudo wget -qO- https://get.docker.com/ | ...

  3. 基于 kubeadm 搭建高可用的kubernetes 1.18.2 (k8s)集群二 搭建高可用集群

    1. 部署keepalived - apiserver高可用(任选两个master节点) 1.1 安装keepalived # 在两个主节点上安装keepalived(一主一备) $ yum inst ...

  4. html5学习之路_001

    安装环境 安装intellij idea作为开发环境 打开环境 新建一个html文件,打开之后出现代码框架,再次基础上继续编码即可,例如: <!DOCTYPE html> <html ...

  5. [优文翻译]003.你应避免的移动开发APP的5个细节(5 Things to Avoid while Developing Your Next Mobile App)

    导读:本文是从<5 Things to Avoid while Developing Your Next Mobile App>这篇文章翻译而来 智能手机的普及带动了大批移动应用的诞生,这 ...

  6. Bank5

    Account: package banking5; //账户 public class Account { protected double balance; public Account(doub ...

  7. Linux光盘yum源软件安装

    关于Linux中的软件安装,有三种方法,个人认为比较方便的就是yum安装,有网的话比较简单,暂且不提.本文主要记录在没有外网的情况下,如何以本地光盘搭建yum源来实现yum安装. 主要包括以下几步: ...

  8. MySQL 可重复读,差点就我背上了一个 P0 事故!

    小黑黑的碎碎念 哎,最近有点忙,备考复习不利,明天还要搬家,好难啊!! 本想着这周鸽了,但是想想还是不行,爬起来,更新一下,周更可不能断.偷懒一下,修改一下之前的一篇历史文章,重新发布一下. P0 事 ...

  9. Chisel3 - Chisel vs. Scala

    https://mp.weixin.qq.com/s/mTmXXBzSizgiigFYVQXKpw     介绍Chisel与Scala的不同与关联.   ​​   一. 层次高低   Chisel是 ...

  10. Java实现 LeetCode 717 1比特与2比特字符(暴力)

    717. 1比特与2比特字符 有两种特殊字符.第一种字符可以用一比特0来表示.第二种字符可以用两比特(10 或 11)来表示. 现给一个由若干比特组成的字符串.问最后一个字符是否必定为一个一比特字符. ...