树遍历混乱?

时间:2016-11-24 00:46:34

标签: haskell tree

我有以下数据类型(来源:http://learnyouahaskell.com/zippers):

data Tree a = Empty | Node a (Tree a) (Tree a) deriving (Show, Eq, Ord)

然后我有以下函数,它遍历树并根据方向指令替换节点:

data Direction = L | R deriving (Show, Eq, Ord)
type Directions = [Direction]

changeNode :: Directions -> Tree Char -> Tree Char
changeNode (L : ds) (Node x l r) = Node x (changeNode ds l) r
changeNode (R : ds) (Node x l r) = Node x l (changeNode ds r)
changeNode [] (Node _ l r) = Node 'P' r l

但是我不了解这个功能的这个方面:

changeNode (L : ds) (Node x l r) = Node x (changeNode ds l) r

我看到这是使用递归(changeNode ds l),但我不明白为什么这是使用递归。

有没有人有简单的解释?

1 个答案:

答案 0 :(得分:6)

这可能不是您希望的简单解释,但尝试通过一个简短的例子可能会有所帮助。用铅笔和纸阅读以下内容,并尝试自己验证一切:

changeNode [L,R]
  (Node 'A' (Node 'B' Empty (Node 'C' Empty Empty)) (Node 'D' Empty Empty))

我假设你同意这个应该留在Node'B'分支然后直接进入Node'C'分支,最后用'P'代替'C' ,对吗?

它是如何做到的?好吧,上面的表达式匹配changeNode的第一个模式,即类型声明之后的第一个模式。具体来说,它与变量赋值匹配:ds = [R],x ='A',l =整个节点'B'分支,r =整个节点'D'分支。因此,我们可以使用与该匹配模式相对应的右侧来重写它。该模式的右侧是:

Node x (changeNode ds l) r

并替换匹配的变量给出:

Node 'A' (changeNode [R] (Node 'B' Empty (Node 'C' Empty Empty)))
         (Node 'D' Empty Empty)    -- (1)

现在你看到发生了什么? changeNode的第一个模式通过“消耗”方向列表的第一个字母(已从[L,R]更改为[R])并将changeNode调用向下推入左分支来进行操作。

现在,请专注于此递归changeNode调用的值。这次它匹配changeNode的第二个模式(ds = [],x ='B',l = Empty,r =(Node'C'Emp empty Empty))。该模式的RHS是:

Node x l (changeNode ds r)

变为(带有适当的减法):

Node 'B' Empty (changeNode [] (Node 'C' Empty Empty))

并将此值替换回第(1)行,我们得到:

Node 'A' (Node 'B' Empty (changeNode [] (Node 'C' Empty Empty)))
         (Node 'D' Empty Empty)   -- (2)

再次,看看第二次调用如何消耗方向向量中的“R”并将changeNode调用推送到节点“B”的右侧分支。最后,这最后一次递归changeNode调用的价值是什么?好吧,它匹配第三个模式,l = Empty,r = Empty,给出RHS值:

Node 'P' Empty Empty

并代入第(2)行,我们得到:

Node 'A' (Node 'B' Empty (Node 'P' Empty Empty)) (Node 'D' Empty Empty)

这正是我们想要的。

将所有这些与如果定义是非递归的将会发生的情况进行对比:

changeNode' :: Directions -> Tree Char -> Tree Char
changeNode' (L : ds) (Node x l r) = Node x l r
changeNode' (R : ds) (Node x l r) = Node x l r
changeNode' [] (Node _ l r) = Node 'P' r l

在这种情况下,我们的简单示例将再次匹配第一个模式,ds = [R],x ='A',l =所有Node'B'分支,r =所有Node'D'分支,但我们不是使用第(1)行,而是使用非递归右侧“Node xlr”代替第(1)行来获取以下内容:

Node 'A' (Node 'B' Empty (Node 'C' Empty Empty)) (Node 'D' Empty Empty)

请参阅?没有递归调用,在changeNode'消耗'L'之后,它就完成了。它返回原始树而不进行进一步处理。需要递归调用以保持进程一直移动直到方向向量为空,并且第三个模式(实际更改节点的唯一模式)可以应用于树中的正确位置。

所以,简短的解释(直到你完成上面的例子才真正有意义)是前两个用于changeNode的递归模式用于将changeNode调用通过树结构移动到最终应用最终模式的目标节点,以更改节点值。