簡體   English   中英

在Haskell中使用尾遞歸拆分BinTree

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM