一些定义:
提到的数都为正整数,区间都为整数区间。
为了代码简洁起见,使用Ruby语言。
首先介绍最简单的Eratosthenes筛。
令数组
# 输入
或者,用循环加法代替乘法:
$n
这是我们熟知的最简单的素数筛,每次筛去已知的素数的倍数。数组
分析一下它的复杂度。它需要为
这里用到了性质
实际上这里数组
一开始我们假设
然后逐个筛去合数,最终使得
对于任意一个素数
因此,一个素数筛可以写成
require
这里还定义了一个
对于不同的素数筛,归根结底只是上面代码中的两处注释位置的代码不同。即,只不过是不同的
Eratosthenes筛可以被优化至只需要
在Eratosthenes筛中,一个数
我们的想法是任一合数仅会被它最小的素因数
设将被筛去的合数
因此,我们对
那么如何计算
然而,计算交集并不快。这启发我们改变
class
这里
使用下面的代码,重定义
def
这样的数据结构需要的存储空间为
接下来我们计算
def
每次产出
有多种办法可以解决这一问题,其中一种办法就是一开始令
def
整合一下代码,我们得到
$n
程序需要的时间为
实际上算法需要的时间可以进一步降低。为了得到更优的算法,我们需要介绍轮子(wheel)的概念:
实际上
我们还需要以下性质:
证:显然素数阶乘具有性质
由
因此
因此
由此可以证明
证:
类似地,有
接下来我们介绍一种数据结构,它能让我们在
我们用一个长度为
为了建立这一数据结构,我们这样做:
- 算出前
个素数以及
;
- 对于每个
,令
;
- 令
;
- 从大到小地迭代每个
,过程中记录上个非零的
,每次遇到新的非零项时为它赋值为这两个非零项的
之差。
接下来我们介绍一种时间复杂度低于
- 选择尽可能大的
,使得
,算出前
个素数,并对于每个
计算
;
- 将
初始化为扩展至
的第
个轮子(实际上
以一个双向链表数组实现);
- 从
开始(即从
而不是
开始)使用之前介绍的筛法筛去
中的合数;
- 输出
。
注意到