PHP求质数-演道网
看起来貌似是个很没营养的问题,网上一搜一大堆,粗制滥造的答案看就能看吐。
求质数,比如:
求 N 是否为质数?那就从 i=2 一直遍历到 i=N-1 之后求余呗,只要余数有为 0 的,那 N 就不是质数————这是不少网上教程给出的答案,可能不少计算机专业老师也是这么教课的。
衍生题目还有:
- 求 从 1 到 N 之间所有质数
- 从 1 开始,求 N 个质数
- 求 从 M 到 N 之间所有质数
- 从 M 开始,求 N 个质数
自计算机编程开始低廉化之后,太多的人蜂拥涌入软件开发行业,以Java和Android为潮流,低效低质量的软件和代码层出不穷。编程从一门优雅的艺术变成了堆满劣质农家肥的垃圾场。
书本算法
从1开始的传统思路:试除
除了1和该数自身外,无法被其他自然数整除的数,即为质数。则确认 一个数 是否为素数的最基本方法,就是将 这个数 除以每个大于1且小于等于 其自身 的整数 m。若余数为0,则这个数不是质数。反之则是。
这种方法就叫做:试除法
问题1
先考虑最基本问题:从 1 到 N 之间所有质数
首先就要把 i=2 一直遍历到 i=N-1 之后求余 这种方法掀翻了。
假如问 101 是否为质数,难道还要验证一下 101 % 100 是否为 0 吗?多余。
要遍历的最后一个值应该是 N的平方根取整 sqrt(101),只要验证 101 % 10 是否为 0 就可以了。等到11的时候,11 * 11 = 121 ,已经大于 101 了,没必要计算。
维基百科上关于试除法的定义:
测试 n 是否为素数的最基本方法为试除法。此一程序将 n 除以每个大于1且小于等于 n 的平方根之整数 m。若存在一个相除为整数的结果,则 n 不是素数;反之则是个素数。
代码:
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
$m = 1;
$n = 10000;
$result = array();
$next = 0;
for ($i = $m; $i <= $n; ++$i) {
for ($x = 2; $x <= sqrt($i); ++$x) {
$Modulo = $i % $x;
if ($Modulo == 0) {
//echo ‘Number ‘.$i.’ is not Prime number’.”\n”;
$flag = false;
break;
}
}
if ($flag) {
$result[] = $i;
}
$flag = true;
}
print_r($result);
echo count($result).“\n”;
|
但对于从1开始的方法,我们还可以继续考虑:
所有大于2的偶数都是合数,这个毫无争议,所以应该跳过所有对大于2的偶数的判断。
接下来,每一合数都可以以唯一形式被写成质数的乘积,算术基本定理。那么只要验证一个数不可以被其他质数整除,那么他就是质数。缩小原有范围(无法被其他自然数整除的数)。
比如要验证 49 是否为质数,则验证 49 % 2 和 49 % 3 之后,没有必要去验证 49 % 4 或 49 % 6,可以跳过 4 直接验证 5,跳过 6 直接验证 7。
我们的计算就会简化为:
数组$result 保存从1到i-1之间所有的质数,为确定数i是否为质数时,确认N是否可以被 数组$result 中的任何一个数整除,如全部都不能,i就是质数。
代码:求1-100000之间的所有质数
PHP
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
31
32
33
|
$n = 100000;
$result = array(2);
$next = 1;
while (true) {//预留
$next = $next + 2;//直接判断奇数,跳过所有偶数
if ($next >= $n) {
break;//已满足所求数量条件,结束
}
$flag = false;//初始化flag
foreach ($result as $x) {
if ($x > sqrt($next)) {//没有找到能范围内能整除此数的质数,这个数字也是质数
//echo ‘Number ‘.$next.’ is Prime number’.”\n”;
$flag = true;
break;
}
$Modulo = $next % $x;
if ($Modulo == 0) {
//echo ‘Number ‘.$next.’ is not Prime number’.”\n”;
break;
}
}
if ($flag) {
$result[] = $next;//将这个数字放入结果数组
//echo ‘count is ‘.count($result).”\n”;
}
}
print_r($result);
echo count($result).“\n”;
|
问题2
第二个问题: 从 1 开始,求 N 个质数
我们的算法是在判断条件达成后结束的,那么我们把判断条件向后延伸就好了。
代码:从 1 开始,求 N 个质数
PHP
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
31
32
33
34
35
|
$n = 10000;//十万太多,一万个就好了
$result = array(2);
$next = 1;
while (count($result) < $n) {//检查$reslut中保存了多少个结果,满足结果后就退出
$next = $next + 2;//直接判断奇数,跳过所有偶数
/*
if ($next >= $n) {
break;//现在用不到这个条件了
}
*/
$flag = false;//初始化flag
foreach ($result as $x) {
if ($x > sqrt($next)) {//没有找到能范围内能整除此数的质数,这个数字也是质数
//echo ‘Number ‘.$next.’ is Prime number’.”\n”;
$flag = true;
break;
}
$Modulo = $next % $x;
if ($Modulo == 0) {
//echo ‘Number ‘.$next.’ is not Prime number’.”\n”;
break;
}
}
if ($flag) {
$result[] = $next;//将这个数字放入结果数组
//echo ‘count is ‘.count($result).”\n”;
}
}
print_r($result);
echo count($result).“\n”;
|
从1开始的新思路
问题1
我们刚才做了一件有趣的事,我们在查找从 2 到 N 之间的时候,把所有偶数都刨除去了,因为我们可以很清楚的确定,偶数就是能被2整除的数字,那么除了2这个特例之外,其他大于2的偶数都是合数,没有判断的必要。
那么我们是否有办法把能被3整除的合数也都刨去?被5呢?接下来还有7、11,我们是否可以一边找到最小的质数,一边把能被这个质数整除的所有合数都刨除在接下来的判定范围内?
这就是所谓的“筛法”、“排除法”了。
代码:从 1 到 N 之间所有质数
PHP
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
31
32
33
34
35
36
37
38
|
$n = 100000;
$result = array();
for ($i = 2; $i <= $n; ++$i) {
$result[] = $i;//首先生成一个从 2 到 100000 的连续数组。[2,3,4,5,….,99999,100000]
}
//print_r($result);
$value = current($result);//数组指针指向第一个位置,将值赋予$value
//echo $value.”\n”;
while ($value !== false) {//确认指针是否还没指向数组结尾的外部,如果指到了,说明已经没有要验证的值,可以结束了。其实用不到这个条件。
$result_copy = $result;//复制一个数组。因为使用foreach时改变数组长度是有风险的。复制的时候会同时复制数组内部指针。
$Prime = $value;//初始化时我们从2开始,2是个特别的质数。
if ($Prime > sqrt(max($result))) {
echo ‘break at $Prime ‘.$Prime.“\n”;//比当前质数平方还要大的值还没有被消除,那么他肯定是质数了。整个数组验证结束。
break;
}
//echo ‘While $Prime=’.$Prime.”\n”;
foreach ($result as $i => $value) {//循环数组
if ($value <= $Prime) {
continue;//假如循环到的数比正在准备的质数还小,就跳过。比如当我们判断数组中被3整除的数的时候,仍然会从第一个2开始,没有比较的必要,跳过。
}
$Modulo = $value % $Prime;
//echo $value.’ % ‘.$Prime.’ = ‘.$Modulo.”\n”;
if ($Modulo == 0) {
//echo ‘Number ‘.$value.’ is not Prime number’.”\n”;
unset($result_copy[$i]);//当我们判定此数会被质数整除时,从数组中剔除这个元素。注意剔除的是复制数组的。
}
}
//print_r($result_copy);
$result = $result_copy;//将被剔除元素的复制数组赋给原数组
//echo current($result).”\n”;
$value = next($result);//继续循环
}
$result = array_values($result);
//print_r($result);
echo count($result).“\n”;
|
上面的代码可能写的比较繁琐。如果要表达的简单一些,那么我们举例 $n = 30,则初始化后 $result 的样子是:
[2 3 5 7 9 11 13 15 17 19 21 23 25 27 29]
(注意这个时候php数组会变成关联数组,key将不是连续的,但不影响使用foreach或next等方法)
[2 3 5 7 11 13 17 19 23 25 29]
[ 2 3 5 7 11 13 17 19 23 29]
之后应该是 7 了,但是 7 的平方是 49 ,已经超出29了,所以判断结束。我们就能得到结果数组。
注意如果不使用 array_values 的话,php数组的key不是关联数组,其状态会是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Array
(
[0] => 2
[1] => 3
[3] => 5
[5] => 7
[9] => 11
[11] => 13
[15] => 17
[17] => 19
[21] => 23
[27] => 29
)
|
执行 array_values 之后才会变成:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
Array
(
[0] => 2
[1] => 3
[2] => 5
[3] => 7
[4] => 11
[5] => 13
[6] => 17
[7] => 19
[8] => 23
[9] => 29
)
|
注意: 在foreach循环时,unset数组是有风险的。
问题2
问题二就开始难办了。我们的筛选法是将已有范围内的不符合条件的值剔除,而会被剔除多少是不确定的,所以我们不能确定自定义范围内会被剔除多少,会剩余多少,想要得到N个质数就开始变得没办法了。
这个时候就捧出我也不理解的:素数定理。
最简单的公式:π(x)≈x/ln x,对正实数x,定义π(x)为素数计数函数,亦即不大于x的素数个数。
那么我们要获取一万个素数个数,x/ln x = 10000 他们的范围应该是 1 – x。虽然我不知道怎么算。这个方法是有误差的,在维基百科有误差表,可以根据其最大误差扩大计算范围,之后把多余的 array_splice 掉(用array_slice保留所需的也可以)。这里就不写代码了。
从 M 开始
求 从 M 到 N 之间所有质数 这种问题,对于传统试除法来讲,就是个扩展计算范围而已。
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
$n = 10000;
$result = array();
$next = 0;
for ($i = 1; $i <= $n; ++$i) {
for ($x = 2; $x <= sqrt($i); ++$x) {
$Modulo = $i % $x;
if ($Modulo == 0) {
//echo ‘Number ‘.$i.’ is not Prime number’.”\n”;
$flag = false;
break;
}
}
if ($flag) {
$result[] = $i;
}
$flag = true;
}
print_r($result);
echo count($result).“\n”;
|
虽然我们的新试除方法看起来更有效率,但是其从2开始的思路无法直接完成 求 从 M 到 N 之间所有质数 这种需求。唯一的解决办法就是,计算出 从 1 到 N 之间所有质数,再把小于 M 的全部删掉。
同理从 M 开始,求 N 个质数也会有这种问题。
而筛法则同样会面临同样的问题。越是有效率的方法,怎么感觉越是没法大范围使用?
工业用法
上面讲了那么多,其实都是理论派、学院派用法。实际工作中,对于质数这种永恒不变的数值,最快的方法,也是最简单高效的方法:查表
对,没看错,查表!
这就相当于你告诉一个不会乘法只会加法的小孩,告诉他 7*9 就是 7个9加到一起,他会7+7+7+7+7+7+7+7+7=63这么计算,而会乘法口诀之后,只要七九六十三,便能瞬间得到答案。
我们首先估算我们所需要的质数范围,之后使用筛法计算出所需要的质数范围,保存到固定文件中。当需要使用时,读取这个文件,再从文件中取出所需内容。这样四个问题都轻易解决:
我们得到足够大的质数表:primelist.json
- 求 从 M 到 N 之间所有质数
PHP
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
|
$m = 1;
$n = 20;
$json = file_get_contents(‘primelist.json’);
$data = json_decode($json, true);
$PrimeList = $data[‘result’];
//print_r($PrimeList);
$min_flag = false;
$max_flag = false;
$length = 0;
foreach ($PrimeList as $key => $value) {
//echo $value.”\n”;
if ($value >= $m && !$min_flag) {
$first = $key;
$min_flag = true;
}
if ($min_flag && $value <= $n) {
++$length;
}
if ($value > $n) {
break;
}
}
$result = array_slice($PrimeList, $first, $length);
print_r($result);
|
- 从 M 开始,求 N 个质数
PHP
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
|
$m = 1;
$n = 20;
$json = file_get_contents(‘primelist.json’);
$data = json_decode($json, true);
$PrimeList = $data[‘result’];
//print_r($PrimeList);
$min_flag = false;
$max_flag = false;
$length = $n;
foreach ($PrimeList as $key => $value) {
//echo $value.”\n”;
if ($value >= $m && !$min_flag) {
$first = $key;
$min_flag = true;
break;
}
}
echo $first.“\n”;
$result = array_slice($PrimeList, $first, $length);
print_r($result);
|
注意:以上代码均未进行过严密测试!所有边界值相关问题均未考虑!
请勿在生产环境中直接使用!
转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn