以特定方式展平二叉树

时间:2019-02-18 10:04:21

标签: haskell recursion tree binary-tree

请考虑以下对二进制树和一元树的定义,一个函数flatten,它将二进制树和一元树转换为列表(例如flatten (Node (Leaf 10) 11 (Leaf 20))[10,11,20])和一个函数{{ 1}},它将列表转换为二叉树(按照此处描述的特定方式(Defining a function from lists to binary and unary trees),并在下图中说明):

reverseflatten

data Tree a = Leaf a | Node (Tree a) a (Tree a) | UNode a (Tree a) deriving (Show) flatten :: Tree a -> [a] flatten (Leaf x) = [x] flatten (Node l x r) = flatten l ++ [x] ++ flatten r flatten (UNode l x) = [l] ++ flatten x reverseflatten :: [a] -> Tree a reverseflatten [x] = (Leaf x) reverseflatten [x,y] = UNode x (Leaf y) reverseflatten [x,y,z] = Node (Leaf x) y (Leaf z) reverseflatten (x:y:xs) = revflat2 (x:y:xs) revflat2 :: [a] -> Tree a revflat2 [x] = (Leaf x) revflat2 [x,y] = UNode y (Leaf x) revflat2 [x,y,z] = Node (Leaf x) y (Leaf z) revflat2 (x:y:xs) = Node (Leaf x) y (revflat2 ([head $ tail xs] ++ [head xs] ++ tail (tail xs))) reverseflatten [1..5],但是Node (Leaf 1) 2 (Node (Leaf 4) 3 (Leaf 5)的返回值与(reverseflatten(flatten(reverseflatten [1..5])))相同。如何修改reverseflatten [1..5],使flattenreverseflatten x: xs相同?

(reverseflatten(flatten(reverseflatten x:xs)))在下图中形成了一系列树。 例如,图片中的reverseflatten构成树2,reverseflatten [x,y,z]构成树3,reverseflatten [x,y,z, x']构成树4,reverseflatten [x,y,z, x', y']构成树5,reverseflatten [x,y,z, x', y', z']构成树6等等。 Series of trees generated by <code>reverseflatten</code>

我想要的是reverseflatten [x,y,z, x', y', z', x'']reverseflatten x: xs相同。因此,我需要设计(reverseflatten(flatten(reverseflatten x:xs)))才能起到这种作用。

我做了以下尝试(假设情况flatten分为flatten Node l x r是叶子的情况和不是情况):

r

但这会产生:

flatten :: Tree a -> [a]
flatten (Leaf x) = [x] 
flatten (UNode l x) = [l] ++ flatten x 
flatten (Node l x r)
    | r == Leaf y   = [l, x, r]  
    | otherwise = flatten (Node l x (revflat2 ([head $ tail r] ++ [head r]     ++ tail (tail r)))

3 个答案:

答案 0 :(得分:3)

我认为您的问题是树的第一个节点与其他节点没有相同的模式,就像您查看Tree1时,它进入[x,y,z],而Tree4中则进入[x,y ,[x',z,y']]。

您可以看到子节点的排序不符合第一个子节点的排序,这就是为什么有些人指出它感觉不自然的原因。要解决此问题,您可以将reverseFlattening的定义更改为具有恒定模式(我认为您不想要)的定义,或者更改展平图以考虑此奇怪的模式:

data Tree a = Leaf a | Node (Tree a) a (Tree a) | UNode a (Tree a) deriving (Show)

reverseFlatten :: [a] -> Tree a
reverseFlatten [x] = (Leaf x)
reverseFlatten [x,y] = UNode y (Leaf x)
reverseFlatten [x,y,z] = Node (Leaf x) y (Leaf z)
reverseFlatten (x:y:xs) = Node (Leaf x) y (reverseFlatten ((xs !! 1) : (head xs) : (drop 2 xs)))

flatten :: Tree a -> [a]
flatten (Leaf x)            = [x]
flatten (UNode l (Leaf x))  = [l,x]
flatten (Node (Leaf l) x r) = l : x : flattenRest r

flattenRest :: Tree a -> [a]
flattenRest (Leaf x)            = [x]
flattenRest (UNode l (Leaf x))  = [l,x]
flattenRest (Node (Leaf l) x r) = x : l : flattenRest r

请注意,我扩展了UNode和左侧节点的模式匹配,因为您已经知道它将是一棵左侧的树,因此如果您已经知道结果将是什么,则无需调用函数。

答案 1 :(得分:1)

可测试的规范

首先,我们可以将您的规范reverseflatten (flatten (reverseflatten (x : xs))) = reverseflatten (x : xs)作为QuickCheck属性来实现。

  • 我们通过flattenreverseflatten对其进行参数化,因此很容易插入不同的实现。

  • 我们将元素类型专用于Int,因为我们必须告诉QuickCheck在某个时候生成什么。

  • 类型变量a的真正含义是Tree Int,但泛型将在以后有用。

import Test.QuickCheck

prop_flat :: (Eq a, Show a) =>
             (a -> [Int]) -> ([Int] -> a) -> (Int, [Int]) -> Property
prop_flat f rf (x0, xs0) =
    (rf . f . rf) xs === rf xs
  where
    xs = x0 : xs0

-- Also remember to derive both Show and Eq on Tree.

通过将其应用于错误的实现,我们可以检查它是否具有重要意义。

ghci> quickCheck $ prop_flat flatten reverseflatten
*** Failed! Falsifiable (after 5 tests and 8 shrinks):    
(0,[0,0,1,0])
Node (Leaf 0) 0 (Node (Leaf 0) 1 (Leaf 0)) /= Node (Leaf 0) 0 (Node (Leaf 1) 0 (Leaf 0))

扁平,首先服用

现在flatten的实现需要分为两个阶段,例如reverseflatten,因为根的行为不同于其他节点:

  • 在根Node (Leaf x) y (Leaf z)[x, y, z]

  • 但在内部节点中,Node (Leaf x) y (Leaf z)[y, x, z]

还要注意,您显示的所有树以及可以由reverseflatten实际生成的树都向右倾斜,因此我们实际上只知道对模式Leaf x,{{ 1}}和UNode x (Leaf y),但不能使用其他模式,例如Node (Leaf x) y rUNode x (Node ...)。因此,考虑到Node (Node ...) y r的整个领域,Tree是非常不完整的:

flatten1

尽管存在偏见,但QuickCheck同意:

flatten1 :: Tree a -> [a]
flatten1 (Leaf x) = [x]
flatten1 (UNode x (Leaf y)) = [x, y]
flatten1 (Node (Leaf x) y r) = x : y : flatten1' r

flatten1' :: Tree a -> [a]
flatten1' (Leaf x) = [x]
flatten1' (UNode x (Leaf y)) = [x, y]
flatten1' (Node (Leaf y) x r) = x : y : flatten1' r

平台,总版本

通过稍微概括一下模式就可以获得全部功能,但是如上面的测试所示,该规范并未涵盖这些额外情况。每当我们在嵌套的ghci> quickCheck $ prop_flat flatten1 reverseflatten +++ OK, passed 100 tests. 上进行模式匹配时,我们只会获取整棵树Leaf y并将其展平。如果确实是ys,则它将被展平为一个单例列表,因此保留了原始语义。

ys = Leaf y

平版,完全指定的版本

我们可以在其域的未指定部分上任意泛泛地泛泛功能,我们还可以限制其域以使其与规范完全匹配。这导致了另一种类型定义:在所有示例中,flatten2 :: Tree a -> [a] flatten2 (Leaf x) = [x] flatten2 (UNode x ys) = x : flatten2 ys flatten2 (Node xs y r) = flatten2 xs ++ y : flatten2' r flatten2' :: Tree a -> [a] flatten2' (Leaf x) = [x] flatten2' (UNode x ys) = x : flatten2' ys flatten2' (Node ys x r) = x : flatten2' ys ++ flatten2' r 仅具有叶子子树,而类似的UNode仅具有叶子作为左子树,因此我们将这些叶子解压缩到构造函数中。

Node

data Tree' a = Leaf' a | UNode' a a | Node' a a (Tree' a) deriving (Eq, Show) 的实现是flatten'的直接改编:

flatten1

flatten' :: Tree' a -> [a] flatten' (Leaf' x) = [x] flatten' (UNode' x y) = [x, y] flatten' (Node' x y r) = x : y : f'' r f'' :: Tree' a -> [a] f'' (Leaf' x) = [x] f'' (UNode' x y) = [x, y] f'' (Node' x y r) = y : x : f'' r 类似地改编自reverseflatten'的重构版本。

reverseflatten

QuickCheck验证:

reverseflatten' :: [a] -> Tree' a
reverseflatten' (x : []) = Leaf' x
reverseflatten' (x : y : []) = UNode' x y
reverseflatten' (x : y : z : r) = Node' x y (rf'' z r)

rf'' :: a -> [a] -> Tree' a
rf'' x [] = Leaf' x
rf'' x (y : []) = UNode' x y
rf'' x (y : z : r) = Node' y x (rf'' z r)

答案 2 :(得分:1)

让我们假设一个稍微强一些的属性,只是不加思索地进行计算,看看它能为我们带来什么。也就是说,更强大的属性是,只要xs不为空,我们就有:

flatten (reverseflatten xs) = xs

根据reverseflatten的定义,有四种情况需要考虑。首先是这样:

flatten (reverseflatten [x]) = [x]
flatten (Leaf x) = [x]

下一步:

flatten (reverseflatten [x,y]) = [x,y]
flatten (UNode x (Leaf y)) = [x,y]

然后:

flatten (reverseflatten [x,y,z]) = [x,y,z]
flatten (Node (Leaf x) y (Leaf z)) = [x,y,z]

最后:

flatten (reverseflatten (x:y:xs)) = x:y:xs
flatten (revflat2 (x:y:xs)) = x:y:xs

由于先前的模式已经捕获了xs[][_]匹配的情况,因此我们只需要考虑revflat2的一种情况,即{{ 1}}至少具有两个元素。

xs

啊哈!为此,最好有一个具有新属性的助手,即:

flatten (revflat2 (x:y:w:z:xs)) = x:y:w:z:xs
flatten (Node (Leaf x) y (revflat2 (z:w:xs))) = x:y:w:z:xs

(当然,我们实际上将使用名称flatten2 (revflat2 (z:w:xs)) = w:z:xs x代替yw。) 再一次让我们计算而无需思考。 z有三种情况,即xs[]和更长的情况。当[_]xs时:

[]

对于flatten2 (revflat2 [x,y]) = [y,x] flatten2 (UNode y (Leaf x)) = [y,x]

[_]

更长的时间:

flatten2 (revflat2 [x,y,z]) = [y,x,z]
flatten2 (Node (Leaf x) y (Leaf z)) = [y,x,z]

根据归纳假设,我们有flatten2 (revflat2 (x:y:w:z:xs)) = y:x:w:z:xs flatten2 (Node (Leaf x) y (revflat2 (z:w:xs))) = y:x:w:z:xs ,因此最后一个等式可以变为:

flatten2 (revflat2 (z:w:xs)) = w:z:xs

现在,我们可以将所有这些案例的所有最后一行都收录下来,然后编写一个程序:

flatten2 (Node (Leaf x) y rest) = y:x:flatten2 rest

这是最好的程序吗?没有!特别是,这是局部的-当flatten (Leaf x) = [x] flatten (UNode x (Leaf y)) = [x,y] flatten (Node (Leaf x) y (Leaf z)) = [x,y,z] flatten (Node (Leaf x) y rest) = x:y:flatten2 rest flatten2 (UNode y (Leaf x)) = [y,x] flatten2 (Node (Leaf x) y (Leaf z)) = [y,x,z] flatten2 (Node (Leaf x) y rest) = y:x:flatten2 rest flatten的第一个树参数不存在时,您可以自由选择flatten2Node的内容UNode(但是无论您做出什么选择都不会影响您关心的属性)以及Leaf应该如何处理叶子。如果您在这里做出明智的选择,则可能会合并许多模式。

但是此过程的优点是完全 机械的:您可以获取您感兴趣的属性,摇动曲柄,并获得具有该属性的函数(或相互矛盾的方程式,告诉您它是不可能,为什么)。仅当您拥有某些有用的东西时,您才需要凝视并思考什么会使它更漂亮或更美好。是的,方程式推理!