假设我有一个自然数n
,我希望列出(或其他)所有素数的列表(或其他)n
。
经典的筛选算法在O(n log n)
时间和O(n)
空间运行 - 对于更多命令式语言而言它很好,但需要对列表和随机访问进行就地修改,基本方式。
这是一个涉及优先级队列的功能版本,非常漂亮 - 您可以查看here。这在大约O(n / log(n))
处具有更好的空间复杂度(渐近更好但在实际尺度上存在争议)。不幸的是,时间分析是令人讨厌的,但它几乎O(n^2)
(实际上,我认为它与O(n log(n) Li(n))
相关,但log(n) Li(n)
约为n
)。
渐近地说,使用连续的试验除法,在生成它时检查每个数字的素数实际上会更好,因为这只需要O(1)
空格和O(n^{3/2})
时间。还有更好的方法吗?
编辑:事实证明我的计算完全不正确。文章中的算法是O(n (log n) (log log n))
,文章解释并证明(并参见下面的答案),而不是我上面提到的复杂的混乱。如果有一个真正的算法,我仍然很乐意看到真正的O(n log log n)
纯算法。
答案 0 :(得分:3)
这是Melissa O'Neill算法的Haskell实现(来自链接文章)。与Gassa链接的实现不同,我最少使用了懒惰,因此性能分析很明确 - O(n log n log log n),即n log log中的linearithmic n,Eratosthenes命令筛选的写入次数。
堆实现只是一个锦标赛树。平衡逻辑在push
;通过每次交换子进程,我们确保对于每个分支,左子树的大小相同或者与右子树相比更大,这确保了深度O(log n)。
module Sieve where
type Nat = Int
data Heap = Leaf !Nat !Nat
| Branch !Nat !Heap !Heap
deriving Show
top :: Heap -> Nat
top (Leaf n _) = n
top (Branch n _ _) = n
leaf :: Nat -> Heap
leaf p = Leaf (3 * p) p
branch :: Heap -> Heap -> Heap
branch h1 h2 = Branch (min (top h1) (top h2)) h1 h2
pop :: Heap -> Heap
pop (Leaf n p) = Leaf (n + 2 * p) p
pop (Branch _ h1 h2)
= case compare (top h1) (top h2) of
LT -> branch (pop h1) h2
EQ -> branch (pop h1) (pop h2)
GT -> branch h1 (pop h2)
push :: Nat -> Heap -> Heap
push p h@(Leaf _ _) = branch (leaf p) h
push p (Branch _ h1 h2) = branch (push p h2) h1
primes :: [Nat]
primes
= let helper n h
= case compare n (top h) of
LT -> n : helper (n + 2) (push n h)
EQ -> helper (n + 2) (pop h)
GT -> helper n (pop h)
in 2 : 3 : helper 5 (leaf 3)
答案 1 :(得分:2)
在这里,如果(Haskell' s)纯数组计为纯(他们应该,IMO)。复杂性显然是 O(n log(log n)),前提是accumArray
确实为每个索引提供了 O(1)时间,因为它应该:
import Data.Array.Unboxed
import Data.List (tails, inits)
ps = 2 : [n | (r:q:_, px) <- (zip . tails . (2:) . map (^2)) ps (inits ps),
(n,True) <- assocs (
accumArray (\_ _ -> False) True (r+1,q-1)
[(m,()) | p <- px, let s=(r+p)`div`p*p,
m <- [s,s+p..q-1]] :: UArray Int Bool )]
按素数的连续平方(map (^2)
位)之间的段计算素数,通过枚举素数的增长前缀的倍数(inits
位)生成复合,就像任何适当的Eratosthenes筛子一样会反复添加。
因此,素数 {2,3} 用于筛选从 10 到 24 的片段; {2,3,5} 从 26 到 48 ;等等。 See also
此外,Python generator-based sieve也可能被视为功能。 Python的dict
表现非常出色,empirically,虽然我不确定那里使用的倍数过量生成方案的确切成本,以避免重复复合。< / p>
更新: testing it确实产生了有利的结果,如预期的那样:
{- original heap tweaked nested-feed array-based
(3*p,p) (p*p,2*p) JBwoVL abPSOx
6Uv0cL 2x speed-up another 3x+ speed-up
n^ n^ n^ n^
100K: 0.78s 0.38s 0.13s 0.065s
200K: 2.02s 1.37 0.97s 1.35 0.29s 1.16 0.13s 1.00
400K: 5.05s 1.32 2.40s 1.31 0.70s 1.27 0.29s 1.16
800K: 12.37s 1.29 1M: 2.10s 1.20 0.82s 1.13
2M: 1.71s 1.06
4M: 3.72s 1.12
10M: 9.84s 1.06
overall in the tested range:
1.33 1.21 1.09
-}
用empirical orders of growth计算生成 n 素数,其中 O(n log log n)通常被视为 n 1.05。 ..1.10 和 O(n log n log log n) as n 1.20 ... 1.25 。
"nested-feed"变体实现了postponement技术(如上面链接的Python answer中所见),它实现了堆大小的二次减少,这显然对经验有显着影响复杂性,即使没有达到这个答案的基于数组的代码仍然更好的结果,在{ides.com.com上10秒内is able to produce 1000万个素数(总体增长率仅为 n < sup> 1.09 在测试范围内)。
("original heap"当然是the other answer的代码)。
答案 2 :(得分:0)