所以本周我们在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函数,但仍然无法解决这个问题。
答案 0 :(得分:4)
由于节点没有父链接,因此一种方法是在列表中维护root-to-leaf路径。最后,可以使用左折叠来构造修改后的树:
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
答案 1 :(得分:2)
在这里,不要破坏任何东西,是一些"尾递归"用于沿左右分支求和的函数的定义,至少我理解"尾递归":
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
具有相同的答案 - 参数只是保持更好和更好的形状,直到它们处于理想的形状,{{ 1}},即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
相同 - 当我们来到它时,{{1} }}。我花了几次尝试才把正确的loop result Empty
错过的论点弄清楚,但是,像往常一样,一旦我得到它们就很明显了。
答案 2 :(得分:2)
正如其他人所暗示的那样,在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)可以将空案例与其余案例分开,以避免分配中间{{1构造函数。所以这个最终代码只分配堆栈帧来处理递归结果。由于这种堆栈的某些表示本质上是解决这个问题的必要条件,因此使用GHC的内置函数似乎很可能会产生最佳结果。