算法-5.质数筛法(数学知识)

1.相关概念及解析

质数:

质数概念:质数,又称素数,即约数只有1以及它本身的数。

01既不是质数也不是合数。

筛质数:

筛质数:将0---n之间的质数筛选出来,并保存到一个数组中或者直接输出。

方法:

1)朴素方法

2)埃氏筛法

3)欧拉筛法

2.朴素筛法

解析:

根据定义,因为质数除了1和本身之外没有其他约数,所以判断n是否为质数,根据定义直接判断从2n-1是否存在n的约数即可。代码如下:

bool isPrime(int n){
	if (n < 2) return 0; //0和1不是质数
	int tmp = n - 1;//判断到n-1
	for (int i = 2; i <= tmp; i++)
		if (n % i == 0)
			return 0; //n与比它小的数相除,除的尽则不是质数
	return 1; //都除不尽,是质数
}
for (int i = 2; i <= n; i++){
	if (isPrime(i) == 1)
		printf(“ % d ”,i);
}

时间复杂度是:O(n^2)

优化:

思考一个数去除以比它的一半还要大的数,一定除不尽的,这还用判断吗???

因此,只需要除到 n/2

优化代码:

bool isPrime(int n){
	if (n < 2) return 0; //0和1不是质数
	int tmp = n / 2; //判断到n/2
	for (int i = 2; i <= tmp; i++)
		if (n % i == 0) //如果n与某数相除为0,则不是质数
			return 0;
	return 1;//都除不尽,是质数
}
for (int i = 2; i <= n; i++)
{
	if (isPrime(i) == 1)
		printf(“ % d ”,i);
}

时间复杂度是: O(n^2)

优化2:

一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),据此,上述代码中并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。因此只要从2枚举到 √n 即可。

优化代码:

bool isPrime ( int n){
      if(n<2) return 0; //0和1不是质数
     int tmp =sqrt(n); //sqrt()是计算一个非负实数的平方根
     for(int i= 2;i <=tmp; i++)
        if(n %i== 0)
          return 0 ;//能整除,是合数
     return 1 ;//都除不尽,是质数
}

for(int i=2;i<=n;i++){
    if(isPrime(i)==1)
    printf(“%d ”,i);
}

时间复杂度是: O(n^(3/2))

3.埃氏筛法

解析:

埃拉托色尼选筛法(the Sieve of Eratosthenes)简称埃氏筛法,是古希腊数学家埃拉托色尼提出的一种筛选法。

基本原理:

一个合数总是可以分解成若干个质数的乘积,那么如果把质数(最初只知道2是质数)的倍数都去掉,那么剩下的就是质数了。

步骤:

1)先把1删除(1既不是质数也不是合数)

2)读取数组中当前最小的数2,然后把2的倍数删去

3)读取数组中当前最小的数3,然后把3的倍数删去

4)读取数组中当前最小的数5,然后把5的倍数删去

.......

n)读取数组中当前最小的状态为true的数n,然后把n的倍数删去

代码思路:

1、状态初始化。

2、从2开始,判断是否是质数,保存2遍历比n小的所有2的倍数,状态置false

3、判断是否是质数,保存3遍历下一个质数即3的倍数,并将状态置false

代码:

const int maxn = 1e7;
bool IsPrime[maxn];
int prime[maxn];
int  Era_prime(int n) {
	int tot = 0;
	memset(IsPrime, true, sizeof(IsPrime)); //初始化,所有数为质数 
	IsPrime[1] = 0, IsPrime[0] = 0;
	for (int i = 2; i <= n; i++)  //遍历2~n之间的所有数 
		if (IsPrime[i]) {   //从当前质数开始,筛去它的倍数 
			prime[tot++] = i;
			for (int j = i * 2; j <= n; j += i) //从当前质数2倍,3倍..直到j>n 
				IsPrime[j] = false;//将状态置为false 
		}
	return tot; //返回总共的质数个数 
}

埃氏做了许多无用功,一个数会被筛到好几次,比如623的倍数,则被筛到了两次,最后的时间复杂度是Onloglogn

如果我们在筛选时,对每一个数只筛一遍,那么这个时间复杂度将会怎样变化呢?

(欧拉筛法的出现)

4.欧拉筛法

解析:

算术基本定理(唯一分解定理):任何合数都能表示为若干质数的乘积,且该分解因式是唯一的。(不考虑顺序性)

原理:规定每个合数只会被它最小的质因数筛去。(后面的质因数直接跳过),这个最小的质因式必定小于它本身。

代码:

const int maxn = 1e7;
bool IsPrime[maxn];
int prime[maxn];
int  Euler_prime(int n) {
	int tot = 0;
	memset(IsPrime, true, sizeof(IsPrime));//初始化,默认所有数为质数 
	IsPrime[1] = 0, IsPrime[0] = 0;
	for (int i = 2; i <= n; i++) {  //外层循环从2~n开始遍历 
		if (IsPrime[i])
			prime[tot++] = i; //当前状态是true,记录
		for (int j = 0; j < tot; j++) { //遍历素数表
			if (i * prime[j] > n)//超过最大范围,跳出 
				break;
			IsPrime[i * prime[j]] = false;//将倍数i倍*某个质数筛除 
			if (i % prime[j] == 0)//保证只筛到以prime[j]为最小质因数的数,退出内层循环
				break;// 换言之,i 之前被 pri[j] 筛过了
			// 由于 pri 里面质数是从小到大的,所以 i乘上其他的质数的结果一定会被
			// pri[j]的倍数筛掉,就不需要在这里先筛一次,所以这里直接 break
			// 掉就好了
		}
	}
	return tot;
}

图例:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值