质数(素数)的核心解释
判断质数时,循环变量 i<=sqrt(n) 的原因:
1. 如果一个数 n 不是质数,那么它一定可以分解为两个因数的乘积: n = a * b
2. 这两个因数中,必然有一个小于等于 sqrt(n),一个大于等于 sqrt(n)
因为如果两个因数都大于 sqrt(n),那么它们的乘积就会大于 n
3. 所以我们只需要检查 n 能否被小于等于 sqrt(n) 的数整除
如果都不能整除,那么 n 就是质数
4. 这样可以将时间复杂度从 O(n) 优化到 O(sqrt(n))
举例:
判断 16 是否为质数:
- sqrt(16) = 4
- 只需要检查 2,3,4 是否能整除 16
- 发现 2 可以整除 16,所以 16 不是质数
// 效率低的版本 bool isPrime(int n) { for(int i = 2; i < n; i++) { // 检查到 n-1 if(n % i == 0) return false; } return true; } // 优化版本 bool isPrime(int n) { for(int i = 2; i * i <= n; i++) { // 只检查到 sqrt(n) if(n % i == 0) return false; } return true; }
性能差异:
- 对于数字100
- 第一种方法需要检查到99
- 第二种方法只需要检查到10
- 检查次数减少了90%!
所以使用 sqrt(n) 作为界限是一个重要的优化,可以大大减少计算量
寻找1-n以内所有质数
直接上代码:
#include <bits/stdc++.h>
using namespace std;
bool isPrime(int n)
{
if(n<=1) return false;
for(int i=2;i<=sqrt(n);i++)
{
if(n%i==0)
{
return false;
break;
}
}
return true;
}
int main()
{
int n,count=0;
cin>>n;
for(int i=1;i<=n;i++)
{
if(isPrime(i))
{
count++;
cout<<"第"<<count<<"项质数为:"<<i<<endl;
}
}
return 0;
}
可以将这段代码作为母版,可以延申出很多csp试题,正所谓万变不离其宗嘛!
试题:
输入一个正整数n,求第n小的质数。
输入:
一个不超过10000的正整数n
输出:
第n小的质数
样例输入:
10
样例输出:
29
// 这种方法虽然可行,但效率不是最优的。如果需要找较大的n,使用埃氏筛法会更快。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, s = 0; // n是要找第几个质数,s是当前找到的质数个数
cin >> n;
for(int i = 2; ; i++) // 从2开始无限循环,直到找到第n个质数
{
int x = 2; // x用来测试i是否能被整除
// 关键的质数判断循环
while(x <= floor(sqrt(i)) && i % x != 0)
x++;
// 如果x超过了sqrt(i),说明i是质数
if(x > floor(sqrt(i)))
{
s++; // 找到一个质数,计数器加1
if(s == n) // 如果是第n个质数
{
cout << i << endl; // 输出这个数
break; // 退出循环
}
}
}
return 0;
}
// ====================== 解法二 ==============
#include <iostream>
using namespace std;
// 判断质数的函数
bool isPrime(int num) {
if (num < 2) return false; // 小于2的数不是质数
for (int i = 2; i * i <= num; i++) { // 只需检查到平方根
if (num % i == 0) return false; // 能被整除就不是质数
}
return true; // 都检查完了没有因数,是质数
}
int main() {
int n; // 要找第几个质数
cin >> n;
int count = 0; // 已找到的质数个数
int num = 2; // 从2开始检查的数
while (count < n) { // 当找到的质数个数小于n时继续循环
if (isPrime(num)) { // 如果当前数是质数
count++; // 计数器加1
}
if (count < n) { // 如果还没找到第n个质数
num++; // 检查下一个数
}
}
cout << num << endl; // 输出第n个质数
return 0;
}
再次解释核心代码
解释一下为什么 x > floor(sqrt(i)) 可以判断一个数是质数:
数学原理:
- 如果一个数 i 不是质数,那么它一定有两个因数 a 和 b,使得 a × b = i
- 这两个因数中,至少有一个小于或等于 √i
举例说明:(比如数字 16)
- √16 = 4
- 16的因数对:1×16, 2×8, 4×4
- 可以看到至少有一个因数 ≤ 4
代码分析:
int x = 2; // 循环条件:x不超过sqrt(i) 且 i不能被x整除 while(x <= floor(sqrt(i)) && i % x != 0) x++; if(x > floor(sqrt(i))) // 关键判断 { // 是质数 }
判断逻辑:
- 如果 x 超过了 √i,说明什么?
- 说明从2到√i的所有数都不能整除i
- 根据上面的数学原理,这就意味着i是质数
具体例子:(检查数字7是否为质数)
i = 7 √7 ≈ 2.646,floor(√7) = 2 x = 2: 7%2 ≠ 0,x++ x = 3: x>2(√7),退出循环 此时 x>floor(√7),所以7是质数
检查数字9是否为质数:
i = 9 √9 = 3 x = 2: 9%2 ≠ 0,x++ x = 3: 9%3 = 0,找到因数 此时 x不大于floor(√9),所以9不是质数
所以 x > floor(sqrt(i)) 是一个高效的质数判断方法,不需要检查到i本身就能得出结论!
埃托拉斯特尼筛法求解质数(素数)-------------------- 对于大数组效率更高。
下面是使用 C++ 实现的 埃拉托斯特尼筛法(Sieve of Eratosthenes)来找出指定范围内的所有质数的代码示例。
C++ 实现
#include <iostream>
#include <vector>
using namespace std;
// 埃拉托斯特尼筛法
vector<int> sieve_of_eratosthenes(int n) {
// 初始化布尔数组,所有数假定为质数
vector<bool> is_prime(n + 1, true);
is_prime[0] = is_prime[1] = false; // 0和1不是质数
// 从2开始,筛选质数
for (int i = 2; i * i <= n; ++i) {
if (is_prime[i]) {
for (int j = i * i; j <= n; j += i) {
is_prime[j] = false; // 标记i的倍数为非质数
}
}
}
// 将所有质数收集到一个结果数组中
vector<int> primes;
for (int i = 2; i <= n; ++i) {
if (is_prime[i]) {
primes.push_back(i);
}
}
return primes;
}
int main() {
int n;
// 输入范围n
cout << "Enter a number: ";
cin >> n;
// 获取所有小于等于n的质数
vector<int> primes = sieve_of_eratosthenes(n);
// 输出结果
cout << "Primes less than or equal to " << n << ": ";
for (int prime : primes) {
cout << prime << " ";
}
cout << endl;
return 0;
}
解释
-
初始化布尔数组:我们创建一个
is_prime
数组,它的大小为n+1
,并将所有元素初始化为true
,表示每个数假设是质数。然后,我们将is_prime[0]
和is_prime[1]
设置为false
,因为 0 和 1 不是质数。 -
筛选过程:从
i = 2
开始,我们遍历每个数。如果当前数i
是质数(即is_prime[i] == true
),则标记从i*i
开始的所有i
的倍数为非质数(is_prime[j] = false
)。我们只需要从i*i
开始标记,因为小于i*i
的倍数已经被之前的质数筛掉了。 -
收集质数:筛选结束后,我们遍历
is_prime
数组,将值为true
的索引(即质数)收集到一个primes
向量中。 -
输出:在
main
函数中,我们接受用户输入的整数n
,并通过调用sieve_of_eratosthenes(n)
获取小于等于n
的所有质数,最后输出这些质数。
示例输出
假设用户输入 n = 30
,则程序输出:
Enter a number: 30
Primes less than or equal to 30: 2 3 5 7 11 13 17 19 23 29
时间复杂度
-
时间复杂度:O(n log log n),这是埃拉托斯特尼筛法的时间复杂度,它比逐个检查每个数是否是质数的 O(n√n) 更高效。
-
空间复杂度:O(n),由于我们使用了一个大小为
n+1
的布尔数组来存储每个数是否是质数的信息。
总结
这段 C++ 代码使用埃拉托斯特尼筛法有效地找出了小于或等于给定数字 n
的所有质数,适合用于求解较大范围内的质数列表。
试题(埃托拉斯特尼解法)
【题目描述】
用筛法求出n(2≤n≤1000)n(2≤n≤1000)以内的全部质数。
【输入】
输入n。
【输出】
多行,由小到大的质数。
【输入样例】
10
【输出样例】
2
3
5
7
#include <bits/stdc++.h>
using namespace std;
const int n = 100;
bool prime[n+1];
int main()
{
int n;
cin>>n;
memset(prime,1,sizeof(prime));
prime[0] = false,prime[1] = false;
for(int i=2;i<=sqrt(n);i++)
{
if(prime[i])
{
for(int j=2;j<=n/i;j++)
{
prime[i*j] = false;
}
}
}
for(int i=2;i<=n;i++)
{
if(prime[i])
{
cout<<i<<endl;
}
}
return 0;
}
质数筛法是这样的:
1.开一个大的 bool型数组prime[],大小就是n+1就可以,先把所有的下标为true.2.然后:
for( i=2;i<=sqrt(n);十十i )
{
if(prime[i])
{
for(j=2;j<=n/i;j++)
prime[i*j] = false;
}
}
3.最后输出bool数组中的值为true的单元的下标,就是所求的n以内的素数了。原理很简单,就是当i是质数的时候,i的所有的倍数必然是合数。如果i已经被判断不是质数了,那么再找到i后面的质数来把这个质数的倍数掉。
一个简单的筛素数的过程:n=30。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30第1步过后4...28 30这15个单元被标成false,其余为true。
第2步开始:
i=3; 由于 prime[3]=true,把 prime[6],[9],[12],[15],[18],[21],[24],[27],[30]标为 false.
i=5;由于 prime[5]=true, 把 prime[10],[15],[20],[25],[30]标为 false.i=6;>sqrt(30)算法结束。
第3步把 prime[]值为true 的下标输出来:
结果是2 3 5 7 11 13 17 19 23 29