素数基本(埃氏筛法/线性筛法)

本文介绍了素数检测的基本方法,包括直接枚举、优化枚举、埃氏筛法及区间筛法等,并探讨了线性筛法实现,旨在提供高效算法解决素数相关问题。

一、检查n是否为素数

  

最简单思路:所有可能的因数全部试一遍。

 

int gg(int n)
{
    for(int i=2;i<n;i++){
        if((n%i)==0)return 0;//有因数就不是素数咯
    }
    return 1;
}

 

进一步思考:没必要枚举所有的数,每一个小于n^(1/2)的因数i,一定有一个大于n^(1/2)的因数j与之对应,也就是i*j=n,所以枚举小于等于n^(1/2)的因数即可

 

int gg(int n)
{
    for(int i=2;i*i<=n;i++){
        if((n%i)==0)return 0;
    }
    return 1;
}

 

二、约数枚举

 上面已经说过,不需要枚举所有因数,枚举出某小因数以后算出对应的大因数即可。

vector<int> gg(int n)
{
    vector<int> a;
    for(int i=2;i*i<=n;i++){
        if((n%i)==0){
            a.push_back(i);
            if((n/i)!=i)a.push_back(n/i);//根号n的情况不要重复添加
        }
    }
    return a;
}

 

三、埃氏筛法

 只对一个整数操作,O(N),已经足够了,如果对许多整数进行素性检测,还有更高效的算法,比如埃氏筛法。

问题:枚举n以内所有素数

操作:先把所有整数列出来,然后把2的倍数全部剔除,然后是三的,以此类推,遍历所有素数,把倍数全部划去。

对于每个数字i,如果没被划去,他一定是素数,因为他不是任何2到i-1数字的倍数。然后就开始划它的倍数就好。

int a[maxx];
int b[maxx+1];
int gg(int n)
{
    int p=0;//记录素数个数
    for(int i=0;i<n+1;i++)b[i]=1;
    b[0]=0;
    b[1]=0;
    //准备完毕
    for(int i=2;i<=n;i++){
        if(b[i]){
            a[p++]=i;//记录素数和个数
            for(int j=2*i;j<=n;j+=i)b[j]=0;//剔除倍数
        }
    }
    return p;//返回素数个数
}

四、区间筛法

 给定整数a和b,请问区间[a,b)内有多少个素数? 

思路:之前说过,因为b以内合数的最小质因数一定不超过sqrt(b),如果有sqrt(b)以内的素数表的话,就可以把筛选法用在[a,b)上了,先分别做好[2,sqrt(b))的表和[a,b)的表,然后从[2,sqrt(b))的表中筛得素数的同时,也将其倍数从[a,b)的表中划去,最后剩下的就是区间[a,b)内的素数了。

//不gg了,这次就来个标准一点的吧
typedef long long ll;
bool is_prime[maxn];
bool is_prime_small[maxn];
void segment_sieve(ll a,ll b) 
{
     for(ll i=0;i*i<b;++i) is_prime_small[i]=true; //初始化
     for(ll i=0;i<b-a;++i) is_prime[i]=true; //初始化,注意下标变化,为了省空间
     for(ll i=2;i*i<b;++i) {
         if(is_prime_small[i]) {
             for(ll j=2*i;j*j<b;j+=i) is_prime_small[j]=false;  //筛选[2,sqrt(b));
             //(a+i-1)/i得到最接近a的i的倍数,最低是i的2倍,然后筛选
             for(ll j=max(2LL,(a+i-1)/i)*i;j<b;j+=i) is_prime[j-a]=false;
         }
     }
}

五、线性实现

筛法很多数被处理了不止1遍,比如6,在素数为2的时候处理1次,为3时候又处理一次,因此又造成了不必要处理。O(nloglogn)已经基本可以满足一般需要了。

本代码保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次,所以时间复杂度是O(n)

证明略

话不多说,上板子

#include<cstdio>
#include<cstring>
#define MAXN 100005
#define MAXL 1299710
int prime[MAXN];
int check[MAXL];
int tot = 0;
memset(check, 0, sizeof(check));
for (int i = 2; i < MAXL; ++i)
{
    if(!check[i])prime[tot++] = i;
    for (int j = 0; j < tot; ++j)//****************************************
    {
        if (i * prime[j] > MAXL)break;//*******************
        check[i*prime[j]] = 1;
        if (i % prime[j] == 0)break;//******
    }
}

素数基本就这些内容咯。。。。

<think>好的,我现在需要帮用户介绍一下素数筛法,特别是埃氏筛法线性筛法,并且要给出C++代码。首先,用户可能是在学习算法或者数论相关内容,需要了解这两种筛法的原理、步骤以及代码实现。用户可能对它们的区别和各自的优缺点感兴趣,尤其是时间复杂度和效率方面。 首先,我应该回忆埃氏筛法基本思想。埃拉托斯特尼筛法是通过不断标记素数的倍数来筛选出所有素数。它的时间复杂度是O(n log log n),对于一般的问题已经够用了,但可能会有重复标记的情况,比如数字6会被2和3都标记一次,导致效率稍低。 接下来是线性筛法,也就是欧拉筛。这种筛法的核心是每个合数只被它的最小质因数筛掉,这样每个合数只被标记一次,时间复杂度是O(n),但实现起来稍微复杂一些,需要维护一个质数列表,并且每次用当前质数去乘已经找到的质数,直到当前数能被该质数整除为止。 用户可能需要对比这两种方法的优缺点,比如埃氏筛法虽然时间复杂度稍高,但实现简单,适合小规模数据或者对代码简洁性要高的场景;而线性筛法在数据规模较大时更高效,但代码稍微复杂,需要更多的空间来存储质数列表。 接下来要考虑用户提供的C++代码示例。需要确保代码正确,并且有注释说明关键步骤。例如,埃氏筛法需要一个布尔数组isPrime,初始化为true,然后从2开始,将每个素数的倍数标记为false。而线性筛法需要维护一个vector存储素数,内层循环中,对于每个数i,用已有的素数prime[j]去标记i*prime[j],并且在i能整除prime[j]时跳出循环,避免重复标记。 还要注意代码的效率优化,比如埃氏筛法可以从i*i开始标记,因为之前的倍数已经被更小的素数标记过了。而线性筛法则必须确保每个合数只被筛一次,因此需要及时break。 用户可能的疑问包括:为什么线性筛法能保证每个合数只被筛一次?这时候需要解释,每个合数都是被其最小质因数筛掉的,比如12会被2筛掉(当i=6,prime[j]=2时),而不会在i=4,prime[j]=3的时候被筛,因为此时i%prime[j]等于0,会break,避免用更大的质数去筛。 另外,可能需要说明这两种筛法的适用场景。例如,埃氏筛法适合需要快速实现且处理数据规模不是特别大的情况,而线性筛法在处理大规模数据时更高效,尤其是在n很大的时候。 最后,用户可能需要实际运行这些代码,所以需要确保代码的正确性,没有语法错误,并且给出测试示例,比如n=30时输出的素数列表是否正确。在代码示例中,最好也包含输入的提示和输出的格式,方便用户理解和使用。</think>以下是关于**素数筛法(埃氏筛法线性筛法)**的详细解析,包含C++代码实现: --- ### 一、埃拉托斯特尼筛法(埃氏筛法) #### 核心思想 通过**标记素数的倍数**来筛选出所有素数,时间复杂度为$O(n \log \log n)$。 #### 步骤 1. 初始化一个布尔数组`isPrime[0..n]`,默认全为`true`。 2. 从最小的素数2开始,若`isPrime[i]`为`true`,则标记所有$i$的倍数为非素数。 3. 重复步骤2直到遍历完$\sqrt{n}$。 #### C++代码 ```cpp #include <iostream> #include <vector> using namespace std; void eratosthenes(int n) { vector<bool> isPrime(n + 1, true); isPrime[0] = isPrime[1] = false; // 0和1不是素数 for (int i = 2; i * i <= n; ++i) { if (isPrime[i]) { // 从i*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 << " "; } } int main() { int n = 30; cout << "埃氏筛法结果(n=30):"; eratosthenes(n); return 0; } ``` #### 输出 ``` 埃氏筛法结果(n=30):2 3 5 7 11 13 17 19 23 29 ``` --- ### 二、线性筛法(欧拉筛法) #### 核心思想 确保每个合数**仅被其最小质因数筛除**,时间复杂度$O(n)$,空间复杂度$O(n)$。 #### 步骤 1. 维护一个质数列表`primes`和一个布尔数组`isPrime[0..n]`。 2. 遍历每个数$i$,若未被标记为非素数,则将其加入质数列表。 3. 用当前质数$p$标记$i \times p$为非素数,当$i \bmod p = 0$时终止标记。 #### C++代码 ```cpp #include <iostream> #include <vector> using namespace std; void eulerSieve(int n) { vector<bool> isPrime(n + 1, true); vector<int> primes; // 存储所有素数 isPrime[0] = isPrime[1] = false; for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.push_back(i); // i是素数,加入列表 } // 用当前已知素数标记i*primes[j] for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) { isPrime[i * primes[j]] = false; if (i % primes[j] == 0) break; // 关键:保证只被最小质因数筛一次 } } // 输出结果 for (int p : primes) cout << p << " "; } int main() { int n = 30; cout << "线性筛法结果(n=30):"; eulerSieve(n); return 0; } ``` #### 输出 ``` 线性筛法结果(n=30):2 3 5 7 11 13 17 19 23 29 ``` --- ### 三、两种筛法的对比 | 特性 | 埃氏筛法 | 线性筛法 | |--------------------|---------------------------------|---------------------------------| | **时间复杂度** | $O(n \log \log n)$ | $O(n)$ | | **空间复杂度** | $O(n)$ | $O(n)$ | | **重复标记次数** | 较多(如6会被2和3各标记一次) | 无(每个合数仅被标记一次) | | **适用场景** | 小规模数据或快速实现 | 大规模数据或严格时间复杂度要 | --- ### 四、关键点解析 1. **埃氏筛法的优化**: - 标记起点从$i^2$开始,避免重复标记(如$i=5$时,直接从25开始标记,跳过10、15、20等已被标记的数)。 2. **线性筛法的核心**: - **`if (i % primes[j] == 0) break`**: 假设当前数为$i=4$,质数列表为$[2,3]$,当标记$4 \times 2=8$后,由于$4 \bmod 2=0$,停止标记,避免后续标记$4 \times 3=12$(12会被$6 \times 2=12$筛除,保证最小质因数优先)。 --- ### 五、实际应用 - **埃氏筛法**:适合一次性生成素数表(如LeetCode计数问题)。 - **线性筛法**:在需要频繁查询或处理极大范围(如$n \geq 10^7$)时更高效。 若有疑问(如代码细节或数学证明),欢迎进一步讨论!
评论 212
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兔老大RabbitMQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值