筛选方法
1. 简单筛选法
回顾前面我们对判断是否为质数的算法所作的最后的改进:如果一个数没有小于它本身的质因子,那么这个数就是质数。不妨从另一个角度考虑,如果我们知道了一些质数,那么显然,这些质数的2倍,3倍,4倍……都不会是质数,我们就可以将这些2倍,3倍,4倍……的数字“筛”去,这就是解本题的另一种方法——“筛选法”。
大致步骤如下:
(1)当前最大的质数X为2,可能为质数的集合S为[3..10000]。
(2)将X的2倍,3倍,4倍……数字从集合S中删去。
(3)如果集合S为空,则结束;否则转到(4)。
(4)当前最大质数X的值更新为当前集合S中的最小值,返回(2)。
显然,每次从集合S中选取的最小值必定为质数,X的值逐渐增大,集合S逐渐减小。由于集合操作速度慢,且存储量有限,因此,在程序实现中,我们用
ok : array[2..10000] of boolean
来代替集合S,ok[i]=false表示i不是质数。
对于“筛选法”的改进,主要也体现在两个方面——枚举要筛选的质数和筛选的过程。
2. 对要筛选的质数范围的改进
假设要求的质数上限为N,那么显而易见,根据“枚举法”的一个结论,即一个数若不是质数,则必然含有小于 的质因子。因此,“筛选法”所要枚举的质数仅仅限于 的范围内,对于其他大于 的质数,只需打印,无须进行“筛选”。
事实上,本次改进的效果并不是特别明显,因为,大于 的质数即使进行筛选,由于其本身数值大,所以在N范围内可被筛选的数并不是很多。
3. 对筛选过程的改进
我们注意到,所有质数,除2以外,都是奇数。换句话说,所有偶数,除2以外,都不是质数。
应用这个性质,我们将2作为特例进行特殊处理后,每次用求得的一个质数(必然为奇数)进行筛选时,不要2倍,3倍,4倍……的筛选了,只要3倍,5倍,7倍……筛选即可(因为奇数×偶数=偶数)。如此,“筛选法”效率从理论上就提高了1倍!
至此,“筛选法”已经被改进得非常优秀了。事实上,“枚举法”和“筛选法”的改进之外还有许多,但编程的复杂度也会随之不断上升!(在竞赛中,求质数仅仅可能是某道题目的一个部分,应用上面的方法就已经足够了)
三、枚举法与筛选法的比较
这样,我们求质数时就有了两种不同的方法:“枚举法”和“筛选法”。现在,让我们从理论上来比较它们的运行效率。
“枚举法”对于每个数都要枚举所有小于该数平方根的质数来判断,并根据最后改进的程序运行,比较次数见下表。
上限N | 1000 | 10000 | 100000 | 500000 | 1000000 |
判断次数 | 1804 | 33756 | 644439 | 5209011 | 12927405 |
比例 | 1﹕1.8 | 1﹕3.4 | 1﹕6.4 | 1﹕10 | 1﹕12 |
运行时间 | 0.01s | 0.01s | 0.50s | 4.90s | 10.05s |
我们看到,随着N的增加,判断次数的增加将是非常惊人的。
“筛选法”是对每个质数(奇数)计算其倍数来筛选。同样根据“筛选法”最后改进的程序运行,“筛选”次数即赋值false的次数,见下表。
上限N | 1000 | 10000 | 100000 | 500000 | 1000000 |
判断次数 | 457 | 5995 | 71556 | 391987 | 811068 |
比例 | 1﹕0.46 | 1﹕0.60 | 1﹕0.72 | 1﹕0.78 | 1﹕0.81 |
运行时间 | 0.01s | 0.01s | 0.11s | 0.38s | 0.77s |
可见,“筛选法”的效率要大大优于“枚举法”。
虽然在时间效率上,“筛选法”比“枚举法”要强许多,但是,大家可能会发现一个问题,即当上限N非常大的时候,“筛选法”的空间如何能够存储下(需要开一个1~N的boolean数组)呢?其实这是可以解决的。
在所开的boolean数组中,只需要记录下奇数的情况,这样原本需筛去数组中a值的3,5,7,……倍,而新数组中a=2*i+1,其所处的位置位于i=(a-1) div 2,而其3倍的值则位于(3*a-a) div 2+i=a+i,同理,其5,7,……倍的值则分别位于2*a+i,3*a+i,……的位置,这样可以节约一半的空间。