一、啥是素数(质数)?
在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。 意思就是: 只能被1和它自己整除。
二、解题思路
双重for循环,第一层从2增长到1000,第二层尝试取余,出现整除,视为非素数。
for ($i = 2; $i < 1001; $i++) {
for ($j = 2; $j < $i; $j++) {
if ($i % $j === 0) {
continue 2;
}
}
echo $i . ',';
}
这种方式很容易理解,速度上也不错。但是事情当然不会这么简单。
三、更好的方法?
毕竟我还是不擅长算法的,想着看看别人怎么做,然而百度和google了一下。答案基本都是
<?php
for($i = 2; $i < 1001; $i++) {
$primes = 0;
for($k = 1; $k <= $i; $k++)
if($i%$k === 0) $primes++;
if($primes <= 2) // 能除以1和自身的整数(不包括0)
echo "<strong>{$i}</strong><br />";
}
谁写的!也太坑了吧。一个是非要除一下1和自己本身,另一个是第二层每次都递增到自身大小。
四、优化
自己想想吧,虽然做过的题不多,但也知道动态规划,隐隐约约觉得每个后边的数字都会和前边有关联。
噢!除以2之后有余数,就不必除4了,除以3有余数就不必除以9了,以此类推。之前得到的素数都可以被利用到。声明一个素数数组,先验证是否可以被素数组里的数整除,如果都无法整除,第二层for循环的起始位置发生更改,从前一个素数之后一个数开始。
// 如果数组是空的,第一次++$item会报错,所以第一个素数2直接放在数组里。
$arr = [2];
for ($i = 3; $i < 1001; $i++) {
foreach ($arr as $item) {
if ($i % $item === 0) {
continue 2;
}
}
for ($j = ++$item; $j < $i; $j++) {
if ($i % $j === 0) {
continue 2;
}
}
$arr[] = $i;
}
嗯,测试,没问题。
我们来看一下这三种方法的速度吧(依次是网上的方法,我的第一版,优化后),1000太小了,换成1W。
第一种和第二种速度差9倍,第二种和第三种,速度差7倍。
去掉第一种方法,用10W再测试。
速度差了9倍。
五、再优化
# 再想想呢?素数里有一个数很特殊,那就是2
,它是唯一一个偶数,其余都是奇数。所以每次可以自增2,$i++
可以改成$i += 2
,既然之后都是奇数了,也就不必再总是先试一次除以2了。
# 再想想呢?按照上一个想法,第二层for循环,++$item
应该改成每次加2。当然,这个和上一点优化一样,起始作用相当微弱。
# 再想想呢? 嗯,先把这些素数打印出来,找规律。。。。。哎!整除,我foreach遍历所有素数数组这里有什么不对,当被除数遍历到超过当前验证数字一半的时候,就不可能被整除了呢!即加上判断$item < count($arr) / 2
# 再想想呢?嗯,,,按照上一个想法,为什么一定是一半呢,1/3 也不可以啊,如果能1/3早就被3整除了,以此类推,1/5、1/7、1/11 … 什么时候是头呢?嗯,应该是开根,当大于$i / sqrt($i)
也就是sqrt($i)
之后就不可能了。
# 再想想呢?算了。
$arr = [3];
for ($i = 5; $i < 100000; $i += 2) {
$sqrt = sqrt($i);
foreach ($arr as $item) {
if ($item <= $sqrt && $i % $item === 0) {
continue 2;
}
}
$item += 2;
for ($j = $item; $j < $i; $j++) {
if ($i % $j === 0) {
continue 2;
}
}
$arr[] = $i;
}
array_unshift($arr, 2);
这样优化后,对比上一次优化后的代码,以10w测试,速度提升一倍多。
应该还有优化的空间,若您知道,烦请告诉我,不胜感谢。