[英]Is there a fast, functional prime generator?
假設我有一個自然數n
並且我想要一個直到n
的所有素數的列表(或其他)。
經典的素數篩選算法在O(n log n)
時間和O(n)
空間中運行——它適用於更多命令式語言,但需要以基本方式對列表和隨機訪問進行就地修改。
有一個涉及優先級隊列的功能版本,它非常巧妙——您可以在此處查看。 這在大約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)
純算法,我仍然會喜歡。
這是 Melissa O'Neill 算法的 Haskell 實現(來自鏈接文章)。 與 Gassa 鏈接的實現不同,我很少使用懶惰,因此性能分析很清楚—— O(n log n log log n) ,即,n log log 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)
在這里,如果(Haskell 的)純數組算作純數組(他們應該,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 ; 等等。 另見。
此外,基於 Python 生成器的篩選器也可能被認為是功能性的。 根據經驗,Python 的dict
非常好,盡管我不確定在那里使用的倍數過度生產方案的確切成本,以避免重復組合。
更新:正如預期的那樣,測試它確實產生了有利的結果:
{- 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
-}
and O(n log n log log n) as .使用經驗增長順序計算產生n 個素數,其中O(n log log n)通常被視為和O(n log n log log n)為 。
in the tested range). "nested-feed"變體實現了延遲技術(也可以在上面鏈接的 Python 答案中看到),它實現了堆大小的二次減小,這顯然對經驗復雜性有顯着影響,即使沒有完全達到更好的結果對於這個答案的基於數組的代碼,它能夠在 ideone.com 上在不到 10 秒的時間內生成1000 萬個素數(在測試范圍內,整體增長率僅為 )。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.