如何在Haskell中找到二叉树的所有可能子树?

时间:2013-08-29 20:37:57

标签: haskell binary-tree traversal tree-traversal

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

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

树是:

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

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

5 个答案:

答案 0 :(得分:6)

bheklilr给出了对你的问题的一种解释的答案,但是我会告诉你作为一个初学者,你将从自己解决问题中受益:

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

然后认为以声明方式,其中= - 符号表示“是”,并写两个语句。第一个应该是“allSubtrees树的Empty是......”(这是你的基本情况):

allSubtrees Empty = ...

然后你的递归案例,阅读allSubtrees的{​​{1}}是......“:

Node

如果你无法解决这个问题,请尝试编写一个适用于allSubtrees (Node l a r) = ...something combining the subTrees of l and the subtrees of r 的递归函数,然后对其进行概括。

答案 1 :(得分:3)

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

答案 2 :(得分: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条款。

答案 3 :(得分:2)

您可以非常轻松地使用递归来解决此问题。可能比使用循环容易。

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

答案 4 :(得分:1)

除了nponeccop的解决方案之外,这里是树的广度优先行走(不可能与paramorphism;真正需要共同递归):​​

{-# 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)