回答
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)
褶皱的粉丝会注意到这些是变形的:
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)
作为chipoints out,这种通用方法与使用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 的 answer 引用了 Okasaki 的 Breadth-First Numbering: Lessons from a Small Exercise in Algorithm Design,这是一篇出色的论文,它突出了许多函数式程序员的“盲点”,并为使抽象数据类型易于使用而不会被遗漏提供了很好的论据。但是,论文描述的问题比这个问题要复杂得多。这里不需要那么多机器。此外,该论文指出,在 ML 中,面向级别的解决方案实际上比基于队列的解决方案要快一些。我希望看到像 Haskell 这样的惰性语言会有更大的不同。
Jakub Daniel 的answer 尝试了面向级别的解决方案,但不幸的是遇到了效率问题。它通过重复将一个列表附加到另一个列表来构建每个级别,并且这些列表可能都具有相同的长度。因此,在最坏的情况下,如果我计算正确,则需要 O(n log n) 来处理带有 n 元素的树。
我选择的方法是面向级别的,但是通过将每个左子树传递到其右兄弟和表兄弟的级别来避免连接的痛苦。树的每个节点/叶子都只处理一次。该处理涉及O(1) 工作:在该节点/叶上进行模式匹配,如果它是一个节点,则在从右兄弟姐妹和堂兄弟派生的列表上进行模式匹配。因此,处理具有n 元素的树的总时间为O(n)。