埃氏筛法的详解

埃氏筛法

一个判断素数的高效算法

关于埃氏筛法的百度百科解释在这里埃拉托斯特尼筛法,当然我不可能给个百度百科的解释就撤,那会被打死的。
众所周知,素数指的是除了1和它本身之外没有其它约数的数
我们假定一个数num,那么如果我们想通过编程来判断它是不是素数,我们首先通过它的定义想到暴力枚举方法,即用for循环配合取模操作实现,c++代码如下

bool flag = true; // flag作为标志来标识这个数是不是素数,默认设置为true
for(int i=2;i<num/2;i++)
{
    if(num % i == 0)
    {
        flag = false; // 如果能被整除了,那就不是素数,退出循环
        break;
    }
}
if(flag)
{
    cout<<"数:"<<num<<"是素数"<<endl;
}

当然这种方法就显得很麻烦,虽然直观易懂,但是时间复杂度达到了n^2级。
当然如果稍微改进一点的话就会知道把我上面的代码循环中的i < num/2改成i*i < num,时间可以缩短。但是仍不够明显。


快乐的分割线


下面我们开始埃氏筛法的学习

看过上图百度百科的同学应该明白,埃氏筛法是范围判断素数的方法,即,你给出一个数n,我用埃氏筛法能判断从1-n的所有素数出来。

原理

素数的倍数肯定不是素数

给定一个数n,我们开一个大小为n+1的bool型数组,即 bool nums[n+1] ,并全部初始化为false, 在这里声明一点,这个数组的 nums[0] 与 nums[1] 我们并不会使用到。
建立了这样一个数组之后,我们就可以直接开干啦,我们首先要找到最小的素数,即2,从而开始循环开炮
OK非常棒,现在我们已经能找出素数中的最小者了,任务完成一半!
接下来,我们应用我们的原理——素数的倍数肯定不是素数
这里有张表

是否是素数
2
3
4不是(通过素数2的倍数判断)
5
6不是(通过素数2,3的倍数判断)
7
8不是(通过素数2的倍数判断)
9不是(通过素数3的倍数判断)
加入我们要求10以内的素数,开一个nums[11],然后定位找到最小的素数->2,以2为基点,开炮,把2的倍数全部打死,当然这个也是有范围的,2的倍数我们只要判断到小于n就可以了,即小于10。然后2的倍数打死完了,再找幸存的最小素数3,以3为基点,开炮,在3的炮火攻击下,上次侥幸存活的9被揪出来打死了。***我们会一直判断,直到我们的基点达到一个临界点——小于等于n的开根号,这样我们就把所有潜藏的合数敌人全部消灭了,不信你瞅瞅? ***

原理如此,接下来附上这一部分的代码,看完代码之后我会有一个小小的问题遗留给大家思考

int main() // 这里只给出实现代码了,上面的头文件什么的都没写了
{
    int num;
    cin >> num;
    bool* nums = new bool[num];   // 这里我们如果需要用变量创建数组的话必须这样创建喔!
    for (int i = 0; i < num; i++)
    {
        nums[i] = true;
    }
    int sn = sqrt(num);
    for (int i = 2; i <= sn; i++) /* 从最小的素数2开始开炮*/
    {
        if (nums[i])
        {
            for (int j = i * i; j < num; j+=i)  // 此处需要注意,我们开炮的时候,第一颗炮弹直接打到i的平方上,这是避免了重复判断,j每次要增长i
            {
                nums[j] = false;   // 跟我一起,开炮!!
            }
        }
    }
    for (int i = 2; i < num; i++)
    {
        if (nums[i])
        {
            cout << i << "\t";  // 打印出选出来的素数你看看,这里我喜欢用制表符,个人习惯,看着舒服
        }
    }

    delete []nums;  // 最后可别忘了释放指针内存哦,并且将指针置空,养成好习惯,不要野指针!
    nums = nullptr;
    return 0;
}

好了,代码部分结束,最后我还遗留一个问题,你们有没有发现,我们这里重复判断了?比如我已经判断12是偶数了,已经杀死了,但是我以3为基点开炮的时候,还是会再打它一次,炸尸,这会不会浪费我们的炮弹资源(时间)呢?该如何改进这个问题呢?思考一下?
关注我的公众号【阳仔不想当码农】

谢谢你来看我!

<think>嗯,用户想了解埃拉托色尼筛法的概念、实现和原理。首先,我需要回忆一下这个算法的基本思想。埃氏筛法是一种用来找出所有小于等于某个数的素数的算法。它的核心思想应该是通过排除法,逐步筛掉合数,剩下的就是素数了。 记得这个算法是从小到大遍历每个数,如果遇到一个素数,比如2,就把它的倍数全部标记为合数。然后继续下一个数,如果是未被标记的素数,继续筛它的倍数。这样重复下去,直到遍历完所有数。不过具体步骤可能需要注意细节,比如从i的平方开始标记,这样可以避免重复标记。比如当处理到i的时候,i的倍数可能已经被更小的素数筛过了,所以直接从i*i开始能提高效率。 然后需要确认用户提供的引用内容。引用[2]提到从j = i*i开始标记倍数,而不是j = i+i,这样可以避免重复标记,提高效率。引用[4]中的代码示例也用了类似的方式,循环条件是p*p <= N,这可能是因为当p的平方超过N时,后面的数已经被处理过了。所以这部分需要详细解释,为什么从i的平方开始,以及为什么循环到sqrt(n)就足够。 接下来是算法步骤。首先,初始化一个布尔数组,用来标记是否为素数。然后从2开始遍历到sqrt(n),如果当前数是素数,就标记它的倍数为合数。最后剩下的未被标记的就是素数。需要注意的是,遍历到sqrt(n)即可,因为任何大于sqrt(n)的合数必然有一个因数小于等于sqrt(n),所以已经被筛过了。 关于时间复杂度,埃氏筛法的时间复杂度是O(n log log n),这比试除法要高效得多。引用[3]也提到该算法的高效性,所以需要解释为什么这个时间复杂度是这样计算的,可能涉及到调和级数或者素数分布的知识。 在代码实现部分,用户提供了不同语言的例子,比如Objective-C、Python和C。可能需要用Python来展示一个例子,因为用户示例中的快速排序用了Python,可能更熟悉。代码中需要注意初始化数组,循环遍历,标记倍数等步骤。例如,创建一个大小为n+1的数组,初始都标记为True,然后0和1设为False,接着从2开始到sqrt(n),如果是素数,就标记从i*i开始,步长为i的数为False。 优化方面,除了从i*i开始,还可以考虑只处理奇数,因为除了2以外的偶数都不是素数。这样可以减少一半的工作量。比如在初始化时,先标记所有偶数,然后从3开始每次加2。不过这可能稍微复杂一些,需要根据具体情况调整。 应用场景的话,埃氏筛法适用于需要快速生成素数列表的情况,比如密码学中的素数生成,或者解决数学问题需要大量素数时。例如RSA算法可能需要生成大素数,虽然埃氏筛法在极大范围内可能不够高效,但小范围内还是很实用的。 用户的问题可能还涉及到算法正确性的证明,为什么这样做能找出所有素数。需要解释每个合数至少有一个因数不大于其平方根,因此在筛到sqrt(n)时,所有合数已经被标记过了。如果有更大的因数,对应的较小因数已经被处理过了,所以无需继续。 另外,引用[2]和[4]都提到了从i*i开始标记的重要性,这样可以避免重复处理。例如,当处理i=2时,会标记4,6,8等;当处理i=3时,从9开始标记,因为6已经被i=2处理过了。这样就减少了重复操作,提高效率。 最后,生成相关问题时,需要考虑用户可能进一步想了解的内容,比如时间复杂度分析、优化方法、应用场景,或者与其他素数筛选算法(如欧拉筛)的比较。这样能帮助用户深入理解埃氏筛法的优缺点。</think>### 埃拉托色尼筛法详解 #### 一、算法原理 埃拉托色尼筛法(Sieve of Eratosthenes)是一种高效筛选素数的算法,核心思想是通过**逐步排除合数**,最终保留所有素数。具体步骤: 1. **初始化布尔数组**:创建一个长度为$n+1$的数组,初始标记所有数为素数(True),将0和1标记为非素数(False)。 2. **遍历筛选**:从$p=2$开始,若$p$是素数,则将所有$p$的倍数标记为合数(False)。 3. **优化起点**:标记合数时从$j = p^2$开始,而非$2p$,因为更小的倍数已被更小的素数处理过[^2]。 4. **终止条件**:遍历到$p \leq \sqrt{n}$时即可终止,因为所有合数的因子在此范围内已被处理[^4]。 #### 二、时间复杂度 - **理论复杂度**:$O(n \log \log n)$,远优于试除法的$O(n \sqrt{n})$[^3]。 - **效率关键**:通过批量标记合数减少重复操作。 #### 三、Python实现代码 ```python def sieve_of_eratosthenes(n): is_prime = [True] * (n + 1) is_prime[0], is_prime[1] = False, False # 0和1非素数 for p in range(2, int(n**0.5) + 1): if is_prime[p]: for j in range(p*p, n+1, p): # 从p的平方开始标记 is_prime[j] = False primes = [p for p, prime in enumerate(is_prime) if prime] return primes ``` #### 四、关键优化点 1. **起点选择**:从$p^2$开始标记倍数,避免重复处理$2p, 3p, \dots, (p-1)p$。 2. **范围限制**:仅需遍历到$\sqrt{n}$,因为若$n$是合数,必有一个因子$\leq \sqrt{n}$。 #### 五、应用场景 - 快速生成素数表(如密码学、哈希函数)。 - 解决数论问题(如哥德巴赫猜想验证)。 - 算法竞赛中高效处理素数相关题目[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值