Jayyyyyy
Jayyyyyy

Reputation: 197

Find whether a tree is a binary search tree in Haskell

  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)

What I want to do is to write a function to determine whether the given tree is a binary search tree, my method is to group all of the values in a list and import Data.List and then sort the list to find whether they are equal, but it is a little complicated. Can we do this without importing other module?

Upvotes: 9

Views: 1739

Answers (5)

Evg
Evg

Reputation: 3080

I try to do this check binary search tree implementation closer to BST definition "for every node X the all left subtree nodes < X and all right subtree nodes >= 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

Upvotes: 0

Will Ness
Will Ness

Reputation: 71119

We can proceed left-to-right over the tree like this:

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

Inspired by John McCarthy's gopher.

The explicit push-down list can be eliminated with continuation-passing,

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)

Maintaining just one, largest-so-far element, is enough.

Upvotes: 1

chi
chi

Reputation: 116174

Here's a hint: make an auxiliary function

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

where BSTResult a is defined as

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

You should be able to proceed recursively, exploiting results on subtrees to drive the computation, in particular the minimum and maximum.

For instance, if you have tree = Node left 20 right, with isBSTree' left = NonEmptyBST 1 14 and isBSTree' right = NonEmptyBST 21 45, then isBSTree' tree should be NonEmptyBST 1 45.

In the same case except for tree = Node left 24 right, we should instead have isBSTree' tree = NotBST.

Converting the result to Bool is then trivial.

Upvotes: 6

pigworker
pigworker

Reputation: 43393

Here's a way to do it without flattening the tree.

From the definition, here,

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

one can see that traversing the tree left to right, ignoring Node and parentheses, gives you an alternating sequence of Nulls and as. That is, between every two values, there is a Null.

My plan is to check that each subtree satisfies suitable requirements: we can refine the requirements at each Node, remembering which values we are between, then test them at each Null. As there is a Null between every in order pair of values, we will have tested that all in order (left-to-right) pairs are non-decreasing.

What is a requirement? It's a loose lower and upper bound on the values in the tree. To express requirements, including those at the leftmost and rightmost ends, we may extend any ordering with Bottom and Top elements, as follows:

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

Now let us check that a given tree satisfies the requirements of being both in order and between given bounds.

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

A binary search tree is a tree that is in order and between Bot and Top.

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

Computing the actual extremal values in each subtree, bubbling them outwards, gives you more information than you need, and is fiddly in the edge cases where a left or right subtree is empty. Maintaining and checking the requirements, pushing them inwards, is rather more uniform.

Upvotes: 13

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477684

Yes, you do not need to sort the list. You can check if every element is less than or equal to the next element. This is more efficient since we can do this in O(n), whereas evaluating the sorted list completely takes O(n log n).

We thus can check this with:

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

So we can check if the binary tree is a binary search tree with:

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

I think one can claim that Null itself is a binary search tree, since it is an empty tree. This thus means that for every node (there are no nodes) the elements in the left subtree are less than or equal to the value in the node, and the elements in the right subtree are all greater than or equal to the value in the node.

Upvotes: 3

Related Questions