蓝桥杯java组之埃氏筛与欧拉筛

埃氏筛

埃氏筛的原理:一个素数的倍数一定是合数
应用这个原理,我们可以逐步筛去0-n区间内的合数
代码实现如下

import java.util.*;
public class Text_1 {
	public static void main (String[] args) {
		Scanner s = new Scanner(System.in);
		int n = s.nextInt();
		int[] is_prime = new int[n+1];             //标记是否为素数,0代表是,1代表不是
		int[] prime = new int[n+1];                //存放素数
		is_prime[0]=1;                            //0与1都不是素数
		is_prime[1]=1;
		int count=0;                             //记录素数的个数           
		for(int i=2;i<=n;i++) {
			if(is_prime[i]==0) {                 //从2开始,如果是素数,将其存放进prime数组里
				prime[count]=i;       
				count++;                         //素数数量加一
				for(int j=2;i*j<n;j++) {         //将0-n中素数的倍数全部筛选出,标记为合数
				is_prime[i*j]=1;
			    }                        
			}
		}
		for(int i=0;i<count;i++) {             //打印出0-n的所有素数
			System.out.println(prime[i]);
		}
	}
}

示例输入

100

示例输出

2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97

从上述代码也可以看出,筛选的过程存在重复,这在很大程度上影响了算法的效率,比如4和6会同时被2和3筛选

欧拉筛

欧拉筛是在埃氏筛上的改进,能有效避免重复筛选的情况,时间复杂度O(n)

欧拉筛的核心思想就是确保每个合数只被最小质因数筛掉。或者说是被合数的最大因子筛掉。

比如说 1 ,2,3,4,5,6,7,8,9,10,11, 12
当 i=4时: primes = {2, 3}
此时i%2=0, 如果不结束内层循环的话,12会被3∗4筛掉, 当i=6时,12又会被2∗6筛掉。

代码实现

import java.util.*;
public class Test_2 {
	public static void main (String[] args) {
		Scanner s = new Scanner(System.in);
		int n = s.nextInt();
		int[] is_prime = new int[n+1];
		int[] prime = new int[n+1];
		is_prime[0]=1;
		is_prime[1]=1;
		int count=0;
		for(int i=2;i<=n;i++) {
			if(is_prime[i]==0) {      
				prime[count]=i;
				count++;
			}
			for(int j=0;j<count&&i*prime[j]<=n;j++) {
				is_prime[i*prime[j]]=1;
				if(i%prime[j]==0)
					break;
			}
		}
		for(int i=0;i<count;i++) {
			System.out.println(prime[i]);
		}
	}
}
<think>好的,我现在需要帮助用户用C++实现埃氏筛欧拉。首先,我得回忆这两种算法的原理和区别,确保自己理解正确,然后考虑如何用代码实现,同时注意用户的可能疑问点。 埃氏筛,全称埃拉托斯特尼法,基本思想是从小到大遍历每个数,如果遇到素数,就标记它的所有倍数为非素数。这样剩下的未被标记的就是素数。需要注意的是,埃氏筛可能会有重复标记的情况,比如数字6会被2和3都标记一次。所以时间复杂度是O(n log log n),效率还不错,但可能不是最优的。 欧拉,也叫线性,它的改进在于每个合数只被其最小的质因数掉,避免了重复标记。这样时间复杂度可以降到O(n),但实现起来稍微复杂一点,需要维护一个质数列表,并且在内循环中及时退出。 接下来,我需要考虑如何用C++实现这两个算法。首先,埃氏筛的实现步骤应该是: 1. 创建一个布尔数isPrime,大小为n+1,初始化为true。 2. 将0和1标记为false,因为它们不是素数。 3. 从2开始遍历到sqrt(n),如果当前数i是素数,则将其所有倍数标记为false。 4. 最后收集所有isPrime[i]为true的i。 注意这里外层循环只需要到sqrt(n),因为当i超过sqrt(n)时,其平方已经大于n,无需再处理。不过,可能有些实现会直接遍历到n,这样虽然效率稍低,但代码更简单。这里可能需要选择更高效的方式,或者根据用户需求调整。 然后是欧拉的实现: 1. 同样创建布尔数isPrime,初始化为true,并准备一个存储素数的vector。 2. 从2遍历到n,如果当前数i是素数,则加入数。 3. 对于每一个i(无论是否为素数),用已知的素数primes[j]去标记i的倍数。当i能被primes[j]整除时,停止内循环,避免重复标记。 关键在于内循环的终止条件,即当i % primes[j] == 0时break,这样确保每个合数只被一次。例如,当i=4时,primes里有2和3。用2去掉4*2=8,然后4%2==0,break,不会处理3,这样当i=6的时候,用2掉12,就不会用3去4*3=12,因为此时i=4已经被处理过了。这样可以保证每个合数仅被其最小质因数除。 然后,我需要编写代码示例。注意代码的正确性,比如数的大小是否足够,循环的边界条件是否正确。例如,在埃氏筛中,内层循环可以从i*i开始,每次增加i,这样可以减少重复标记的次数。比如当i=2时,标记4,6,8...;当i=3时,标记9,12...,而6已经被i=2处理过了,这样可以减少一些步骤,提高效率。 对于欧拉,需要确保primes数在过程中正确维护,且内循环的终止条件正确。例如,当i是合数时,比如4,此时primes数中有2,3,当j=0时,primes[j]=2,4*2=8被标记,然后4%2==0,break,所以不会处理primes[j]=3的情况。这样,8被标记为合数,且由最小的质因数2除。 接下来,测试代码是否正确。例如,当n=30时,埃氏筛欧拉都应该输出正确的素数列表:2,3,5,7,11,13,17,19,23,29。需要确保代码中没有数越界等问题,比如埃氏筛中i*i可能会溢出,但n在合理范围内的话应该没问题。 另外,考虑到用户可能需要不同大小的n,代码中的函数应该接受一个整数参数n,并返回或输出对应的素数列表。 最后,需要比较两种算法的优缺点,帮助用户理解何时选择哪一种。埃氏筛代码简单,适合小规模数据或对时间要求不高的场景;欧拉效率更高,适合处理大规模数据,但代码稍微复杂一些。 可能用户会问为什么欧拉能保证线性时间复杂度,这时候需要详细解释其原理,即每个合数仅被标记一次。而埃氏筛因为重复标记,所以时间复杂度更高。 在代码实现中,还要注意数的初始化,比如在C++中,布尔数默认值可能不是true,需要显式初始化。例如,使用vector<bool> isPrime(n+1, true); 来确保初始状态正确。 此外,在欧拉中,内层循环的条件是j < primes.size() && i * primes[j] <= n,这样可以避免越界和超过n的情况。 总结来说,我需要分步骤编写两个法的代码,确保语法正确,并添加注释解释关键步骤,最后比较两者的差异和适用场景,以帮助用户理解和应用。</think>以下是使用C++实现埃拉托斯特尼埃氏筛)和欧拉(线性)的详细步骤及代码: --- ### 一、埃氏筛法(Sieve of Eratosthenes) #### 算法原理 通过标记素数的倍数来选素数。时间复杂度 $O(n \log \log n)$,空间复杂度 $O(n)$。 #### 代码实现 ```cpp #include <vector> #include <cmath> std::vector<int> eratosthenes(int n) { std::vector<bool> isPrime(n + 1, true); isPrime[0] = isPrime[1] = false; // 0和1不是素数 for (int i = 2; i * i <= n; ++i) { // 只需遍历到sqrt(n) if (isPrime[i]) { // 标记i的倍数(从i²开始优化) for (int j = i * i; j <= n; j += i) { isPrime[j] = false; } } } // 收集所有素数 std::vector<int> primes; for (int i = 2; i <= n; ++i) { if (isPrime[i]) primes.push_back(i); } return primes; } ``` #### 关键点 - **优化点**:从 $i^2$ 开始标记,避免重复标记(例如:当 $i=5$ 时,直接标记25, 30, 35...) - **停止条件**:外层循环只需到 $\sqrt{n}$,因为更大因子的倍数已被处理。 --- ### 二、欧拉法(Euler's Sieve) #### 算法原理 每个合数仅被其最小质因数标记一次,时间复杂度 $O(n)$,空间复杂度 $O(n)$。 #### 代码实现 ```cpp #include <vector> std::vector<int> eulerSieve(int n) { std::vector<bool> isPrime(n + 1, true); std::vector<int> primes; isPrime[0] = isPrime[1] = false; for (int i = 2; i <= n; ++i) { if (isPrime[i]) { primes.push_back(i); // 记录素数 } // 用当前已知素数标记i的倍数 for (int j = 0; j < primes.size() && i * primes[j] <= n; ++j) { isPrime[i * primes[j]] = false; // 关键:当i能被当前素数整除时停止 if (i % primes[j] == 0) break; } } return primes; } ``` #### 关键点 - **线性时间复杂度**:每个合数仅被标记一次(如12只被2标记,不会被3重复标记)。 - **终止条件**:当 `i % primes[j] == 0` 时终止内循环,保证最小质因数选。 --- ### 三、算法对比 | 特性 | 埃氏筛 | 欧拉 | |--------------|---------------------------|--------------------| | 时间复杂度 | $O(n \log \log n)$ | $O(n)$ | | 空间复杂度 | $O(n)$ | $O(n)$ | | 重复标记 | 可能(如6被2和3标记) | 无 | | 适用场景 | 小规模数据或内存敏感 | 大规模数据 | --- ### 四、测试代码 ```cpp #include <iostream> int main() { int n = 30; std::cout << "埃氏筛结果: "; for (int num : eratosthenes(n)) std::cout << num << " "; std::cout << "\n欧拉结果: "; for (int num : eulerSieve(n)) std::cout << num << " "; return 0; } // 输出: // 埃氏筛结果: 2 3 5 7 11 13 17 19 23 29 // 欧拉结果: 2 3 5 7 11 13 17 19 23 29 ``` --- ### 五、常见问题 1. **为什么欧拉更高效?** 每个合数仅被其最小质因数标记一次,避免重复操作。 2. **埃氏筛的 `i * i` 优化是否必要?** 是,减少内层循环次数,但即使不优化,时间复杂度仍为 $O(n \log \log n)$。 3. **如何处理大数(如 $n \geq 10^7$)?** 使用欧拉,因其线性复杂度更高效。若内存不足,可分段选。 如需进一步优化,可考虑位运算压缩空间(如用 `bitset`)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值