[英]Slow solution to project euler 25 using haskell
我是 Haskell 語言的新手,為了學習我想我會跳進一些歐拉項目。 在Project Euler 25中,我們的任務是執行以下操作:
第 12 項 F12 是第一個包含三位數字的項。 斐波那契數列中包含 1000 位數字的第一項的索引是多少?
這是我對問題的解決方案:
fibGen :: Int -> Int
fibGen 0 = 0
fibGen 1 = 1
fibGen n = fibGen (n-1) + fibGen (n-2)
stepper n
| length (show ( fibGen n )) >= 1000 = n
| otherwise = stepper n + 1
這里n
只是序列的起點。 但是這種方法非常慢,在我決定嘗試另一種方法之前已經運行了一個多小時。 然后我找到了另一個解決方案,如下所示:
fibs = 0:1:(zipWith (+) fibs (tail fibs))
t = 10^999
problem_25 = length w
where
w = takeWhile (< t) fibs
這非常快。
所以我的問題是第一種方法有什么問題導致它如此緩慢。
所以我的問題是第一種方法有什么問題導致它如此緩慢。
您的第一種方法在stepper
的定義中有一個無限循環,但是,即使沒有無限循環,由於指數分支策略,它也需要相當長的時間。
您的第一種方法導致指數遞歸。 實際上,除了兩個基本情況,所有其他情況都會導致兩個額外的調用:
fibGen :: Int -> Int
fibGen 0 = 0
fibGen 1 = 1
fibGen n = fibGen (n-1) + fibGen (n-2)
所以這意味着以fibGen 5
為例,我們將評估為:
fibGen 5
- fibGen 4
- fibGen 3
- fibGen 2
- fibGen 1
- fibGen 0
- fibGen 1
- fibGen 2
- fibGen 1
- fibGen 0
- fibGen 3
- fibGen 2
- fibGen 1
- fibGen 0
- fibGen 1
因此,為了計算fibGen 5
,我們將總共進行 15 次調用。 一個用於fibGen 4
,兩個用於fibGen 3
,三個用於fibGen 2
,五個用於fibGen 1
,三個用於fibGen 0
。
每次我們增加n
時,我們的調用量幾乎會翻倍。 很明顯,對於一個大的n
,調用的數量是巨大的,現代機器仍然無法處理它。
此外,您的stepper
function 被定義為無限循環。 實際上,您的 function 的一個更詳細的變體是:
stepper n
| length (show ( fibGen n )) >= 1000 = n
| otherwise = (stepper n) + 1
所以這意味着如果你計算stepper n
,並且約束失敗,你再次調用stepper n
,然后你會在這個結果上加1
,但是你會陷入一個無限循環。
您可以通過添加括號來解決此問題:
stepper n
| length (show ( fibGen n )) >= 1000 = n
| otherwise = stepper (n + 1)
現在最終程序將終止,但由於遞歸定義中的分支,這將花費很多時間。 請注意,每次調用fibGen
時,它都會再次分支,這意味着即使我們修復了無限循環,如果我們調用fibGen 5
,那么如果我們稍后調用fibGen 6
,我們將再次完成所有工作來計算fibGen 5
計算fibGen 6
。 因此,我們在這里不使用記憶。
另一方面,您的第二個斐波那契程序會生成一個列表,並重用列表中的結果。 因此fib
將評估為:
0 : 1 : zipWith …
-> 0 : 1 : 1 : zipWith …
-> 0 : 1 : 1 : 2 : zipWith …
-> 0 : 1 : 1 : 2 : 3 : zipWith …
-> 0 : 1 : 1 : 2 : 3 : 5 : zipWith …
所以這不會受到分支的影響,因為它會重用列表中已經存在的結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.