Дерево поиска, наивная реализация — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Нерекурсивная реализация)
Строка 86: Строка 86:
 
Обе операции выполняются за время <tex>O(h)</tex>.
 
Обе операции выполняются за время <tex>O(h)</tex>.
 
====Реализация без использования информации о родителе====
 
====Реализация без использования информации о родителе====
Рассмотрим поиск следующего элемента для некоторого ключа <tex>x</tex>. Поиск будем начинать с корня дерева, храня текущий узел <tex>current</tex> и узел <tex>parent</tex>, последний посещенный узел, ключ которого больше <tex>x</tex>. Спускаемся вниз по дереву, как в алгоритме поиска узла, обновляя <tex>parent</tex> при переходе к левому потомку (если мы перешли к правому потомку, ключ в предыдущем узле меньше <tex>x</tex>). Таким образом, мы обойдем всё дерево и найдем элемент с минимальным ключем, большим <tex>x</tex>. Аналогично реализуется операция поиска предыдущего элемента.
+
Рассмотрим поиск следующего элемента для некоторого ключа <tex>x</tex>. Поиск будем начинать с корня дерева, храня текущий узел <tex>current</tex> и узел <tex>parent</tex>, последний посещенный узел, ключ которого больше <tex>x</tex>.  
 +
Спускаемся вниз по дереву, как в алгоритме поиска узла. Рассмотрим ключ текущего узла <tex>current</tex>. Если <tex>current.key \leq x</tex>, значит искомый узел находится в правом поддереве левом поддереве все ключи меньше <tex>current.key</tex>). Если <tex>current.key > x</tex>, значит либо <tex>current</tex> {{---}} искомый узел (сохраним его в <tex>parent</tex> как возможный ответ), либо искомый узел содержится в левом поддереве. Перейдем к нужному поддереву и повторим те же самые действия. Таким образом, каждый раз, переходя в левое поддерево, мы будем уменьшать ответ (ключ узла <tex>parent</tex> будет уменьшаться после каждого обновления). В конце обхода, по достижении листа, в <tex>parent</tex> будет находиться узел с минимальным ключем, большим <tex>x</tex>.
 +
Аналогично реализуется операция поиска предыдущего элемента.
 
  '''Node''' next(x : '''T'''):
 
  '''Node''' next(x : '''T'''):
 
     '''Node''' current = root, parent = ''null''                <font color="green">// root {{---}} корень дерева</font>
 
     '''Node''' current = root, parent = ''null''                <font color="green">// root {{---}} корень дерева</font>
Строка 99: Строка 101:
 
Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент.
 
Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент.
 
====Реализация с использованием информации о родителе====
 
====Реализация с использованием информации о родителе====
  '''func''' insert(x : '''Node''', z : '''T'''):               <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый ключ</font>
+
  '''func''' insert(x : '''Node''', z : '''Node'''):           <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый элемент</font>
     '''if''' z > x.key
+
     '''while''' x != ''null''
      '''if''' x.right != ''null''
+
      '''if''' z.key > x.key
          insert(x.right, z)
+
        '''if''' x.right != ''null''
      '''else'''
+
            x = x.right
          '''Node''' y
+
        '''else'''
          y.parent = x
+
            z.parent = x
          y.key = z
+
            x.right = z
          x.right = y
+
            '''break'''
    '''else if''' z < x.key
+
      '''else if''' z.key < x.key
      '''if''' x.left != ''null''
+
        '''if''' x.left != ''null''
          insert(x.left, z)
+
            x = x.left
      '''else'''
+
        '''else'''
          '''Node''' y
+
            z.parent = x
          y.parent = x
+
            x.left = z
          y.key = z
+
            '''break'''
          x.left = y
 
 
====Реализация без использования информации о родителе====
 
====Реализация без использования информации о родителе====
  '''func''' insert(x : '''Node''', z : '''T'''):              <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый ключ</font>
+
  '''Node''' insert(x : '''Node''', z : '''T'''):              <font color="green">// x {{---}} корень поддерева, z {{---}} вставляемый ключ</font>
 
     '''if''' x == ''null''  
 
     '''if''' x == ''null''  
       x = Node(z)                           <font color="green">// подвесим Node с key = z</font>
+
       '''return''' Node(z)                       <font color="green">// подвесим Node с key = z</font>
 
     '''else if''' z < x.key
 
     '''else if''' z < x.key
       insert(x.left, z)
+
       x.left = insert(x.left, z)
 
     '''else if''' z > x.key
 
     '''else if''' z > x.key
       insert(x.right, z)
+
       x.right = insert(x.right, z)
 +
    '''return''' x
  
 
Время работы алгоритма для обеих реализаций {{---}} <tex>O(h)</tex>.
 
Время работы алгоритма для обеих реализаций {{---}} <tex>O(h)</tex>.
Строка 129: Строка 131:
 
=== Удаление ===
 
=== Удаление ===
 
====Нерекурсивная реализация====
 
====Нерекурсивная реализация====
Для удаления узла из бинарного дерева поиска нужно рассмотреть три возможные ситуации. Если у узла нет дочерних узлов, то у его родителя нужно просто заменить указатель на <tex>null</tex>. Если у узла есть только один дочерний узел, то нужно создать новую связь между родителем удаляемого узла и его дочерним узлом. Наконец, если у узла два дочерних узла, то нужно найти следующий за ним элемент (у этого элемента не будет левого потомка), его правого потомка подвесить на место найденного элемента, а удаляемый узел заменить найденным узлом. Таким образом, свойство бинарного дерева поиска не будет нарушено. Время работы алгоритма {{---}} <tex>O(h)</tex>.
+
Для удаления узла из бинарного дерева поиска нужно рассмотреть три возможные ситуации. Если у узла нет дочерних узлов, то у его родителя нужно просто заменить указатель на <tex>null</tex>. Если у узла есть только один дочерний узел, то нужно создать новую связь между родителем удаляемого узла и его дочерним узлом. Наконец, если у узла два дочерних узла, то нужно найти следующий за ним элемент (у этого элемента не будет левого потомка), его правого потомка подвесить на место найденного элемента, а удаляемый узел заменить найденным узлом (для упрощения реализации, можно использовать функцию, вставляющую узел в дерево, и вставить правый дочерний узел в левое поддерево). Таким образом, свойство бинарного дерева поиска не будет нарушено. Время работы алгоритма {{---}} <tex>O(h)</tex>.
 
{| border="1" cellpadding="5" cellspacing="0"  
 
{| border="1" cellpadding="5" cellspacing="0"  
 
  !Случай
 
  !Случай
Строка 164: Строка 166:
 
             v.left.parent = p
 
             v.left.parent = p
 
     '''else'''                                          <font color="green">// третий случай: удаляемый элемент имеет двух потомков</font>
 
     '''else'''                                          <font color="green">// третий случай: удаляемый элемент имеет двух потомков</font>
         successor = next(v, t)                  
+
         insert(v.left, v.right)
         v.key = successor.key
+
         v.right = ''null''
         '''if''' successor.parent.left == successor
+
         '''if''' v.parent == ''null''
            successor.parent.left = successor.right
+
          t = v.left;
            '''if''' successor.right != ''null''
+
        '''else if''' v.parent.left == v
                successor.right.parent = successor.parent
+
          v.parent.left = v.left
 
         '''else'''
 
         '''else'''
            successor.parent.right = successor.right
+
          v.parent.right = v.left
            '''if''' successor.right != ''null''
 
                successor.right.parent = successor.parent
 
  
 
====Рекурсивная реализация====
 
====Рекурсивная реализация====

Версия 17:10, 1 июня 2015

Бинарное дерево поиска из 9 элементов
Бинарное дерево поиска (англ. binary search tree, BST) — структура данных для работы с упорядоченными множествами.

Бинарное дерево поиска обладает следующим свойством: если [math]x[/math] — узел бинарного дерева с ключом [math]k[/math], то все узлы в левом поддереве должны иметь ключи, меньшие [math]k[/math], а в правом поддереве большие [math]k[/math].

Операции в бинарном дереве поиска

Для представления бинарного дерева поиска в памяти будем использовать следующую структуру:

struct Node:
  T key                    // ключ узла
  Node left                // указатель на левого потомка
  Node right               // указатель на правого потомка
  Node parent              // указатель на предка

Обход дерева поиска

Есть три операции обхода узлов дерева, отличающиеся порядком обхода узлов:

  • [math]\mathrm{inorderTraversal}[/math] — обход узлов в отсортированном порядке,
  • [math]\mathrm{preorderTraversal}[/math] — обход узлов в порядке: вершина, левое поддерево, правое поддерево,
  • [math]\mathrm{postorderTraversal}[/math] — обход узлов в порядке: левое поддерево, правое поддерево, вершина.
func inorderTraversal(x : Node):
   if x != null
      inorderTraversal(x.left)
      print x.key
      inorderTraversal(x.right)

При выполнении данного обхода вершины будут выведены в следующем порядке: 1 3 4 6 7 8 10 13 14.

func preorderTraversal(x : Node)
   if x != null
      print x.key
      preorderTraversal(x.left)
      preorderTraversal(x.right)

При выполнении данного обхода вершины будут выведены в следующем порядке: 8 3 1 6 4 7 10 14 13.

func postorderTraversal(x : Node)
   if x != null
      postorderTraversal(x.left)
      postorderTraversal(x.right)
      print x.key

При выполнении данного обхода вершины будут выведены в следующем порядке: 1 4 7 6 3 13 14 10 8.

Данные алгоритмы выполняют обход за время [math]O(n)[/math], поскольку процедура вызывается ровно два раза для каждого узла дерева.

Поиск элемента

Поиск элемента 4

Для поиска элемента в бинарном дереве поиска можно воспользоваться следующей процедурой, которая принимает в качестве параметров корень дерева и искомый ключ. Для каждого узла функция сравнивает значение его ключа с искомым ключом. Если ключи одинаковы, то функция возвращает текущий узел, в противном случае функция вызывается рекурсивно для левого или правого поддерева. Узлы, которые посещает функция образуют нисходящий путь от корня, так что время ее работы [math]O(h)[/math], где [math]h[/math] — высота дерева.

Node search(x : Node, k : T):
   if x == null or k == x.key
      return x
   if k < x.key
      return search(x.left, k)
   else
      return search(x.right, k)


Поиск минимума и максимума

Чтобы найти минимальный элемент в бинарном дереве поиска, необходимо просто следовать указателям [math]left[/math] от корня дерева, пока не встретится значение [math]null[/math]. Если у вершины есть левое поддерево, то по свойству бинарного дерева поиска в нем хранятся все элементы с меньшим ключом. Если его нет, значит эта вершина и есть минимальная. Аналогично ищется и максимальный элемент. Для этого нужно следовать правым указателям.

Node minimum(x : Node):
  if x.left == null
     return x
  return minimum(x.left)
Node maximum(x : Node):
  if x.right == null
     return x
  return maximum(x.right)

Данные функции принимают корень поддерева, и возвращают минимальный (максимальный) элемент в поддереве. Обе процедуры выполняются за время [math]O(h)[/math].

Поиск следующего и предыдущего элемента

Реализация с использованием информации о родителе

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

Node next(x : Node):
   if x.right != null
      return minimum(x.right)
   y = x.parent
   while y != null and x == y.right
      x = y
      y = y.parent
   return y
Node prev(x : Node):
   if x.left != null
      return maximum(x.left)
   y = x.parent
   while y != null and x == y.left
      x = y
      y = y.parent
   return y

Обе операции выполняются за время [math]O(h)[/math].

Реализация без использования информации о родителе

Рассмотрим поиск следующего элемента для некоторого ключа [math]x[/math]. Поиск будем начинать с корня дерева, храня текущий узел [math]current[/math] и узел [math]parent[/math], последний посещенный узел, ключ которого больше [math]x[/math]. Спускаемся вниз по дереву, как в алгоритме поиска узла. Рассмотрим ключ текущего узла [math]current[/math]. Если [math]current.key \leq x[/math], значит искомый узел находится в правом поддереве (в левом поддереве все ключи меньше [math]current.key[/math]). Если [math]current.key \gt x[/math], значит либо [math]current[/math] — искомый узел (сохраним его в [math]parent[/math] как возможный ответ), либо искомый узел содержится в левом поддереве. Перейдем к нужному поддереву и повторим те же самые действия. Таким образом, каждый раз, переходя в левое поддерево, мы будем уменьшать ответ (ключ узла [math]parent[/math] будет уменьшаться после каждого обновления). В конце обхода, по достижении листа, в [math]parent[/math] будет находиться узел с минимальным ключем, большим [math]x[/math]. Аналогично реализуется операция поиска предыдущего элемента.

Node next(x : T):
   Node current = root, parent = null                // root — корень дерева
   while current != null
      if current.key > x
         parent = current
         current = current.left
      else
         current = current.right
   return parent

Вставка

Операция вставки работает аналогично поиску элемента, только при обнаружении у элемента отсутствия ребенка нужно подвесить на него вставляемый элемент.

Реализация с использованием информации о родителе

func insert(x : Node, z : Node):            // x — корень поддерева, z — вставляемый элемент
   while x != null
     if z.key > x.key
        if x.right != null
           x = x.right
        else
           z.parent = x
           x.right = z
           break
     else if z.key < x.key
        if x.left != null
           x = x.left
        else
           z.parent = x
           x.left = z
           break

Реализация без использования информации о родителе

Node insert(x : Node, z : T):               // x — корень поддерева, z — вставляемый ключ
   if x == null 
      return Node(z)                        // подвесим Node с key = z
   else if z < x.key
      x.left = insert(x.left, z)
   else if z > x.key
      x.right = insert(x.right, z)
   return x

Время работы алгоритма для обеих реализаций — [math]O(h)[/math].

Удаление

Нерекурсивная реализация

Для удаления узла из бинарного дерева поиска нужно рассмотреть три возможные ситуации. Если у узла нет дочерних узлов, то у его родителя нужно просто заменить указатель на [math]null[/math]. Если у узла есть только один дочерний узел, то нужно создать новую связь между родителем удаляемого узла и его дочерним узлом. Наконец, если у узла два дочерних узла, то нужно найти следующий за ним элемент (у этого элемента не будет левого потомка), его правого потомка подвесить на место найденного элемента, а удаляемый узел заменить найденным узлом (для упрощения реализации, можно использовать функцию, вставляющую узел в дерево, и вставить правый дочерний узел в левое поддерево). Таким образом, свойство бинарного дерева поиска не будет нарушено. Время работы алгоритма — [math]O(h)[/math].

Случай Иллюстрация
Удаление листа Bst del1.png
Удаление узла с одним дочерним узлом Bst del2.png
Удаление узла с двумя дочерними узлами Bst del3.png
func delete(t : Node, v : Node):                 // [math]t[/math] — дерево, [math]v[/math] — удаляемый элемент
   p = v.parent                                  // предок удаляемого элемента
   if v.left == null and v.right == null         // первый случай: удаляемый элемент - лист
     if p.left == v
       p.left = null
     if p.right == v
       p.right = null
   else if v.left == null or v.right == null     // второй случай: удаляемый элемент имеет одного потомка
       if v.left == null                 
           if p.left == v
             p.left = v.right
           else
             p.right = v.right
           v.right.parent = p 
       else
           if p.left == v
               p.left = v.left
           else
               p.right = v.left
           v.left.parent = p
   else                                          // третий случай: удаляемый элемент имеет двух потомков
       insert(v.left, v.right)
       v.right = null
       if v.parent == null
         t = v.left;
       else if v.parent.left == v
         v.parent.left = v.left
       else
         v.parent.right = v.left

Рекурсивная реализация

При рекурсивном удаления узла из бинарного дерева нужно рассмотреть три случая: удаляемый элемент находится в левом поддереве текущего поддерева, удаляемый элемент находится в правом поддереве или удаляемый элемент находится в корне. В двух первых случаях нужно рекурсивно удалить элемент из нужного поддерева. Если удаляемый элемент находится в корне текущего поддерева и имеет два дочерних узла, то нужно заменить его минимальным элементом из правого поддерева и рекурсивно удалить минимальный элемент из правого поддерева. Иначе, если удаляемый элемент имеет один дочерний узел, нужно заменить его потомком. Время работы алгоритма — [math]O(h)[/math]. Рекурсивная функция, возвращающая дерево с удаленным элементом [math]z[/math]:

Node delete(root : Node, z : T):               // корень поддерева, удаляемый ключ
  if root == null
     return root
  if z < root.key
    root.left = remove(root.left, z)
  else if z > root.key
     root.right = remove(root.right, z)
  else if root.left != null and root.right != null
     root.key = minimum(root.right).key
     root.right = remove(root, root.right)
  else
     if root.left != null
        root = t.left
     else
        root = t.right
  return root

См. также

Источники информации