[英]Splitting a BinTree with tail recursion in Haskell
所以本周我們在Haskell中了解了聯合類型,尾遞歸和二叉樹。 我們定義了我們的樹數據類型:
data BinTree a = Empty
| Node (BinTree a) a (BinTree a)
deriving (Eq, Show)
leaf :: a -> BinTree a
leaf x = Node Empty x Empty
現在我們被要求編寫一個函數來查找最左邊的節點,返回它,將其剪切掉,並返回剩下的樹而沒有我們剛剪切的節點。
我們做了類似的事情,效果很好:
splitleftmost :: BinTree a -> Maybe (a, BinTree a)
splitleftmost Empty = Nothing
splitleftmost (Node l a r) = case splitleftmost l of
Nothing -> Just (a, r)
Just (a',l') -> Just (a', Node l' a r)
現在我需要使這個函數尾遞歸。 我想我理解尾遞歸是什么,但發現很難將它應用於這個問題。 我被告知編寫一個函數,它使用擬合參數調用main函數,但仍然無法解決這個問題。
由於節點沒有父鏈接,因此一種方法是在列表中維護根到葉路徑。 最后,可以使用左折疊構造修改后的樹:
slm :: BinTree a -> Maybe (a, BinTree a)
slm = run []
where
run _ Empty = Nothing
run t (Node Empty x r) = Just (x, foldl go r t)
where go l (Node _ x r) = Node l x r
run t n@(Node l _ _) = run (n:t) l
這里,不要破壞任何東西,是一些“尾遞歸”的函數定義,用於沿左右分支求和,至少我理解“尾遞歸”:
sumLeftBranch tree = loop 0 tree where
loop n Empty = n
loop n (Node l a r) = loop (n+a) l
sumRightBranch tree = loop 0 tree where
loop n Empty = n
loop n (Node l a r) = loop (n+a) r
你可以看到循環的所有遞歸使用將與第一個調用loop 0 tree
具有相同的答案 - 參數只是保持更好和更好的形狀,直到它們處於理想的形狀, loop n Empty
,這是n
,期望的總和。
如果這是想要的東西,那么splitleftmost
的設置就是
splitLeftMost tree = loop Nothing tree
where
loop m Empty = m
loop Nothing (Node l a r) = loop ? ?
loop (Just (a',r')) (Node l a r) = loop ? ?
這里, loop
的第一個用途是loop Nothing tree
的形式,但是這與loop result Empty
相同 - 當我們來到它時,即result
。 我花了幾次嘗試讓缺少的參數loop ? ?
loop ? ?
是的,但是,像往常一樣,一旦我拿到它們,它們就很明顯了。
正如其他人所暗示的那樣,在Haskell中沒有理由使這個函數尾遞歸。 事實上,尾遞歸解決方案幾乎肯定會慢於你設計的解決方案! 您提供的代碼中潛在的低效率主要涉及對和Just
構造函數的分配。 我相信GHC(啟用優化)將能夠弄清楚如何避免這些。 我的猜測是它的最終代碼可能看起來像這樣:
splitleftmost :: BinTree a -> Maybe (a, BinTree a)
splitleftmost Empty = Nothing
splitleftmost (Node l a r) =
case slm l a r of
(# hd, tl #) -> Just (hd, tl)
slm :: BinTree a -> a -> BinTree a
-> (# a, BinTree a #)
slm Empty a r = (# a, r #)
slm (Node ll la lr) a r =
case slm ll la lr of
(# hd, tl' #) -> (# hd, Node tl' a r #)
那些看起來很滑稽的(# ..., ... #)
東西是未裝箱的對 ,它們的處理方式與多個返回值非常相似。 特別是,直到結束才分配實際的元組構造函數。 通過識別每個使用非空樹的splitleftmost
調用將產生Just
結果,我們(因此幾乎肯定GHC)可以將空案例與其余案件分開,以避免分配中間Just
構造函數。 所以這個最終代碼只分配堆棧幀來處理遞歸結果。 由於這種堆棧的某些表示本身就是解決這個問題的必要條件,因此使用GHC內置的堆棧似乎很可能會產生最佳結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.