埃氏筛法与欧拉筛(线性筛)(快速筛选n以内素数的个数)


前言

埃氏筛法与欧拉筛(线性筛)是快速找出n以内素数的算法,其中埃氏筛法的时间复杂度是O(nlogn),而欧拉筛(线性筛)的时间复杂度接近O(n)

相关题目:https://leetcode-cn.com/problems/count-primes/


一、埃氏筛法

  1. 所谓筛法,就是在1-n的序列中,筛掉不符合的数,最终留下符合条件的数。
  2. 利用一条定律:合数都是素数的倍数。
  3. 从小到大,利用已有的素数num,将其的k倍,即k*num筛除掉。

1、示例

初始化一个序列,其中红色标记的代表素数:

-2345678910
11121314151617181920

从素数2出发,将2的倍数(4,6,8,10,12,14,16,18,20)删除掉:

-23-5-7-9-
11-13-15-17-19-

然后将序列中升序中首个未标记的数字3标记为素数

-23-5-7-9-
11-13-15-17-19-

从素数3出发,将3的倍数(6,9,12,15,18)删除掉:

-23-5-7---
11-13---17-19-

然后将序列中升序中首个未标记的数字5标记为素数

-23-5-7---
11-13---17-19-

从素数5出发,将3的倍数(10,15,20)删除掉:

-23-5-7---
11-13---17-19-

以此类推,直到最终序列中所有数字被标记:

-23-5-7---
11-13--- 17-19-

2、代码实现

int countPrimes(int n) {
    vector<bool>iscut(n+1);             //筛选标记,true-被筛掉,false-未筛除
    vector<int>prime;                   //存储素数的容器
    for (int i = 2; i <= n; i++)
    {   
        if (!iscut[i]) {                //i=true,即i未被筛掉,加入素数
            prime.push_back(i);
            for (int k = 2; k < n; k++)  //把i的倍数筛掉
            {
                if (k * i > n)       //防止越界
                    break;
                iscut[k * i] = true;
            }
        }
    }
    return prime.size();             //返回素数个数
}

3、一些改进的点

1.对于把i的倍数筛掉这里,倍数k无需从2开始,因为当k<i时,i*k已经被筛掉过一遍了。例:i=7,k=3,num=7*3=21,在i=3,k=7,num=3*7=21时就筛选过了。所以k应当从k=i开始。
2.当i*i>SIZE时,就可以结束判断,将序列中剩余未被筛除掉的数加入到素数序列中。

4、改进后代码

int countPrimes(int n) {
    vector<bool>iscut(n+1);             //筛选标记,true-被筛掉,false-未筛除
    vector<int>prime;                   //存储素数的容器
    for (int i = 2; i < n+1; i++)
    {   
        if (!iscut[i]) {                //iscut[i]=false,即i未被筛掉,加入素数
            prime.push_back(i);
            for (int k = i; k < n; k++){//把i的倍数筛掉
                if (k * i > n){         //防止越界
                    if (k == i){        //是i的平方越界,直接把序列中剩余数加入到素数
                        for (int j = i+1; j <= n; j++){
                            if(!iscut[j])
                                prime.push_back(j);
                        }
                        i = n + 1;      //添加素数完毕后将最外层循环跳出;
                    }
                    break;
                }
                iscut[k * i] = true;
            }
        }
    }
    return prime.size();             //返回素数个数
}

改了之后测试发现性能几乎没有区别。。。。

5、缺点

会有一些数被重复地筛除掉,造成了不必要的计算量。例如数字16,被2*84*4筛除。

二、欧拉筛(线性筛)

欧拉筛相对埃氏筛而言就是减少了重复的筛除,从而降低复杂度,达到O(n)

1、原理

  1. 欧拉筛与埃氏筛使用的原理有所区别。欧拉筛是合数=最小质因数*合数(质数)。以上组合唯一。
  2. 欧拉筛中不是从素数序列中出发,而是直接从数字序列依次遍历。对序列中的每一个数字i,将i依次与素数序列成员相乘得到num=i*prime[j],然后将num筛除掉,当num超出统计范围或者i%prime[j]==0(原因后面会讲)时退出循环。

2、代码实现

int countPrimes(int n) {
    vector<bool>iscut(n + 1);             //筛选标记,true-被筛掉,false-未筛除
    vector<int>prime;                     //存储素数的容器
    for (int i = 2; i <= n; i++)
    {
        if (!iscut[i])                    //iscut[i]=false,即i未被筛掉,加入素数
            prime.push_back(i);
        for (int j = 0; j < prime.size(); j++)
        {
            if (i * prime[j] > n)         //防越界
                break;
            iscut[i * prime[j]] = true;   //把i的质数倍筛除
            if (i % prime[j] == 0)        //*判断最小质因数,防止重复筛除
                break;
        }
    }
    return prime.size();             //返回素数个数

注解:整个欧拉筛中的关键判断就是i % prime[j] == 0,当为true时,i可以被prime[j]整除,有k=i/prime[j]。如果此时未跳出循环,则下一个被筛除的数为num=i*prime[j+1],而num又可以写为num=prime[j] * k * prime[j+1],即当i=k*prime[j+1]时,会再对num进行筛除。故跳出循环可以避免重复的筛除操作。

例:当i=10时,素数序列中有素数2,3,5,7
①不判断最小质因数时,被筛除的数有20,30,50,70。而30会在i=15时再被筛除一次,因为30=15*2
②判断最小质因数时,被筛除的数有20,因为筛除完20后就因为10%2==0跳出循环,而30,50,70等就留到后面的数进行筛除,这样就有效避免了重复筛选除的操作。


总结

总的来说,两种筛法使用的原理有所不同,埃氏筛法更为直观,欧拉筛则需要理解一下。两者的性能虽然有所区别,但实际上区别并不大。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值