筛质数

常用的有3种算法,分别有不同的用途。

  1. 暴力枚举 O(sqrt(n)) 常用于判断单个或少量数是否质数
  2. 一般的线性筛 O(n^2) 常数挺小,常用于O(1)查找是否质数,但需要开O(n)大小的数组
  3. 快速线性筛(欧拉筛) O(n),虽然代码表面上看起来时间复杂度并不是O(n)

实现:

  • 暴力枚举

代码:

ok = 1;
if(n < 2 || n % 2 == 0) ok = 0; //偶数肯定不是
else
	for(i = 3; i <= sqrt(n); i += 2) //奇数
		if(n % i == 0)
		{
			ok = 0;
			break;
		}
cout << (ok?"YES":"NO");
  • 线性筛

代码:

bool notprime[MAXN];
for(i = 2; i <= sqrt(n); i++)
{
	j = 2; t = i<<1;
	while(t<=m) 
		notprime[t] = 1, t = i*(++j);
}
int ask;
cin >> ask;
while(ask--)
{
	cin >> t;
	cout << (notprime[t]?"NO":"YES");
}
  • 欧拉筛

(部分转自http://blog.youkuaiyun.com/dinosoft/article/details/5829550

快速线性筛法没有冗余,不会重复筛除一个数

 

int prime[MAXN], num_prime = 0;    
int isNotPrime[MAXN] = {1, 1};   
for(int i = 2; i <= N; i++)       
{
	if(!isNotPrime[i])               
	 	prime[num_prime++] = i;  
	//关键处1        
	for(int j = 0; j < num_prime && i * prime[j] <= N; j++)
	{
		isNotPrime[i * prime[j]] = 1;
		if(!(i % prime[j]))  //关键处2
			break;
	}
}

证明:

首先,先明确一个条件,任何合数都能表示成一系列素数的积。

不管 i 是否是素数,都会执行到“关键处1”,

  1. 如果 i 都是是素数的话,那简单,一个大的素数 i 乘以不大于 i 的素数,这样筛除的数跟之前的是不会重复的。筛出的数都是 N=p1*p2的形式, p1,p2之间不相等
  2. 如果 i 是合数,此时 i 可以表示成递增素数相乘 i=p1*p2*...*pn, pi都是素数(2<=i<=n),  pi<=pj  ( i<=j )

p1是最小的系数。

根据“关键处2”的定义,当p1==prime[j] 的时候,筛除就终止了,也就是说,只能筛出不大于p1的质数*i。

我们可以直观地举个例子。i=2*3*5

此时能筛除 2*i ,不能筛除 3*i

如果能筛除3*i 的话,当 i' 等于 i'=3*3*5 时,筛除2*i' 就和前面重复了。

 

需要证明的东西:

  1. 一个数会不会被重复筛除。
  2. 合数肯定会被干掉。

根据上面红字的条件,现在分析一个数会不会被重复筛除。

设这个数为 x=p1*p2*...*pn, pi都是素数(1<=i<=n)  ,  pi<=pj ( i<=j )

当 i = 2 时,就是上面①的情况,

当 i >2 时, 就是上面②的情况, 对于 i ,第一个能满足筛除 x 的数  y 必然为 y=p2*p3...*pn(p2可以与p1相等或不等),而且满足条件的 y 有且只有一个。所以不会重复删除。

证明合数肯定会被干掉? 用归纳法吧。

类比一个模型,比如说我们要找出 n 中2个不同的数的所有组合 { i , j } ,1<=i<=n, 1<=j<=n,

我们会这么写

for(i = 1; i < n; i++)
	for(j = i+1; j <= n; j++)
	{
		/////;
	}

我们取 j=i+1 便能保证组合不会重复。快速筛法大概也是这个道理,不过这里比较难理解,没那么直观。

转载于:https://www.cnblogs.com/iwtwiioi/p/3521819.html

<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]。如果您需要其他法的细节或优化版本,请随时提问!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值