【算法】质数的判定/质数筛(埃氏筛/线性筛)

目录

【质数和合数】

试除法判断一个数是否是质数

【埃⽒筛法】

【线性筛法】


【质数和合数】

• ⼀个⼤于 1 的⾃然数,除了 1 和它⾃⾝外,不能被其他⾃然数整除的数叫做质数;否则称为合数。其中,质数⼜称素数。规定 1 既不是质数也不是合数。

试除法判断一个数是否是质数

• 对于⼀个数 x ,根据定义,可以从 [2, x − 1] 依次拿出一个数,判断 x 能否被该数整除。
但是,没有必要每⼀个都去判断。因为 a 如果是 x 的约数,那么 x/a 也是 x 的约数。因此,我们仅需判断较⼩的 a 是否是 x 的约数,没有必要再去看看 x/a 。那么,仅需枚举到根号 x 即可。

bool isprime(int x)
{
    if(x <= 1) return false; // ⼩于等于 1 的数不考虑
    // 试除法判断是否是质数 - 只需枚举到 sqrt(x)

    // for(int i = 2; i*i <= x; i++) // i*i 可能溢出
    for(int i = 2; i <= x / i; i++) // 防溢出的写法
    {
        if(x % i == 0) return false;
    }
    return true;
}

上面解决如何判断⼀个数是否是质数,如果此时想知道 [ 1 , n ] 中有多少个素数呢?或者是 [ 1 , n ]中第 k 个素数是多少?
• ⼀个⾃然的想法就是从 2 开始,依次向后对每⼀个⾃然数进⾏⼀次上面所示的质数检验。
但是这种解法相对暴⼒,我们这⾥介绍两种⽅法,能够快速地将 [1, n] 中的素数全部记录下来。

【埃⽒筛法】

算法思想:
• 对于任意⼀个⼤于 1 的正整数 ,那么它的 k(k > 1) 倍就是合数。
因此,如果我们从⼩到⼤考虑每个数,然后同时把当前这个数的所有倍数记为合数,没有被标记的数就是素数。
⼩优化:
• 找到⼀个质数 x 之后,可以从该数的 x 倍向后筛,因为⼩于 x 的倍数⼀定被之前筛过了

bool st[N]; // true :合数,false :质数
int p[N]; // 记录质数
int cnt; // 统计质数个数
// 埃⽒筛
void get_prime()
{
    for(LL i = 2; i <= n; i++)
    {
        if(!st[i]) // 没有被标记,说明是质数
        {
            p[++cnt] = i; // 记录这个质数
            // 从 i*i 开始,因为⼩于 i 的倍数已经被筛了
            for(LL j = i * i; j <= n; j += i) // 筛掉这个质数的倍数
            {
                st[j] = true;
            }
        }
    }    
}

埃⽒筛的时间复杂度为: O(nlog(log n))

【线性筛法】

线性筛法,⼜称欧拉筛法。算法思想:
• 在埃⽒筛法中,它会将⼀个合数重复多次标记。如果能让每个合数都只被标记⼀次,那么时间复杂度就可以降到 O(n*2) 了(每个数最多被遍历一次和筛掉一次)。
我们的做法是,让每⼀个合数被它的最⼩质因数筛掉。如何做到呢?从前往后遍历每个数 i

1、如果当前数没有被标记,说明它是质数,把它记录在质数表 p 中;

2、用 i 的质数倍去筛,这些质数来自记录的质数表

3、如果 i 是当前质数的倍数时,停止筛,遍历到下一个数

bool st[N];
int p[N], cnt;// 欧拉筛
void get_prime()
{
	for (long long i = 2; i <= n; i++) // 把 i 定义成 long long 防止溢出
	{
		if (!st[i]) p[++cnt] = i; // 如果没标记过,就是质数
		
		// 用 i 的质数倍去筛
		for (int j = 1; i * p[j] <= n; j++)
		{
			st[i * p[j]] = true;
			if (i % p[j] == 0) break; //如果 i 是质数的倍数时,停止筛
			/*
			这个判定条件能让每⼀个合数被⾃⼰的最⼩质因数筛掉。
			1. 如果 i 是合数,枚举到最⼩质因数的时候跳出循环
			2. 如果 i 是质数,枚举到⾃⾝时跳出循环
			注意,在筛的过程中,我们还能知道 p[j] 是 i 的最⼩质因数
			*/
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值