在 Haskell 中查找一棵树是不是是二叉搜索树

Posted

技术标签:

【中文标题】在 Haskell 中查找一棵树是不是是二叉搜索树【英文标题】:Find whether a tree is a binary search tree in Haskell在 Haskell 中查找一棵树是否是二叉搜索树 【发布时间】:2019-10-09 12:24:24 【问题描述】:
  type BSTree a = BinaryTree a

  data BinaryTree a = Null | Node (BinaryTree a) a (BinaryTree a)
                      deriving Show

  flattenTree :: BinaryTree a -> [a]
  flattenTree  tree = case tree of
      Null -> []
      Node left val right -> (flattenTree left) ++ [val] ++ (flattenTree right)

  isBSTree :: (Ord a) => BinaryTree a -> Bool
  isBSTree btree = case btree of
      Null -> False
      tree -> (flattenTree tree) == sort (flattenTree tree)

我要做的是写一个函数来判断给定的树是否是二叉搜索树,我的方法是将所有值分组到一个列表中并导入Data.List然后对列表进行排序以查找是否它们是平等的,但有点复杂。我们可以在不导入其他模块的情况下做到这一点吗?

【问题讨论】:

我不会先定义flattenTree。如果节点违反搜索属性,您可以提前返回False,而无需遍历以该节点为根的整个子树。 @chepner 问题出在sort,而不是flattenTree,这已经够懒了。 是的,在查看其他一些答案后,我想到了这一点。 【参考方案1】:

这是一种不压扁树的方法。

从定义来看,这里

data BinaryTree a = Null | Node (BinaryTree a) a (BinaryTree a)
     deriving Show

可以看到从左到右遍历树,忽略Node 和括号,得到Nulls 和as 的交替序列。也就是说,每两个值之间,都有一个Null

我的计划是检查每个子树是否满足合适的要求:我们可以细化每个Node 的要求,记住我们之间的值,然后在每个Null 测试它们。由于在每个有序值对之间都有一个Null,我们将测试所有有序(从左到右)对都是非递减的。

什么是要求?它是树中值的松散下限和上限。为了表达需求,包括最左端和最右端的需求,我们可以使用Bottom 和Top 元素扩展任何排序,如下所示:

data TopBot a = Bot | Val a | Top deriving (Show, Eq, Ord)

现在让我们检查给定的树是否满足有序和在给定边界之间的要求。

ordBetween :: Ord a => TopBot a -> TopBot a -> BinaryTree a -> Bool
  -- tighten the demanded bounds, left and right of any Node
ordBetween lo hi (Node l x r) = ordBetween lo (Val x) l && ordBetween (Val x) hi r
  -- check that the demanded bounds are in order when we reach Null
ordBetween lo hi Null         = lo <= hi

二叉搜索树是在BotTop 之间按顺序排列的树。

isBSTree :: Ord a => BinaryTree a -> Bool
isBSTree = ordBetween Bot Top

计算每个子树中的实际极值,将它们向外冒泡,为您提供比您需要的更多的信息,并且在左子树或右子树为空的边缘情况下很巧妙。维护和检查需求,将它们向内推,比较统一。

【讨论】:

(只是一个建议,)您可以标记这个答案(或任何其他)并要求版主将您的旧帐户 ***.com/users/733183/conor 合并到这个帐户中,这样人们就可以从那里看到另外两个答案好吧。 (我尝试要求模组这样做,但他们说只有用户自己可以请求合并帐户)。 :) (希望我在这里联系你不会太麻烦) 为什么要等到 Null 才比较边界?对我来说,在每个节点上检查值是否在递归之前已经确定的范围内对我来说似乎更自然。这个调整应该更懒惰:它可以为树 Node (Node undefined 10 undefined) 5 Null 正确生成 False【参考方案2】:

这里有个提示:做一个辅助函数

isBSTree' :: (Ord a) => BinaryTree a -> BSTResult a

BSTResult a 定义为

data BSTResult a
   = NotBST             -- not a BST
   | EmptyBST           -- empty tree (hence a BST)
   | NonEmptyBST a a    -- nonempty BST with provided minimum and maximum

您应该能够递归地进行,利用子树上的结果来驱动计算,尤其是最小值和最大值。

例如,如果您有tree = Node left 20 right,有isBSTree' left = NonEmptyBST 1 14isBSTree' right = NonEmptyBST 21 45,那么isBSTree' tree 应该是NonEmptyBST 1 45

在相同的情况下,除了tree = Node left 24 right,我们应该使用isBSTree' tree = NotBST

然后将结果转换为Bool 很简单。

【讨论】:

或为BSTResult a 定义明显的 Monoid 并折叠到其中。 :)(或者即使它不是合法的 Monoid ....) (但我认为这是合法的)【参考方案3】:

是的,您不需要对列表进行排序。您可以检查每个元素是否小于或等于下一个元素。这更有效,因为我们可以在 O(n) 中做到这一点,而评估排序列表 完全 需要 O(n log n).

因此,我们可以通过以下方式进行检查:

ordered :: Ord a => [a] -> Bool
ordered [] = True
ordered xa@(_:xs) = and (zipWith (<=) xa xs)

所以我们可以检查二叉树是否是二叉搜索树:

isBSTree :: Ord a => BinaryTree a -> Bool
isBSTree = ordered . flattenTree

我认为可以声称Null 本身是一棵二叉搜索树,因为它是一棵空树。这意味着对于每个节点(没有节点),左子树中的元素都小于或等于节点中的值,而右子树中的元素都大于或等于节点中的值.

【讨论】:

【参考方案4】:

我们可以像这样从左到右遍历树:

isBSTtreeG :: Ord a => BinaryTree a -> Bool
isBSTtreeG t = gopher Nothing [Right t]
    where
    gopher  _   []                        =  True
    gopher  x   (Right Null:ts)           =  gopher x ts
    gopher  x   (Right (Node lt v rt):ts) =  gopher x (Right lt:Left v:Right rt:ts)
    gopher Nothing   (Left v:ts)          =  gopher (Just v) ts
    gopher (Just y)  (Left v:ts)          =  y <= v && gopher (Just v) ts

灵感来自John McCarthy's gopher

显式下推列表可以通过延续传递来消除,

isBSTtreeC :: Ord a => BinaryTree a -> Bool
isBSTtreeC t = gopher Nothing t (const True)
    where
    gopher  x   Null           g  =  g x 
    gopher  x   (Node lt v rt) g  =  gopher x lt (\case
                                       Nothing -> gopher (Just v) rt g
                                       Just y  -> y <= v && gopher (Just v) rt g)

只维护一个迄今为止最大的元素就足够了。

【讨论】:

【参考方案5】:

我尝试这样做check binary search tree 实现更接近 BST 定义“对于每个节点 X,所有左子树节点 = X”:

data Btree a = Empty | Node a (Btree a) (Btree a) deriving Show

instance Functor Btree where
  fmap _ Empty = Empty
  fmap f (Node v l r) = Node (f v) (fmap f l) (fmap f r)

instance Foldable Btree where
  --  foldMap :: Monoid m => (a -> m) -> t a -> m
  foldMap _ Empty = mempty
  foldMap f (Node v l r) = (f v) <> foldMap f l <> foldMap f r

check :: Ord a => Btree a -> Bool
check Empty = True
check (Node v l r) = all' (v>) l && all' (v<=) r && check l && check r
                     where all' f b = foldr (&&) True $ f <$> b

【讨论】:

以上是关于在 Haskell 中查找一棵树是不是是二叉搜索树的主要内容,如果未能解决你的问题,请参考以下文章

判断一棵树是否是二叉搜索树

二叉树:我是不是一棵二叉搜索树

java数据结构与算法之判断是否是二叉搜索树

二叉搜索树

二叉搜索树的简易实现

二十一:判断二叉树是否是二叉查找树?