【问题标题】:How to find all possible subtrees of a binary tree in Haskell?如何在 Haskell 中找到二叉树的所有可能子树?
【发布时间】:2013-08-31 16:40:18
【问题描述】:

我需要在二叉树中找到所有可能的子树:

allSubtrees :: BinaryT a -> [BinaryT a]
allSubtrees = undefined

而树是:

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

我是 Haskell 的新手,我知道 Haskell 中没有 whilefor 循环。 Haskell 是关于递归的。我的问题是,如何在没有无限递归的情况下获得树的所有可能子树?

【问题讨论】:

标签: haskell binary-tree traversal tree-traversal


【解决方案1】:

bheklilr 为您提供了对您问题的一种解释的答案,但这是我作为初学者将告诉您的,他们将从自己解决问题中受益:

首先确保您已明确定义您希望函数执行的操作。我假设您希望它像 tails 一样工作。

然后考虑声明式,您的=-sign 表示“是”,并写两个语句。第一个应该是“allSubtrees of the Empty tree is ...”(这是您的基本情况):

allSubtrees Empty = ...

然后你的递归案例,阅读“allSubtrees of a Node is ...”:

allSubtrees (Node l a r) = ...something combining the subTrees of l and the subtrees of r

如果您对此无法理解,请尝试编写一个适用于 Node Empty 1 Empty 的递归函数,然后对其进行泛化。

【讨论】:

    【解决方案2】:

    Uniplate 是你的朋友,在这里:

    {-# LANGUAGE DeriveDataTypeable #-}
    
    import Data.Generics.Uniplate.Data (universe)
    import Data.Data (Data)
    import Data.Typeable (Typeable)
    
    data BinaryT a =
       Empty
       | Node (BinaryT a) a (BinaryT a)
    deriving (Eq, Show, Typeable, Data)
    
    
    allSubtrees :: (Data a, Typeable a) => BinaryT a -> [BinaryT a]
    allSubtrees = universe
    

    【讨论】:

    • @jtobin:另请参阅我的回答中的 geniplate 示例
    【解决方案3】:

    由于 Uniplate 演示已经存在,为了完整起见,这里是使用 recursion-schemes 库的实现:

    {-# LANGUAGE DeriveFunctor, TypeFamilies #-}
    import Data.Functor.Foldable
    
    data BinaryT a 
        = Empty
        | Node (BinaryT a) a (BinaryT a)
        deriving (Eq, Show)
    
    data BinaryTBase a b 
        = BaseEmpty 
        | BaseNode b a b
        deriving (Functor)
    
    type instance Base (BinaryT a) = BinaryTBase a
    
    instance Foldable (BinaryT b) where
        project Empty = BaseEmpty
        project (Node a b c) = BaseNode a b c 
    
    instance Unfoldable (BinaryT b) where
        embed BaseEmpty = Empty
        embed (BaseNode a b c) = Node a b c 
    
    allSubtrees :: BinaryT a -> [BinaryT a]     
    allSubtrees = para phi where
        phi BaseEmpty = []
        phi (BaseNode (l, ll) v (r, rr)) = ll ++ rr ++ [Node r v l] 
    

    基本函子样板很大,但相对而言并不令人惊讶,并且从长远来看可能会节省您的工作量,因为它是每种类型一次。

    这是另一个使用 geniplate 库的实现:

    {-# LANGUAGE TemplateHaskell #-}
    import Data.Generics.Geniplate
    
    data BinaryT a =
        Empty
      | Node (BinaryT a) a (BinaryT a)
      deriving (Eq, Show)
    
    allSubTrees :: BinaryT a -> [BinaryT a]
    allSubTrees = $(genUniverseBi 'allSubTrees)
    

    这里是@bheklilr 显式递归方法的缩短版本,人们可能期望新人提供这种方法(我使用(++) 表示对称):

    allSubTrees3 :: BinaryT a -> [BinaryT a]
    allSubTrees3 Empty = []
    allSubTrees3 this @ (Node left _ right) = [this] ++ leftSubs ++ rightSubs where
        leftSubs = allSubTrees3 left
        rightSubs = allSubTrees3 right
    

    请注意,它列出了根但不列出空子树,但它很容易更改。

    我想知道不同方法的优缺点是什么。 uniplate 在某种程度上比其他方法更安全吗?

    请注意,recursion-schemes 方法既简洁(如果您需要对一种类型进行许多不同的遍历)又灵活(您可以完全控制遍历顺序,是否包含空子树等)。一个缺点是para 和其他方案的类型过于笼统,无法进行类型推断,因此通常需要类型签名来消除歧义。

    geniplate 似乎没有uniplate 那样具有侵入性,因为不需要添加deriving 子句。

    【讨论】:

      【解决方案4】:

      你可以很容易地使用递归来解决这个问题。可能比使用循环更容易。

      allSubTrees :: BinaryT a -> [BinaryT a]
      allSubTrees Empty = []
      allSubTrees (Node Empty n Empty) = []
      allSubTrees (Node Empty n right) = right : allSubTrees right
      allSubTrees (Node left n Empty) = left : allSubTrees left
      allSubTrees (Node left n right) = left : right : leftSubs ++ rightSubs
          where
              leftSubs = allSubTrees left
              rightSubs = allSubTrees right
      

      【讨论】:

      • 这是否列出了根目录?
      • @SassaNF 不。但如果你愿意,我会让你自己弄清楚=P
      【解决方案5】:

      除了 nponeccop 的解决方案之外,这里是树的广度优先遍历(对于拟态是不可能的;确实需要协同递归):​​

      {-# LANGUAGE DeriveFunctor, TypeFamilies #-}
      import Data.Functor.Foldable
      
      data BinaryT a 
          = Empty
          | Node (BinaryT a) a (BinaryT a)
          deriving (Eq, Show)
      
      allSubtrees :: BinaryT a -> [BinaryT a]
      allSubtrees t = ana phi [t] where
          phi [] = Nil
          phi (Empty:t) = Cons Empty t
          phi (n@(Node l v r):t) = Cons n (t++(l:[r]))
      
      main = print $ allSubtrees $ 
             Node (Node Empty "a" Empty) "b" (Node (Node Empty "c" Empty) "d" Empty)
      

      【讨论】:

      • [Node r v l] ++ ll ++ rr 在我的代码中生成与您的变形相同的 badc 遍历顺序。
      • @nponeccop 我不这么认为。 ll++rr 将添加整个左子树,然后是整个右子树,不是吗? Anamorphism 首先选择子树的顶部节点,然后将分支添加到尾部。看看 Empty 在你和我的情况下是如何定位的;这表示当这些节点不为空时的顺序不同。 (您的案例没有将 Empty 添加到子树列表中,这可能是也可能不是错误 - 取决于对任务的解释 - 但假设您确实添加了 Empty)
      猜你喜欢
      • 1970-01-01
      • 2020-02-07
      • 2014-05-10
      • 1970-01-01
      • 2021-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多