【问题标题】:Flattening a binary tree in a specific manner以特定方式展平二叉树
【发布时间】:2019-07-11 16:26:23
【问题描述】:

考虑以下二叉树和一元树的定义,一个函数 flatten,它将二叉树和一元树转换为列表(例如,flatten (Node (Leaf 10) 11 (Leaf 20))[10,11,20])和一个函数 reverseflatten,它将列表转换为二叉树(以此处描述的特定方式 (Defining a function from lists to binary and unary trees) 并如下图所示):

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] 不同。如何修改flatten 使reverseflatten x: xs(reverseflatten(flatten(reverseflatten x:xs))) 相同?

reverseflatten 构成下图中的一系列树。 例如,reverseflatten [x,y,z] 形成图中的树 2,reverseflatten [x,y,z, x'] 形成树 3,reverseflatten [x,y,z, x', y'] 形成树 4,reverseflatten [x,y,z, x', y', z'] 形成树 5,reverseflatten [x,y,z, x', y', z', x''] 形成树 6,等等。

我想要的是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)))

但这会产生:

experiment.hs:585:1: error:
    parse error (possibly incorrect indentation or mismatched brackets)
    |
585 | flatten (UNode l x) = [l] ++ flatten x 
    | ^

【问题讨论】:

  • 首先简化问题 - 真正的问题是 flatten $ reverseflatten [1..5] 不等于 [1..5],您显然希望它是。 (由于 Haskell 是参照透明的,如果它们相等,将reverseflatten 应用于每一边会产生相同的结果。)(我不知道如何解决这个问题,尽管它与你相当复杂和不自然的配方有关“反向展平”,但只是说考虑根本问题而不是其后果会更简单。)
  • @Robin Zigmond:我可以(礼貌地)乞求不自然的不同吗?在自然语言语法和语义(我的领域)中,我设计的配方实际上是非常自然的 :) 当然,从其他角度来看它可能是不自然的,也许你来自哪里......
  • 公平地说,这是一个见仁见智的问题,我将澄清我的第一条评论以反映这一点。好吧,显然我不能再编辑评论了,但希望这可以澄清一下。无论如何,我真正的意思是,你不能用一个简单的 2/3 行递归定义来表达它,而是最终需要一个辅助函数,两个函数都有 4 种情况。
  • 您从上一篇文章的答案中获取了reverseflatten,这似乎是错误的。这个答案实际上是正确的,还是我们仍然应该以您之前的描述作为参考?目前,您想要的结构只有一个非常非正式的描述,众所周知,这很容易被误解。一些关于它来自哪里、它在你的领域中意味着什么的上下文将有助于澄清这种情况。
  • 如果您希望reverseflatten (flatten (reverseflatten xs))) 始终为真,那么您的代码可能基于某些数据结构描述,其中包括此属性持有的证明。你应该逐行检查你的实现,如果它确实符合你的运营商定义的要求。

标签: haskell recursion tree binary-tree


【解决方案1】:

我认为你的问题是树的第一个节点与其他节点的模式不同,如果你看 Tree1 它去 [x,y,z] ,而 Tree4 去 [x,y ,[x',z,y']]。

您可以看到子节点的顺序与第一个节点的顺序不同,这就是为什么有些人注意到它感觉不自然的原因。要修复它,您可以将 reverseFlattening 的定义更改为具有恒定模式的定义,我假设您不希望这样做,或者更改您的 flatten 以考虑到这种奇怪的模式:

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 和左节点扩展了模式匹配,因为您已经知道它将是一个左侧树,因此如果您已经知道结果将是什么,则无需调用您的函数。

【讨论】:

  • 就是这样! :) 这些功能对我来说很自然:)
【解决方案2】:

可测试规范

首先,我们可以将您的规范 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 xUNode x (Leaf y) 和@987654338 @,但不是像 UNode x (Node ...)Node (Node ...) y r 这样的其他模式。因此,考虑到Trees 的整个域,flatten1 是高度局部的:

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

尽管存在偏袒,QuickCheck 同意:

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 的重构版本。

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)

QuickCheck 验证:

ghci> quickCheck $ prop_flat flatten' reverseflatten'
+++ OK, passed 100 tests.

【讨论】:

    【解决方案3】:

    让我们假设一个稍微强一些的属性,不假思索地计算一下,看看它会把我们带到哪里。也就是说,更强大的属性是,只要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的一种情况,即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
    

    (当然,我们实际上将使用名称xy 而不是wz。) 再一次让我们不假思索地计算一下。 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
    

    这是最好的程序吗?不!特别是,它是部分的——当NodeUNode 的第一个树参数不是Leaf(但不管您做出的选择不会影响您关心的财产)以及flatten2 应该如何处理树叶。如果您在这里做出明智的选择,可能会合并许多模式。

    但是这个过程的好处在于它完全是机械的:你可以获取你感兴趣的属性,转动曲柄,然后得到一个具有该属性的函数(或告诉你它的冲突方程不可能,为什么)。只有当你有了一些有用的东西时,你才需要凝视并思考什么会使它更漂亮或更好。是的,等式推理!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-12
      • 1970-01-01
      • 2019-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多