Найти, является ли дерево бинарным деревом поиска в Haskell

10
  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а затем отсортировать список, чтобы определить, равны ли они, но это немного сложно. Можем ли мы сделать это без импорта другого модуля?

Jayyyyyy
источник
Я бы не стал определять flattenTreeпервым. Вы можете вернуться Falseрано, если узел нарушает свойство поиска, не обходя все поддерево, укорененное в этом узле.
Chepner
@chepner проблема с sort, а не с flattenTree, который достаточно ленив.
Уилл Несс
Да, это пришло мне в голову после просмотра некоторых других ответов.
Chepner

Ответы:

13

Вот способ сделать это без сплющивания дерева.

Из определения здесь

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

Можно видеть, что обход дерева слева направо, игнорирование Nodeи скобки дают чередующуюся последовательность Nulls и as. То есть между каждыми двумя значениями есть Null.

Мой план состоит в том, чтобы проверить, что каждое поддерево удовлетворяет подходящим требованиям : мы можем уточнить требования к каждому Node, помня, с какими значениями мы находимся, а затем протестировать их на каждом Null. Поскольку Nullмежду значениями в каждом порядке есть пара значений, мы проверим, что все в порядке (слева направо) пары не убывают.

Что такое требование? Это свободная нижняя и верхняя граница значений в дереве. Чтобы выразить требования, включая те, которые находятся слева и справа, мы можем расширить любой порядок с помощью Botтома и 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

Двоичное дерево поиска - это дерево, которое находится в порядке и между Botи Top.

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

Вычисление фактических экстремальных значений в каждом поддереве, всплытие их наружу, дает вам больше информации, чем вам нужно, и очень сложно в тех случаях, когда левое или правое поддерево пусто. Поддерживать и проверять требования , толкая их внутрь, довольно равномерно.

pigworker
источник
6

Вот подсказка: сделайте вспомогательную функцию

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 14и isBSTree' right = NonEmptyBST 21 45, то isBSTree' treeдолжно быть NonEmptyBST 1 45.

В том же случае, за исключением tree = Node left 24 right, мы должны вместо этого isBSTree' tree = NotBST.

Преобразование результата в Boolзатем тривиально.

чи
источник
1
или определите очевидный моноид для BSTResult aи сложите его. :) (или даже если это не законный моноид ....)
Уилл Несс
(но это все же законно, я думаю)
Уилл Несс
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само является бинарным деревом поиска, поскольку оно пустое. Таким образом, это означает, что для каждого узла (нет узлов) элементы в левом поддереве меньше или равны значению в узле, а все элементы в правом поддереве больше или равны значению в узле ,

Виллем Ван Онсем
источник
1

Мы можем перейти слева направо по дереву следующим образом:

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

Вдохновленный Джоном Маккарти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)

Достаточно сохранить только один, самый большой на данный момент элемент.

Уилл Несс
источник