质数判断是编程中常见的基础算法问题,也是检验程序员基本功的经典题目。我们先来理解质数的数学定义:质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的数。换句话说,质数只能被1和它自身整除。
最直观的判断方法是试除法:对于一个给定的数n,我们从2开始,一直试除到n-1,如果都不能整除,则n是质数。这种方法虽然简单直接,但效率很低,时间复杂度为O(n)。
cpp复制bool isPrime_naive(int n) {
if (n <= 1) return false;
for (int i = 2; i < n; i++) {
if (n % i == 0) return false;
}
return true;
}
数学上可以证明,如果n不是质数,那么它一定有一个因数小于等于√n。因此,我们只需要检查2到√n之间的数即可,这样时间复杂度降为O(√n)。
cpp复制bool isPrime_sqrt(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
注意:这里使用i*i <= n而不是i <= sqrt(n)是为了避免重复计算平方根,提高效率。这种写法是C++中常见的优化技巧。
除了2以外,所有偶数都不是质数。我们可以先排除偶数,然后只检查奇数,这样循环次数减少一半。
cpp复制bool isPrime_optimized(int n) {
if (n <= 1) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
完整的区间质数筛选程序包含以下几个部分:
cpp复制#include <iostream>
using namespace std;
bool isPrime(int num) {
// 优化后的质数判断函数
if (num <= 1) return false;
if (num == 2) return true;
if (num % 2 == 0) return false;
for (int i = 3; i * i <= num; i += 2) {
if (num % i == 0) return false;
}
return true;
}
int main() {
int n, m;
cin >> n >> m;
bool hasPrime = false;
for (int i = n; i <= m; i++) {
if (isPrime(i)) {
cout << i << " ";
hasPrime = true;
}
}
if (!hasPrime) {
cout << "没有";
}
return 0;
}
程序使用标准输入输出流进行数据交互:
cin >> n >> m 读取两个整数作为区间边界cout << i << " " 输出质数,用空格分隔hasPrime标志记录是否找到质数提示:在实际应用中,可以考虑添加输入验证,确保n ≤ m,且两者都是正整数。
cpp复制// 埃拉托斯特尼筛法示例
void sieveOfEratosthenes(int n) {
vector<bool> prime(n+1, true);
prime[0] = prime[1] = false;
for (int p = 2; p * p <= n; p++) {
if (prime[p]) {
for (int i = p * p; i <= n; i += p)
prime[i] = false;
}
}
// 输出质数
for (int p = 2; p <= n; p++)
if (prime[p])
cout << p << " ";
}
cpp复制// 处理边界情况的改进版本
if (n > m) swap(n, m); // 交换边界
if (n < 2) n = 2; // 质数最小为2
健壮的程序应该处理各种异常输入:
cpp复制if (!(cin >> n >> m)) {
cout << "输入错误,请输入两个整数";
return 1;
}
if (n > m) {
cout << "区间下限不能大于上限";
return 1;
}
对区间[1, 1000000]进行测试:
提示:对于一次性查询大量质数,筛法优势明显;对于少量查询或大数判断,优化后的单次判断更合适。
数学原理:如果n有因数d大于√n,那么必然存在对应的因数n/d小于√n。因此只需要检查到√n就能确定n是否为质数。
对于非常大的数(如超过int范围):
cpp复制bool isPrime_big(long long n) {
if (n <= 1) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (long long i = 3; i <= n / i; i += 2) {
if (n % i == 0) return false;
}
return true;
}
cpp复制// 使用预生成的小质数优化
vector<int> smallPrimes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
bool isPrime_optimized2(int n) {
if (n <= 1) return false;
// 先用小质数试除
for (int p : smallPrimes) {
if (n == p) return true;
if (n % p == 0) return false;
}
// 再用常规方法检查
for (int i = 31; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
现代加密算法(如RSA)依赖大质数的难分解性。虽然我们的算法对于加密所需的大质数还不够高效,但理解基础原理很重要。
同样的算法可以用不同语言实现。以下是Java版本:
java复制public class PrimeNumbers {
public static boolean isPrime(int num) {
if (num <= 1) return false;
if (num == 2) return true;
if (num % 2 == 0) return false;
for (int i = 3; i * i <= num; i += 2) {
if (num % i == 0) return false;
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
boolean hasPrime = false;
for (int i = n; i <= m; i++) {
if (isPrime(i)) {
System.out.print(i + " ");
hasPrime = true;
}
}
if (!hasPrime) {
System.out.print("没有");
}
}
}
对于区间[1, 10000000]的质数筛选,我们可以这样优化:
cpp复制// 分段筛法示例
void segmentedSieve(int n) {
const int SEG_SIZE = 10000; // 分段大小
vector<int> primes;
// 先筛小质数
sieveOfEratosthenes(sqrt(n), primes);
for (int low = 2; low <= n; low += SEG_SIZE) {
int high = min(low + SEG_SIZE - 1, n);
vector<bool> mark(high - low + 1, true);
for (int p : primes) {
int firstMultiple = max(p * p, (low + p - 1) / p * p);
for (int j = firstMultiple; j <= high; j += p)
mark[j - low] = false;
}
for (int i = low; i <= high; i++)
if (mark[i - low] && i >= 2)
cout << i << " ";
}
}
在实际编程中,质数判断和筛选是基础但重要的算法。理解其原理并掌握优化技巧,不仅能解决具体问题,也能提升整体编程思维能力。我在实际项目中发现,很多性能问题都源于对基础算法的不当使用,因此扎实掌握这些基础算法至关重要。