一、什么是质数
质数又叫素数,是只有1和它本身两个因数的正整数。 比如 2 ,3,5,7…
不是质数的正整数称为合数。比如,4,6,8,9 … ,最小的合数是 4。
注意:1 既不是质数,也不是合数。
二、如何判断一个整数是不是质数
根据定义,查看整数是否存在除1和它本身之外的因子。存在,说明不是质数;不存在,说明是质数。

#include<iostream>
using namespace std;
int main() {
	int n;
	cin >> n;
	// 判断n是否为质数
	for (int i = 2; i < n; ++i)
		if (n % i == 0) { //存在除1和它本身之外的因子,说明不是质数
			cout << n << "不是质数";
			return 0; //结束程序
		}
	//程序没有被提前结束,说明n是质数
	cout << n << "是质数";
	return 0;
}

通常,我们会把判断质数的功能抽离出来,写成函数。这样,主函数中可以根据需要多次调用,代码的逻辑也更清晰。

#include<iostream>
using namespace std;
bool is_prime(int x) {
	// 判断x是否为质数
	for (int i = 2; i < x; ++i)
		if (x % i == 0) return false; //存在除1和它本身之外的因子,说明不是质数
	return true;
}
int main() {
	int n;
	cin >> n;
	// 判断n是否为质数
	if (is_prime(n))
		cout << n << "是质数";
	else
		cout << n << "不是质数";
	return 0;
}

如果x有一个不是1或者它本身的因数 i,那么x一定还有一个因数是 什么是Ingress 什么是质数_#include。也就是说,整数的因子都是成对存在的。比如,12 的因数有 2和6、3和4 两对。
特殊情况是,当 x 是完全平方数时,存在一对儿相同的因数。比如,16的因数有 2和8、4和4.
显然,只要找到了一个因数,另一个因数也就找到了。
观察这些因数发现,这些成对的因数,都是一大一小,较小的因数最大能达到 x 的平方根 什么是Ingress 什么是质数_c++_02;较大的因数一定大于 x 的平方根 什么是Ingress 什么是质数_c++_02
所以,查找 x 的因数的时候,只需要遍历较小的那一半因数。也就是说只需要查看2 ~ 什么是Ingress 什么是质数_c++_02

bool is_prime(int x) {
// 判断x是否为质数 
	for (int i = 2; i * i <= x; ++i)
		if (x % i == 0) return false; //存在除1和它本身之外的因子,说明不是质数
	return true;
}

时间复杂度:根号什么是Ingress 什么是质数_c++_02
适用场景:判断一个正整数是不是质数

三、如何判断一个整数区间的质数有哪些
比如,找到 1 ~ n 区间内的所有质数
- 1、最朴素的方法是: 遍历整数区间,依次判断每个整数是不是质数。

#include<iostream>
using namespace std;
int a[100001];
bool isprime(int x) {
// 判断x是否为质数
	for (int i = 2; i * i <= x; ++i)
		if (x % i == 0)
			return false;
	return true;
}
int main() {
	int k = 0;
	for (int i = 2; i < 100001; i++) {
		if (isprime(i)) {
			a[k] = i;
			k++;
		}
	}
	for (int i = 0; i < k; i++)
		cout << a[i] << " ";
	return 0;
}

复杂度:O(n*什么是Ingress 什么是质数_c++_02),当n达到50000就有超时的风险。
适用场景:判断区间里的所有质数,但是由于时间复杂度比较高,只适合n<50000的情况。

- 2、埃氏筛
埃拉托斯特尼筛法,简称埃氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定质数的算法。算法思想是:要得到自然数n以内的全部质数,必须把不大于根号n的所有质数的倍数剔除,剩下的就是质数。
想要知道2~n范围内的所有质数,处理步骤如下:
(1)先用2去筛,即把2留下,把2的倍数剔除掉;
(2)再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;
(3)接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;
(4)不断重复下去…,直到把不大于根号n的所有质数的倍数剔除。

以n=25为例,求2~25范围内的所有质数。1不是质数,直接排除;2是最小的质数。

首先假设2~25的整数都是质数。

(1)第一个质数是2,将3~25 中所有2的倍数花掉,结果就是:

什么是Ingress 什么是质数_#include_07

目前,我们得到的质数有:2

(2)2后面的整数中,第一个整数是3,3没有被前面的质数划掉,那么3是质数,然后将5~25中3的倍数划掉,结果就是:

什么是Ingress 什么是质数_什么是Ingress_08


目前,我们得到的质数有:2,3

(3)3后面的整数中,第一个整数是5,5没有被前面的质数划掉,那么5是质数,然后将7~25中5的倍数划掉,结果就是:

什么是Ingress 什么是质数_c++_09


目前,我们得到的质数有:2,3,5 。

5是25的平方根。到目前为止,不大于根号n的所有质数的倍数都已经被剔除。

所以,2到25之间的质数是:2 3 5 7 11 13 17 19 23。

代码实现:

#include<bits/stdc++.h>
using namespace std;
bool isPrime[20000]; //全局变量,默认都是false
void getPrime(int n) {
	//假设2-n都是质数
	for (int i = 2; i <= n; i++)
		isPrime[i] = true;
	//遍历2-根号n里的所有整数
	for (int i = 2; i*i <= n; i++) {
		//如果i是质数
		if (isPrime[i]) {
			//那么,将i的倍数都划掉:i*2,i*3 .....肯定不是质数,注意边界j<=n
			for (int j = 2*i; j <= n; j+=i)
				isPrime[j] = false;
		}
	}
}
int main() {
	int n = 25;
	getPrime(n);
	for(int i=2; i<=n; i++)
		if(isPrime[i])
			cout << i << " ";

	return 0;
}

当n=25时,代码的输出结果是:

2 3 5 7 11 13 17 19 23

时间复杂度: O(nlog (logn))
使用范围:一般的题目都可以使用。

看到这里,出个问题考考你。

输入两个整数m和n,请计算区间[m,n]中有多少个质数?
解决思路:筛出2~n的所有质数,遍历区间[m,n]统计质数个数即可。
参考代码:

#include<bits/stdc++.h>
using namespace std;
bool isPrime[20000];
void getPrime(int n) {
	//假设2-n都是质数
	for (int i = 2; i <= n; i++)
		isPrime[i] = true;
	//遍历2-n里面所有数
	for (int i = 2; i <= n; i++) {
		//如果i是质数
		if (isPrime[i]) {
			//那么,将i的倍数都划掉:i*2,i*3 .....i*j肯定不是质数,注意边界i*j<=n
			for (int j = 2; i * j <= n; j++)
				isPrime[i * j] = false;
		}
	}
}
int main() {
	int m,n;
	cin >> m >> n;
	getPrime(n);
	int tot = 0;
	for(int i=m; i<=n; i++)
		if(isPrime[i])
			tot++;
	cout << tot;
	return 0;
}

再出个问题考考你,判断正整数n是不是质数。(2<n<10000000)
解决思路:判断一个整数是不是质数的情况下,筛质数法效率就低了。直接判断更好。时间复杂度是根号n。
参考代码:

bool is_prime(int x) {
// 判断x是否为质数 
	for (int i = 2; i * i <= x; ++i)
		if (x % i == 0) return false; //存在除1和它本身之外的因子,说明不是质数
	return true;
}

- 3、 欧拉筛(线性筛)
埃氏筛算法已经非常优秀了。但是我们会发现,在筛的过程中还是会重复筛掉同一个数。比如,12同时被2和3筛掉,20同时被2、5筛到。
为了进一步提高筛质数的效率,欧拉筛就出现了。欧拉筛也叫线性筛,因为它可以在 O(n) 的 时间内完成对2~n的质数筛选。
欧拉筛的核心思想是: 让每一个合数只被其最小质因数筛到。
欧拉筛的过程是这样的:

准备工作:除了一个标记是否是质数的数组外,还需要维护一个质数表,每找到一个新的质数,就将其存到这个质数表里。

还是以筛 2~25 中的质数为例。

开始时,数据的状态如下

什么是Ingress 什么是质数_什么是Ingress_10

依次遍历这些数据,首先遍历到 2 ,2没有被划掉,说明是质数,把2放进质数表:

什么是Ingress 什么是质数_#include_11


质数表: p = {2}

然后,用2去乘质数表里的每个数p[j],划掉它们。划到 2 是p[j]的倍数后,就提前结束。质数表里只有2,所以,只划掉了4。结果如下。

什么是Ingress 什么是质数_什么是Ingress_12


接下来遍历到 3,3没有被划掉,说明是质数,把3放进质数表:

什么是Ingress 什么是质数_算法_13


质数表: p = {2, 3}

然后,用3去乘质数表里的每个数 p[j],划掉它们。划到 3 是p[j]的倍数后,就提前结束。3是3的倍数。所以,划掉了6和9。结果如下。

什么是Ingress 什么是质数_i++_14


接下来遍历到4,注意,被划掉的数字也要遍历。4被划掉了,说明不是质数,不用把4放进质数表。

质数表: p = {2, 3}

然后,用4去乘质数表里的每个数 p[j],划掉它们。划到 4 是p[j]的倍数时,就提前结束。4是2的倍数,所以划掉8后,就提前结束。结果如下。

什么是Ingress 什么是质数_i++_15


接下来遍历到5。5没有被划掉,说明5是质数,把5放进质数表。

什么是Ingress 什么是质数_i++_16


质数表: p = {2, 3, 5}

然后,用5去乘质数表里的每个数 p[j],划掉它们。划到 5 是p[j]的倍数时,就提前结束。5是5的倍数,所以划掉了 10、15 和25。结果如下。

什么是Ingress 什么是质数_算法_17


接下来遍历到6。6被划掉了,说明6不是质数,不用把6放进质数表。

质数表: p = {2, 3, 5}

然后用6去乘质数表里的每个数 p[j],划掉它们。划到 6 是p[j]的倍数时,就提前结束。6是2的倍数,所以划掉了 12就提前结束。结果如下。

什么是Ingress 什么是质数_c++_18


接下来遍历到7。7没有被划掉,说明7是质数,把7放进质数表。

什么是Ingress 什么是质数_#include_19


质数表: p = {2, 3, 5, 7}

然后用7去乘质数表里的每个数 p[j],划掉它们。划到 7 是p[j]的倍数时,就提前结束。57=35,超出了2~25的范围,所以划掉了 14、21,提前结束。结果如下。

什么是Ingress 什么是质数_什么是Ingress_20


接下来遍历到8,注意,被划掉的数字也要遍历。8被划掉了,说明不是质数,不用把8放进质数表。
质数表: p = {2, 3, 5, 7}
然后,用8去乘质数表里的每个数 p[j],划掉它们。划到 8 是p[j]的倍数时,就提前结束。8是2的倍数,所以划掉16后,就提前结束。结果如下。

什么是Ingress 什么是质数_c++_21


接下来遍历到9,注意,被划掉的数字也要遍历。9被划掉了,说明不是质数,不用把9放进质数表。
质数表: p = {2, 3, 5, 7}
然后,用9去乘质数表里的每个数 p[j],划掉它们。划到 9 是p[j]的倍数时,就提前结束。9是3的倍数,但是3
9=27,已经超出了2~25的范围,所以划掉18后,就提前结束。结果如下。

什么是Ingress 什么是质数_什么是Ingress_22


接下来遍历到10,注意,被划掉的数字也要遍历。10被划掉了,说明不是质数,不用把10放进质数表。

质数表: p = {2, 3, 5, 7}

然后,用10去乘质数表里的每个数 p[j],划掉它们。划到 10 是p[j]的倍数时,就提前结束。10是2的倍数,所以划掉20后,就提前结束。结果如下。

什么是Ingress 什么是质数_c++_23


按这样的步骤进行下去,筛掉所有的合数,就得到一张质数表。

p = {2, 3, 5, 7, 11, 13, 17, 19, 23}

代码实现:

#include<iostream>
#include<vector>
using namespace std;
bool isnp[2000000]; //标记是不是和数
vector<int> p; // 质数表
int len; //用于记录质数的个数
//欧拉筛质数
void ola(int n) {
	//遍历每个整数
	for (int i = 2; i <= n; i++) {
		if (!isnp[i]) {
			p.push_back(i);
			len++;
		}

		for (int j = 0; j < len; j++) {
			if (p[j] * i > n) break; //超出范围,提前结束
			isnp[p[j] * i] = 1; //划掉
			if (i % p[j] == 0) break; //当i是p[j]的倍数时,提前结束
		}
	}
}
int main() {
	int n;
	cin >> n;
	ola(n); //欧拉筛
	//输出所有质数
	for (int i = 0; i < len; i++)
		cout << p[i] << " ";

	return 0;
}

C++11风格代码:

#include<iostream>
#include<vector>
using namespace std;
bool isnp[2000000]; //标记是不是和数
vector<int> p; // 质数表
//欧拉筛质数
void ola(int n) {
	//遍历每个整数
	for (int i = 2; i <= n; i++) {
		if (!isnp[i]) {
			p.push_back(i);
		}
		//C++11语法遍历vector中所有元素
		for (int j : p) {
			if (j * i > n) break; //超出范围,提前结束
			isnp[j * i] = 1; //划掉
			if (i % j == 0) break; //当i是p[j]的倍数时,提前结束
		}
	}
}
int main() {
	int n;
	cin >> n;
	ola(n); //欧拉筛
	//输出所有质数
	for (int i : p) //C++11语法遍历vector中所有元素
		cout << i << " ";

	return 0;
}

欧拉筛(线性筛)除了解决一些卡埃氏筛的毒瘤题外,在些数论算法中还有更多的应用。