欢迎光临
我们一直在努力

GESP C++考试五级语法知识(二、埃氏筛和线性筛)

🌟《素数王国的两种超级筛子》

故事前言:

1、在数字王国里,国王需要一份 素数名单。

(1)比如:

1 ~ 30 之间的所有素数


(2)答案应该是:

2 3 5 7 11 13 17 19 23 29


(3)问题来了:

如果数字很大,比如:

1 ~ 1,000,000


(4)一个一个判断素数就会 非常慢。


2、于是王国发明了两种神器:

1️⃣ 埃氏筛(Eratosthenes Sieve)
2️⃣ 线性筛(Euler Sieve)

今天我们就来学习这两个神器。


第一部分:埃氏筛法(筛沙子)

🌾故事:筛沙子找金子

小C来到一条河边。

河里有很多 沙子(合数) 和 金子(素数)。

工人拿来一个 大筛子:

每发现一个素数,就把它的倍数全部筛掉!

这样剩下的就都是素数。


一、筛法思想

1、假设我们要找

1 ~ 30 的素数


2、先把所有数写出来:

2 3 4 5 6 7 8 9 10 11 … 30


3、我们准备一个数组:

isPrime[i]


4、表示:

i 是否是素数


5、开始全部设为:

true


二、开始筛

1、第一步:2 是素数

(1)因为 2 没被划掉。

于是:

把 2 的倍数全部划掉

4 6 8 10 12 14 16 18 20 22 24 26 28 30


(2)剩下:

2 3 5 7 9 11 13 15 17 19 21 23 25 27 29


2、第二步:3 是素数

因为 3 没被划掉。

划掉:

6 9 12 15 18 21 24 27 30


3、第三步:5 是素数

划掉:

10 15 20 25 30


最后剩下:

2 3 5 7 11 13 17 19 23 29

这些就是素数。


三、为什么只筛到 √n?

(1)比如

n = 100


(2)如果一个数是合数:

a × b = n


(3)那么一定有:

一个 ≤ √n
一个 ≥ √n


(4)所以只要筛到:

i * i <= n

就可以了。


四、埃氏筛 C++模板

#include <iostream>
using namespace std;

const int N = 1000000;

bool isPrime[N];

int main()
{
int n;
cin >> n;

for(int i=2;i<=n;i++)
isPrime[i]=true;

for(int i=2;i*i<=n;i++)
{
if(isPrime[i])
{
for(int j=i*i;j<=n;j+=i)
isPrime[j]=false;
}
}

for(int i=2;i<=n;i++)
if(isPrime[i])
cout<<i<<" ";
}


五、为什么从 i*i 开始?

(1)很多同学会问:

为什么不是:

2*i


(2)例如:

i = 5


(3)5 的倍数:

10 15 20 25


(4)向前看一下:

10 已经被 2 删过
15 已经被 3 删过
20 已经被 2 删过


(5)所以:

从 25 开始可以节省时间

也就是:

i * i


六、埃氏筛时间复杂度

大约是:

O(n log log n)

已经非常快了。


第二部分:线性筛

我们见到了一个更厉害的科学家,他叫欧拉。

他说:

我有一种方法,可以让每个合数只被删一次!

这就是:

线性筛(Euler筛)


一、线性筛思想

核心思想:

每个合数 = 最小质因数 × 另一个数

只用 最小质因数 来筛掉它。

这样就不会重复删除。


二、例子(还是1~30)

我们一边走一边记录 素数表


1、i = 2

2 是素数

加入素数表:

prime = {2}

筛:

2×2 = 4


2、i = 3

3 是素数

prime = {2,3}

筛:

3×2 = 6
3×3 = 9


3、i = 4

4 已经被删

不是素数

但继续筛:

4×2 = 8


4、i = 5

5 是素数

prime = {2,3,5}

筛:

5×2 = 10
5×3 = 15
5×5 = 25


5、就这样一直继续。

每个合数只被 最小质因数筛一次。


三、线性筛 C++模板

#include <iostream>
using namespace std;

const int N = 1000000;

int prime[N];
bool vis[N];

int main()
{
int n;
cin >> n;

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]] = true;

if(i%prime[j]==0)
break;
}
}

for(int i=0;i<cnt;i++)
cout<<prime[i]<<" ";
}


四、为什么要 break?

1、关键一句:

if(i % prime[j] == 0) break;


2、意思是:

如果:

prime[j]

已经是 i 的最小质因数


3、那:

i × 更大的质数

就不是最小质因数分解了。

所以必须停止。


4、这样保证:

每个合数只被筛一次


五、时间复杂度

线性筛:

O(n)

是真正的 线性时间


六、两种筛法对比

方法原理复杂度
埃氏筛 删除倍数 O(n log log n)
线性筛 最小质因数 O(n)

七、理解区别

1、埃氏筛

(1)特点:

一个合数会被删除很多次


(2)例如

30


(3)会被:

2删
3删
5删


2、线性筛

(1)特点:

每个合数只删除一次


(2)例如:

30 = 2 × 15

只由 2 删除。


八、什么时候用哪种筛法?

1、小数据

n ≤ 10^6

用:

埃氏筛

简单好写。


2、大数据

n ≤ 10^7 或更大

用:

线性筛

更快。


九、最重要的理解总结

(1)埃氏筛

思想:

发现素数 → 删除倍数


(2)线性筛

思想:

用最小质因数删除合数


十、一句话记忆

(1)埃氏筛:

素数 × 所有倍数


(2)线性筛:

每个合数只筛一次


赞(0)
未经允许不得转载:171主机测评 » GESP C++考试五级语法知识(二、埃氏筛和线性筛)
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址