追踪一棵树?

时间:2015-02-13 20:51:25

标签: haskell

我有一个类似树的类型:

data Tree a = EmptyTree | Tree a [Tree a] deriving (Show, Ord, Eq)

freeTree :: Tree Integer  
freeTree =  Tree 2 [Tree 5 [], Tree 6 [Tree 8 [], Tree 9 []], Tree 7 []]

main = print freeTree

我要做的是编写一个可以像这样使用的函数:

trace freeTree

此树上的痕迹将返回:[2],[2,5],[2,6],[2,7],[2,6,8],[2,6,9]

基本上它的作用是:

保留已经在'堆栈上的节点列表。 (每个深度的根节点让我们来到这里)。每次到达新节点时,都会将一个列表作为堆栈节点列表++ current_node添加到结果列表中。

有人可以就如何做到这一点提出任何建议吗?

由于

3 个答案:

答案 0 :(得分:3)

第一个(不是真正有效的实施):

trace :: Tree a -> [[a]]
trace t = trace' [([],t)] []

type Level a = [([a],Tree a)]

trace' :: Level a -> Level a -> [[a]]
trace' [] [] = []                                         -- current and next level empty? We're done!
trace' [] l = trace' l []                                 -- current level exhausted? Next level becomes current level and we construct a new level
trace' ((_,EmptyTree):ts) lu = trace' ts lu               -- currently an EmptyTree? Skip it
trace' ((h,Tree t c):ts) lu = ht : trace' ts (lu++el)     -- currently a tree? Enumerate and add childs
    where ht = h++[t]
          el = map (\x -> (ht,x)) c

该算法使用两个Level a,即当前级别和下一级别。您始终首先在当前级别上迭代,并且对于当前级别中的每个项目,您将该级别的子级别添加到下一级别,直到当前级别用尽为止。这种方法的唯一问题是++操作非常昂贵,特别是因为它们应用于左关联而非右关联。通过使用更紧凑的元组列表表示,可以使内存效率更高一些。

您可以通过使用 FIFO队列来提高效率,例如this one(让我们假设至少所有队列的接口都相同,以防万一你更喜欢另一个,你可以交换位置。

在这种情况下,代码将为:

type Level a = [([a],Tree a)]
type LevelFiF a = FIFO ([a],Tree a)

trace' :: Level a -> LevelFiF a -> [[a]]
trace' [] ln | isEmpty ln = []
             | otherwise = trace' (toList ln) empty
trace' ((h,Tree t c):ts) ln = ht : trace' ts (foldl (flip enqueue) ln el)
    where ht = h++[t]
          el = map (\x -> (ht,x)) c
trace' (_:ts) ln = ht : trace' ts ln

你也可以使用Haskell的一个monadic队列来提高效率。

答案 1 :(得分:2)

我们可以认为我们有一个trie,其中每个节点都标记一个有效的单词,然后工作就是招募单词:

trace :: Tree a -> [[a]]
trace (Tree a ts) = [a] : map (a:) (trace =<< ts)
trace Empty       = []

tree1 = Tree 1 [Tree 2 [ Tree 3 [ Tree 4 [Tree 5 [] ]]]]
tree2 = Tree 2 [Tree 5 [], Tree 6 [Tree 8 [], Tree 9 []], Tree 7 []]

-- trace tree1 = [[1],[1,2],[1,2,3],[1,2,3,4],[1,2,3,4,5]]
-- trace tree2 = [[2],[2,5],[2,6],[2,6,8],[2,6,9],[2,7]]

这是一种深度优先的解决方案,可以通过仅对当前单词所需的空间进行延迟处理。它不会以您指定的完全相同的顺序返回单词;如果严格的广度优先顺序很重要,那么你应该进行迭代深化:

traceIDeep :: Tree a -> [[a]]
traceIDeep t = concat $ takeWhile (not . null) $ map (`lvl` t) [0..] 
  where
  lvl 0 (Tree a ts) = [[a]]
  lvl l (Tree a ts) = map (a:) (lvl (l - 1) =<< ts)
  lvl _ Empty       = []

-- Now we have bfs order:
-- trace tree2 = [[2],[2,5],[2,6],[2,7],[2,6,8],[2,6,9]]

答案 2 :(得分:0)

基本上,您希望在trace的递归调用中保持状态。所以考虑这样的函数签名:

-- First argument is the tree to trace
-- Second argument is the accumulated route
trace :: Tree a -> [a] -> [[a]]

通过这种结构,您可以有效地在此树上进行深度优先搜索。