筛素数的方法

本文详细介绍了三种常用的求素数方法:暴力枚举法、埃氏筛法和欧拉筛法(线性筛法)。暴力枚举法效率较低,而埃氏筛法复杂度为O(nloglogn)。欧拉筛法则通过优化避免重复删除,实现线性时间复杂度。每种方法都提供了关键代码示例,并指出了一些优化技巧,如利用6的倍数性质减少检查次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天,我们来介绍三种筛素数的方法:

1、暴力枚举法

复杂度为O(n*sqrt(n))

关键代码:

int ans[Maxn],tot;
bool isprime(int x){
    for(int i=2;i*i<=x;i++)
        if(x%i==0)
            return 0;
    return 1;
}

for(int i=2;i<=n;i++)
    if(isprime(i))
        ans[tot++]=i;
        

Tips:

经研究,除2、3外质数只能是6n+1,6n-1.因为6n+2、6n+4、6n为2的倍数,6n+3为三的倍数。

优化代码:

int ans[Maxn],tot;
bool isprime(int x){
    for(int i=2;i*i<=x;i++)
        if(x%i==0)
            return 0;
    return 1;
}

for(int i=2;i<=n;i++)
    if(i%6==1||i%6==5)
        if(isprime(i))
            ans[tot++]=i;
        

2、埃氏筛法

每次用未被删除的质数乘1、2、3……知道边界,复杂度为O(nloglogn)

关键代码:

bool prime[Maxn];
void Prime(int n){
    for(int i=0;i<=n;i++)
		prime[i]=1;
    prime[0]=prime[1]=0;
    for(int i=2;i*i<=n;i++)
        if(prime[i])
            for(int j=i*i;j<=n;j+=i)
                prime[j]=0;
} 

这里有一个小优化,如上,j从i*i出发,因为i*i以下的已被删除了

但是,一个合数可能会被删去多次,如60会被2、3、5同时删去

3、欧拉筛法(线性筛法)

优化埃氏筛法,使得每个质数只删去一次,空间复杂度提高,时间复杂度为O(n)

关键代码:

int ans[Maxn],cnt;
bool prime[Maxn];	
void Prime(int n){
	for(int i=2;i<=n;i++){
		if(!prime[i])
			ans[++cnt]=i;
		for(int j=1;j<=cnt&&i*ans[j]<=n;j++){
			prime[i*ans[j]]=1;
			if(!(i%ans[j]))
				break;
		}
	}
}

<think>首先,用户的问题是:“质数的朴素算实现或原理 请问质数 朴素算 实现原理 时间复杂度”。我需要基于系统级指令和用户可见层的规则来回答。 系统级指令: - 所有行内数学表达式用 `$...$` 格式。 - 独立公式用 `$$...$$` 格式并单独成段。 - LaTeX 语正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如 `[^1]`。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 - 参考站内引用:引用[1]到[5]提供了关于质数的信息。 从引用中提取关键信息: - 引用[1]:描述了朴素(时间复杂度 O(n log n)),代码示例使用 C++ 实现。 - 引用[2]:比较了朴素、埃氏和欧拉,指出朴素重复率高。 - 引用[3]:解释了线性(欧拉)如何改进埃氏,时间复杂度 O(n)。 - 引用[4]:总结了三种,包括朴素,并提到时间复杂度。 - 引用[5]:讨论了算时间复杂度,但主要针对其他问题;相关部分是多项式时间复杂度。 用户特别询问朴素: - 实现原理:朴素是通过遍历每个数,并标记其倍数作为合数。引用[1]中代码显示:从2开始,对于每个数i,如果没有被标记(即质数),则将其倍数全部标记为合数。 - 时间复杂度:引用[1]说 O(n log n),引用[4]也提到类似。计算:对于每个质数i,标记倍数的操作次数约为 n/i,所以总操作是 sum_{i=2}^n (n/i) ≈ n * H_n ≈ n log n,其中 H_n 是调和级数。 - 实现:引用[1]提供了 C++ 代码,我需要用中文解释它。 回答结构: 1. **介绍朴素**:简要解释什么是质数的朴素算。 2. **实现原理**:详细描述算步骤。 3. **时间复杂度分析**:解释为什么是 O(n log n)。 4. **代码示例**:提供代码(基于引用[1]),并用中文注释。 5. **生成相关问题**:在结尾添加相关问题。 数学表达式: - 行内数学:如时间复杂度 $O(n \log n)$。 - 独立公式:如果必要,但这里可能不需要复杂的公式。 引用标识: - 当引用信息时,在末尾添加 `[^1]` 等。例如,引用引用[1]的代码或描述。 生成相关问题: - 基于主题,如其他、优化、应用等。 完整回答大纲: 1. **开头**:直接回答问题。 2. **原理部分**:解释朴素的工作原理。 3. **时间复杂度**:分析并给出数学依据。 4. **代码实现**:展示代码,解释关键部分。 5. **总结**:简要总结。 6. **相关问题**:列出3-5个相关问题。 确保中文回答。 最终输出: - 使用 Markdown 格式。 - 代码块用 ``` 包裹。 - 相关问题用 "§§相关问题§§" 开头。</think>### 质数的朴素算:实现原理与时间复杂度分析 质数(又称素数选)是一种用于高效找出给定范围内所有质数的算。朴素(Naive Sieve)是最基础的一种实现,其核心思想是通过遍历和标记合数来选质数。下面我将逐步解释其实现原理、时间复杂度,并提供代码示例。内容基于可靠算知识,并结合了站内引用信息[^1][^2][^4]。 #### 1. **实现原理** 朴素的原理基于一个简单事实:所有合数都可以被分解为更小的质因数。算步骤如下: - **初始化**:创建一个布尔数组 `st`(大小至少为 $n+1$),初始假设所有数(从2开始)都是质数(即 `st[i] = false` 表示 $i$ 是质数)。 - **遍历数字**:从 $i = 2$ 开始遍历到 $n$。 - 如果 `st[i]` 为 `false`(即 $i$ 是质数),则: - 将 $i$ 加入质数列表。 - 标记 $i$ 的所有倍数(如 $i \times j$,其中 $j \geq i$)为合数(设置 `st[i * j] = true`)。 - **结束条件**:遍历完成后,所有未被标记的数即为质数。 关键点: - 算重复标记每个数的倍数,包括质数和合数本身(如标记 $4$ 时,$2$ 的倍数已被标记,但 $4$ 仍会被再次处理)。 - 这导致高重复率,因为一个合数可能被多个质因数多次标记(例如,$6$ 会被 $2$ 和 $3$ 都标记)[^2][^4]。 - 时间复杂度较高,但实现简单易懂。 #### 2. **时间复杂度分析** 朴素的时间复杂度为 $O(n \log n)$。原因如下: - **操作计数**:对于每个数 $i$($2 \leq i \leq n$),算需要标记其倍数。倍数标记的次数约为 $\frac{n}{i}$(因为 $j$ 从 $i$ 开始,步长为 $i$,直到 $j \leq n$)。 - **总操作量**:所有 $i$ 的标记操作总和为 $\sum_{i=2}^{n} \frac{n}{i}$。 - **调和级数近似**:该和式可近似为 $n \times H_n$,其中 $H_n$ 是调和级数($H_n = \sum_{k=1}^{n} \frac{1}{k} \approx \log n + \gamma$,$\gamma$ 为欧拉常数)。因此,总操作量约为 $n \log n$。 - **最终复杂度**:忽略常数项,时间复杂度为 $O(n \log n)$。 相比于更高效的(如埃氏 $O(n \log \log n)$ 或线性 $O(n)$),朴素在 $n$ 较大时性能较差,因为其重复标记导致额外开销[^2][^3][^4]。 #### 3. **代码实现(C++ 示例)** 以下代码基于引用[^1]的实现,我用中文添加了详细注释。代码找出小于等于 $n$ 的所有质数,并输出质数个数。 ```cpp #include <iostream> #include <vector> using namespace std; // 朴素实现 void naive_sieve(int n) { vector<bool> st(n+1, false); // st[i] 为 true 表示 i 是合数,false 表示质数(初始假设所有数都是质数) vector<int> primes; // 存储质数列表 int cnt = 0; // 质数计数器 // 从2开始遍历到n for (int i = 2; i <= n; i++) { if (st[i]) continue; // 如果 i 已被标记为合数,跳过 primes.push_back(i); // i 是质数,加入列表 cnt++; // 质数计数增加 // 标记 i 的所有倍数(从 i*i 开始优化,但朴素版本通常从 i 开始) for (int j = i; j <= n; j += i) { st[j] = true; // 标记 j 为合数 } } // 输出结果 cout << "小于等于 " << n << " 的质数个数: " << cnt << endl; cout << "质数列表: "; for (int prime : primes) { cout << prime << " "; } cout << endl; } int main() { int n; cout << "请输入 n: "; cin >> n; naive_sieve(n); return 0; } ``` **代码说明**: - **输入**:用户输入 $n$(例如 $n=100$)。 - **输出**:质数个数和列表(如小于等于 100 的质数有 25 个)。 - **优化点**:内层循环从 $j = i$ 开始(而非 $j = 2$),但标记所有倍数(包括 $i$ 本身),这保证了所有合数被覆盖,但增加了冗余。 - **缺点**:如引用[^2][^4]指出,合数会被多次标记(例如 $6$ 被 $2$ 和 $3$ 都标记),导致效率低下。 #### 4. **总结** 朴素通过遍历和标记倍数来选质数,实现简单但时间复杂度为 $O(n \log n)$。当 $n$ 较小时(如 $n \leq 10^5$),它仍可用;但当 $n$ 较大时,推荐使用埃氏($O(n \log \log n)$)或线性($O(n)$)以提高性能[^2][^3][^4]。如果您需要其他的细节或优化版本,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值