题目
判断一个正整数是否为素数有哪几种方法,每种方法的时间复杂度怎么样。
解析
素数又称质数,是指在大于1的自然数中,除了1和它本身以外,不再有其他因数的自然数。素数只有1和它本身两个正因数,最小的素数是2,素数的个数是无穷的。
埃拉托斯特尼筛法(Sieve of Eratosthenes),简称“筛法”,是一种简单判定素数的算法,也是一种历史悠久的筛法。要得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。其方法是,给出要筛数值的范围n,先用2去筛,把2留下,把2的倍数剔除掉;再用下一个素数,也就是3筛,把3留下,把3的倍数剔除掉;不断重复下去。
根据上面的思路,我们给出了下面的示例代码。
fn is_prime(num: u32) -> bool {
if num <= 1 {
return false;
}
let mut primes = vec![true; (num + 1) as usize];
primes[0] = false;
primes[1] = false;
let sqrt_num = (num as f64).sqrt() as u32;
for i in 2..=sqrt_num {
if primes[i as usize] {
let mut j = i * i;
while j <= num {
primes[j as usize] = false;
j += i;
}
}
}
primes[num as usize]
}
fn main() {
for i in 0..100 {
if is_prime(i) {
println!("{}", i);
}
}
}
通过上面的代码可以得知,埃拉托斯特尼筛法的时间复杂度为O(nlogn)。
判断一个数是否为素数,最直接也最常用的方法为:循环遍历从2到该数的平方根之间的所有整数,尝试能否整除该数。如果存在能整除该数的整数,则该数不是素数;否则,该数是素数。这种方法的时间复杂度为O(sqrt(n)),具体实现,可参考下面的示例代码。
use std::time::{Duration, Instant};
fn is_prime(num: u32) -> bool {
if num <= 1 {
return false;
}
let sqrt_num = (num as f64).sqrt() as u32;
for i in 2..=sqrt_num {
if num % i == 0 {
return false;
}
}
true
}
fn main() {
let start = Instant::now();
for i in 0..100 {
if is_prime(i) {
println!("{}", i);
}
}
let duration = start.elapsed();
println!("{}ms", duration.as_millis());
}
还有一种更高效的方法,需要用到素数分布的规律。该规律为:大于等于5的素数,一定和6的倍数相邻。比如:素数5、7在6的两侧,素数11、13在12的两侧,素数17、19在18的两侧。
该分布规律的证明其实并不难,假设有一个数x>=1,则可将大于等于5的自然数N表示为:6x-1、6x、6x+1、6x+2、6x+3、6x+4、6x+5、6(x+1)、6(x+1)+1、...。可以看到,不在6的倍数两侧的数为:6x+2、6x+3、6x+4。由于6x+2为2(3x+1),6x+3为3(2x+1),6x+4为2(3x+2),故它们一定不是素数。再除去6x本身,则素数只可能出现在6x的相邻两侧。但是注意,在6x的相邻两侧,并不一定就是素数。
对于N为6x-1、6x+1这两种情况,可以将其表示为:6i-1、6i、6i+1、6i+2、6i+3、6i+4。如果N能被6i、6i+2、6i+4整除,则N至少得是一个偶数,但6x-1、6x+1明显为一个奇数,故不成立。如果N能被6i+3整除,则N至少能被3整除,但6x-1、6x+1不可能被3整除,故亦不成立。综上,只需要考虑6i-1和6i+1的情况,即:循环的步长可以设置为6,每次判断循环变量k和k+2的情况。理论上,该方法的时间复杂度为O(sqrt(n)/3)。
根据上面的思路,我们给出了下面的示例代码。
use std::time::{Duration, Instant};
fn is_prime(num: u32) -> bool {
// 单独处理0和1
if num <= 1 {
return false;
}
// 单独处理2和3
if num == 2 || num == 3 {
return true;
}
// 不在6的倍数两侧的,一定不是素数
if num % 6 != 1 && num % 6 != 5 {
return false;
}
// 在6的倍数两侧的,也可能不是素数
let sqrt_num = (num as f64).sqrt() as u32;
for i in (5..=sqrt_num).step_by(6) {
// 只需要考虑6i-1和6i+1的情况
if num % (i) == 0 || num % (i + 2) == 0 {
return false;
}
}
// 排除所有情况,剩余的肯定是素数
true
}
fn main() {
let start = Instant::now();
for i in 0..100 {
if is_prime(i) {
println!("{}", i);
}
}
let duration = start.elapsed();
println!("{}ms", duration.as_millis());
}
总结
通过这道题,我们学习了判断一个正整数是否为素数的若干方法。既有历史悠久的埃拉托斯特尼筛法,也有常用的遍历法。在遍历法的基础上,我们利用素数的分布规律,对算法进行了优化,提升了其运行效率,降低了其时间复杂度。
💡 如果想阅读最新的文章,或者有技术问题需要交流和沟通,可搜索并关注小红书“希望睿智”。