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

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

求小于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. Spring 框架的 AOP 简介

    Spring 框架的 AOP Spring 框架的一个关键组件是面向方面的编程(AOP)(也称为面向切面编程)框架. 面向方面的编程需要把程序逻辑分解成不同的部分称为所谓的关注点. 跨一个应用程序的多 ...

  2. CSS实现漂亮的小水球效果

    先看效果图: 代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> ...

  3. MySQL的日期类型

    -- MySQL 中有多种数据类型可以用于日期和时间的表示,不同的版本可能有所差异,表 3-2 中-- 列出了 MySQL 5.0 中所支持的日期和时间类型.-- 表 3-2 MySQL 中的日期和时 ...

  4. [PHP工具推荐]0001.分析和解析代码的7大工具

    引言:PHP已成为时下最热门的编程语言之一,然而却有许多PHP程序员苦恼找不到合适的工具来帮助自己分析和解析PHP代码.今天SD就为大家介绍几个非常不错的工具,来帮助程序员们提高自己的工作效率,一起来 ...

  5. 一文让你快速上手 Mockito 单元测试框架

    前言 在计算机编程中,单元测试是一种软件测试方法,通过该方法可以测试源代码的各个单元功能是否适合使用.为代码编写单元测试有很多好处,包括可以及早的发现代码错误,促进更改,简化集成,方便代码重构以及许多 ...

  6. css 3 背景图片为渐变色(渐变色背景图片) 学习笔记

    6年不研究CSS发现很多现功能都没有用过,例如渐变色,弹性盒子等,年前做过一个简单的管理系统,由于本人美工不好,设计不出好看的背景图片,偶然百度到背景图片可以使用渐变色(感觉发现了新大陆).以后的项目 ...

  7. python pexpect总结

    基本使用流程 pexpect 的使用说来说去,就是围绕3个关键命令做操作: 首先用 spawn 来执行一个程序 然后用 expect 来等待指定的关键字,这个关键字是被执行的程序打印到标准输出上面的 ...

  8. 个人工具,编辑器visual studio code

    个人收集的使用方法:简化版 主要基于基础web前端开发,visual studio code教程——基础使用.扩展插件安装使用 下载地址: https://visualstudio.microsoft ...

  9. jQuery-ajax请求使用

    1.jQuery中的ajax $.ajax({ url:'地址', type:'get/post', data:{}, dataType:'json/jsonp', success:function( ...

  10. 最好用的FTP工具,最好用的FTP工具推荐!

    IIS7服务器管理工具是一个良好的FTP的客户端,可以进行FTP的操作!同时,还可以作为VNC的客户端进行VNC的相关操作!它还能连接Windows和Linux的服务器和PC,并对他们的连接状态进行实 ...