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

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

求小于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. Gym101635C Macarons

    题目链接:http://codeforces.com/gym/101635/attachments 题目大意: 给出一个 \(N \times M\) 的网格图,请你用 \(1 \times 1\) ...

  2. Understanding REST and RESTful APIs

    Understanding REST and RESTful APIs If you've spent any amount of time with modern web development, ...

  3. Keyboard Shortcuts Reference

    Sublime Text 3快捷键 Ctrl + Shift + P 打开命令面板 Ctrl + P 搜索项目中的文件 Ctrl + G 跳到第几行 Ctrl + W 关闭当前打开文件 Ctrl + ...

  4. C#中操作JSON

    引入支持JSON操作的库,比如LitJSON: 引入之后就可以解析JSON了. 写一个JSON文本 读取这个JSON文本: 解析JSON数据最佳实践--使用泛型,将泛型类型指定成自己定义的类型,直接获 ...

  5. PowerDesigner使用教程(一)

    一.PowerDesigner简介 PowerDesigner是一款功能非常强大的建模工具软件,足以与Rose比肩,同样是当今最著名的建模软件之一.Rose是专攻UML对象模型的建模工具,之后才向数据 ...

  6. word修改页眉使本页的页眉与别的页不一样

    关键回答:双击要修改的页的页眉,word顶端的工具栏“页面设置”当中的“链接到前一个”选项不要选中,即可修改本页页眉使之与别的页不一样. 详见:https://iask.sina.com.cn/b/2 ...

  7. maven中scope

    scope maven中scope的默认值是compilescope的分类1)compile 默认是compile.compile表示被依赖项目需要参与当前项目的编译,包括后续的测试,运行周期也参与其 ...

  8. 啪啪,打脸了!领导说:try-catch必须放在循环体外!

    哈喽,亲爱的小伙伴们,技术学磊哥,进步没得说!欢迎来到新一期的性能解读系列,我是磊哥. 今天给大家带来的是关于 try-catch 应该放在循环体外,还是放在循环体内的文章,我们将从性能和业务场景分析 ...

  9. 关于js 原生原生链

    可以这么理解 (1).所有的引用类型都有一个 _proto_ (隐式原型)属性,属性值是一个普通的对象 (2).所有的函数都有一个prototype(显示原型)属性,属性值是一个普通的对象 (3).所 ...

  10. webstorm 单词快捷翻译设置

    1.打开webstorm中的设置,选择plugins,搜索 translations 安装,安装完成重启webstorm 2.设置快捷键翻译,打开webstorm设置,选择keymap,搜索trans ...