【算法学习之路】4.简单数论(1)

简单数论(1)

  • 前言
  • 一. 质数筛
    • 1.什么是质数筛
    • 2.指数筛的种类
    • 3.朴素方法
    • 4.埃氏筛法
    • 5.欧拉筛法(线性筛法)
    • 总结

前言

我会将一些常用的算法以及对应的题单给写完,形成一套完整的算法体系,以及大量的各个难度的题目,目前算法也写了几篇,滑动窗口的题单正在更新,其他的也会陆陆续续的更新,希望大家点赞收藏我会尽快更新的!!!

一. 质数筛

1.什么是质数筛

质数:质数又称素数,是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。例如,2、3、5、7、11 等都是质数。

顾名思义质数筛就是通过某种方法将其质数给筛出来,对于某些要对质数进行操作的题目很有帮助。

2.指数筛的种类

基本的质数筛有三种:

1.朴素方法:时间复杂度为O(n ^ 2),但经过优化可以达到O(n ^(3 / 2))。
2.埃氏筛法:时间复杂度为O(n log logn)。
3.欧拉筛法:时间复杂度为O(n),因为其时间复杂度为O(n),所以又叫线性筛法。

以上三种方法时间复杂度逐渐降低,但代码逐渐抽象。

3.朴素方法

朴素方法就是暴力求解通过两层for循环来找到所有质素,其代码为:

#include <iostream>

using namespace std;

int n, k;
int prime[10005];

bool isPrime(int nums) {
	for (int i = 2; i <= nums; i++) {
		if (nums % i == 0) {//从二开始,看看有没有能被其整除的,若有则不是质数
			return false;
		}
	}
	return true;
}


int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {//从i= 2开始,因为1不是质数
		if (isPrime(i)) {
			prime[k++] = i;
		}
	}
	for (int i = 0; i < k; i++) {
		cout << prime[i] << endl;
	}

	return 0;
}

** 第一次简化:**从质数的定义我们知道,如果一个数(n)不是质数那么它就可以由两个数相乘得到 (这两个数都不能是1),那么两个数最小的一个数都要大于2,另一个数必然<= n / 2。所以我们只要求n在[2, n / 2]范围内能不能被整除就可以知道n是不是质数。所以第一次简化的代码为:

#include <iostream>

using namespace std;

int n, k;
int prime[10005];

bool isPrime(int nums) {
	for (int i = 2; i <= nums / 2; i++) {//注意是i <= nums / 2,不是i < nums / 2,如果是后者就会多个4,导致答案错误 
		if (nums % i == 0) {//从二开始,看看有没有能被其整除的,若有则不是质数
			return false;
		}
	}
	return true;
}


int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {//从i= 2开始,因为1不是质数
		if (isPrime(i)) {
			prime[k++] = i;
		}
	}
	for (int i = 0; i < k; i++) {
		cout << prime[i] << endl;
	}

	return 0;
}

虽然范围减少了一半,但其时间复杂度还是O(n)。
所以来第二次简化
还是从质数的定义入手,如果一个数(n)不是质数那么它就可以由两个数相乘得到 (这两个数都不能是1),那么我们来看看这两个数有什么特点,如图:

我们不难发现无论这个数有几种分法,但其中一个数是肯定小于等于根号n的,所以我们可以简化成,下面这串代码:

#include <iostream>
#include <cmath>

using namespace std;

int n, k;
int prime[10005];

bool isPrime(int nums) {
	for (int i = 2; i <= sqrt(nums); i++) {//sqrt()函数为求一个数的平方根运算
		if (nums % i == 0) {			   //注意这里也是<=因为4,9...也不行
			return false;
		}
	}
	return true;
}


int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {
		if (isPrime(i)) {
			prime[k++] = i;
		}
	}
	for (int i = 0; i < k; i++) {
		cout << prime[i] << endl;
	}

	return 0;
}

此时,代码的时间复杂度为O(n ^ (3 / 2))。虽然时间复杂度被优化了,但面对一些题目时间复杂度还是太高了,那我们来看看时间复杂度更低的代码。

4.埃氏筛法

了解埃氏筛法之前我们先了解一个定理:

算数基本定理(唯一分解定理):任何合数(除1和质数外的正整数)都可以表示为若干个质数的乘积,该分解式是唯一的。

而埃氏筛法就是利用算术基本定理来求解的:

我们可以从最小的质数开始(即2),将它的倍数都标记为合数,然后不断重复这个过程,直到遍历完所有小于等于目标数平方根的数,剩下未被标记的数就是质数。

也就是从最小的质数开始每次乘以i(2,3,4…)倍,将不是质数的数全部找出来,当>= n时停止,从下一个质数开始继续找。其代码如下:

#include <iostream>

using namespace std;

int Prime[10005];//全局变量初始化为0,且Prime[i] = 0为质数Prime[i] = 1不是质数
int isPrime[10005];//将质数存入这个数组
int n, k;

int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {
		if (Prime[i] == 0) {
			isPrime[k++] = i;
			for (int j = i * 2; j < n; j += i) {//质数的所有倍数都不是质数
				Prime[j] = 1;
			}
		}
	}
	
	for (int i = 0; i < k; i++) {
		cout << isPrime[i] << endl;
	}

	return 0;
}

此时代码的时间复杂度为O(n log log n),已经很低了,但是还能再低。

5.欧拉筛法(线性筛法)

我们观察上面的代码发现其中有好多数字被判断了好几次(如:6,可以看成2的3倍,也可以看成3的2倍),那么我们能不能只判断一次呢:
如果有一种方法可以将一个非质数分成两个特定的数就好了,那这两个数要么都是合数,要么一个是合数一个是质数,我们又发现,埃氏筛法的筛除方法使用的质数是从小到大的,那么我们可以把这个数分成一个最小的质数和一个合数,具体代码如下:

#include <iostream>

using namespace std;

int Prime[10005];
int isPrime[10005];
int n, k;


int main() {
	cin >> n;
	for (int i = 2; i <= n; i++) {
		if (isPrime[i] == 0) {
			Prime[k++] = i;
		}
		for (int j = 0; j < k; j++) {
			int x = i * Prime[j];
			if (x > n) {
				break;
			}
			isPrime[x] = 1;
			if (i % Prime[j] == 0) {//前面都没有==0,到这才==0,说明i的最小质数是Prime ,那么后面的x = i * Prime[j1],其最小质因数必然也是Prime[j],后续这些合数会在之后的筛选中由更小的i值和Prime[j]筛去,为避免重复筛选,此时应停止。
				break;
			}
		}
	}
	for (int i = 0; i < k;i++) {
		cout << Prime[i] << endl;
	}

	return 0;
}

总结

以上3个质数筛中欧拉筛法时间复杂度最低,所以一般情况下建议大家使用欧拉筛法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

零零时

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

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

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

打赏作者

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

抵扣说明:

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

余额充值