答案
levels :: Tree a -> [[a]]
levels t = levels' t []
levels' :: Tree a -> [[a]] -> [[a]]
levels' EmptyT rest = rest
levels' (NodeT a l r) [] = [a] : levels' l (levels r)
levels' (NodeT a l r) (x : xs) = (a : x) : levels' l (levels' r xs)
一种稍微复杂但更加懒惰的实现方式是 levels'
:
levels' EmptyT rest = rest
levels' (NodeT a l r) rest = (a : front) : levels' l (levels' r back)
where
(front, back) = case rest of
[] -> ([], [])
(x : xs) -> (x, xs)
喜欢函数式编程的人会注意到这些都是以catamorphism形式结构化的:
cata :: (a -> b -> b -> b) -> b -> Tree a -> b
cata n e = go
where
go EmptyT = e
go (NodeT a l r) = n a (go l) (go r)
levels t = cata br id t []
where
br a l r rest = (a : front) : l (r back)
where
(front, back) = case rest of
[] -> ([], [])
(x : xs) -> (x, xs)
正如chi指出的那样,这种通用方法与使用Jakub Daniel的解决方案并以差异列表作为中间形式的结果似乎存在某种联系。这可能看起来像:
import Data.Monoid
levels :: Tree a -> [[a]]
levels = map (flip appEndo []) . (cata br [])
where
br :: a -> [Endo [a]] -> [Endo [a]] -> [Endo [a]]
br a l r = Endo (a :) : merge l r
merge :: Monoid a => [a] -> [a] -> [a]
merge [] ys = ys
merge (x : xs) ys = (x <> y) : merge xs ys'
where
(y,ys') =
case ys of
[] -> (mempty, [])
p : ps -> (p, ps)
我不确定这种方法与更直接的方法相比如何。
讨论
Kostiantyn Rybnikov的答案引用了Okasaki的《广度优先编号:从算法设计的小练习中获得的教训》,这是一篇很好的论文,强调了许多函数式程序员的“盲点”,并提出了使抽象数据类型易于使用的良好论据,以便它们不会被忽略。然而,这篇论文描述的问题比这个问题复杂得多;这里需要的机器不是那么多。此外,该论文指出,在ML中,面向级别的解决方案实际上比基于队列的解决方案稍微快一些;在Haskell等惰性语言中,我希望看到更大的差异。
Jakub Daniel的答案尝试了一个面向级别的解决方案,但不幸的是存在效率问题。它通过反复将一个列表附加到另一个列表来构建每个级别,这些列表可能都具有相等的长度。因此,在最坏的情况下,如果我计算正确,处理具有n
个元素的树需要O(n log n)
的时间。
我选择的方法是面向级别的,但通过将每个左子树传递给其右兄弟和堂兄弟的级别来避免了连接的痛苦。树的每个节点/叶子仅处理一次。该处理涉及O(1)
的工作:对该节点/叶子进行模式匹配,并且如果它是一个节点,则对从右兄弟和堂兄弟派生的列表进行模式匹配。因此,处理具有n
个元素的树的总时间为O(n)
。
level
函数接受一棵树并返回一个差异列表?如果是这样,那么使用DList
进行所有连接是否比使用 Jakub 的答案更有效率? - chi