如何在Haskell中表示共享树

时间:2011-12-07 07:24:46

标签: haskell functional-programming

我想在Haskell中表示以下形状的“树”:

   /\                            
  /\/\
 /\/\/\
/\/\/\/\
` ` ` ` `

/和\是分支和`叶子。您可以看到从左侧路径开始的任何节点开始,然后右侧将您带到与右侧路径相同的节点,然后是左侧。您应该能够标记叶子,在每个节点应用两个后代的函数,并在O(n ^ 2)时间内将此信息传播到根。我天真的努力给了我一个指数的运行时间。任何提示?

2 个答案:

答案 0 :(得分:20)

使用共享节点构建树当然是可能的。例如,我们可以定义:

data Tree a = Leaf a | Node (Tree a) (Tree a)

然后在

中仔细构造此类型的值
tree :: Tree Int
tree = Node t1 t2
  where
    t1 = Node t3 t4
    t2 = Node t4 t5
    t3 = Leaf 2
    t4 = Leaf 3
    t5 = Leaf 5

实现子树的共享(在本例中为t4)。

然而,由于这种共享形式在Haskell中是不可观察的,因此很难维护:例如,如果你遍历一棵树来重新标记它的叶子

relabel :: (a -> b) -> Tree a -> Tree b
relabel f (Leaf x) = Leaf (f x)
relabel f (Node l r) = Node (relabel f l) (relabel f r)
你松散分享了。此外,在进行自下而上的计算时,例如

sum :: Num a => Tree a -> a
sum (Leaf n) = n
sum (Node l r) = sum l + sum r

你最终没有利用共享和可能重复的工作。

要克服这些问题,您可以通过以类似图形的方式对树进行编码来使共享显式(因此可观察):

type Ptr = Int
data Tree' a = Leaf a | Node Ptr Ptr
data Tree a = Tree {root :: Ptr, env :: Map Ptr (Tree' a)}

上面示例中的树现在可以写为

tree :: Tree Int
tree = Tree {root = 0, env = fromList ts}
  where
    ts = [(0, Node 1 2), (1, Node 3 4), (2, Node 4 5),
          (3, Leaf 2), (4, Leaf 3), (5, Leaf 5)]

支付的代价是遍历这些结构的函数编写起来有些麻烦,但我们现在可以定义一个保留共享的重新标记函数

relabel :: (a -> b) -> Tree a -> Tree b
relabel f (Tree root env) = Tree root (fmap g env)
  where
    g (Leaf x)   = Leaf (f x)
    g (Node l r) = Node l r

sum函数,当树具有共享节点时不会重复工作:

sum :: Num a => Tree a -> a
sum (Tree root env) = fromJust (lookup root env')
  where
    env' = fmap f env
    f (Leaf n) = n
    f (Node l r) = fromJust (lookup l env') + fromJust (lookup r env')

答案 1 :(得分:2)

也许你可以简单地将它表示为叶子列表并逐级应用函数,直到你达到一个值为止,即:

type Tree a = [a]

propagate :: (a -> a -> a) -> Tree a -> a
propagate f xs =
  case zipWith f xs (tail xs) of
    [x] -> x
    xs' -> propagate f xs'