2-3 дерево — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Find)
(Следующий и предыдущий)
Строка 152: Строка 152:
 
   '''return''' t;
 
   '''return''' t;
 
      
 
      
[[Файл:Treenext.png|border|325px]]  [[Файл:Treeprev.png|border|300px]]
+
[[Файл:23treenext.png|border|left|thumb|путь при нахождение следующего элемента за 2|325px]]
  
 
=== Нахождение m следующих элементов ===
 
=== Нахождение m следующих элементов ===

Версия 22:48, 10 мая 2015

23treemain.png
2-3 дерево (англ. Heapsort) — структура данных, представляющая собой сбалансированное дерево поиска, такое что из каждого узла может выходить две или три ветви и глубина всех листьев одинакова. Является частным случаем B+ дерева.

Свойства

2-3 дерево — сбалансированное дерево поиска, обладающее следующими свойствами:

  • нелистовые вершины имеют либо [math]2[/math], либо [math]3[/math] сына,
  • нелистовая вершина, имеющая двух сыновей, хранит максимум левого поддерева. Нелистовая вершина, имеющая трех сыновей, хранит два значения. Первое значение хранит максимум левого поддерева, второе максимум центрального поддерева,
  • сыновья упорядочены по значению максимума поддерева сына,
  • все листья лежат на одной глубине,
  • Высота 2-3 дерева [math]O(\log{n})[/math], где [math] n [/math] — количество элементов в дереве.

Операции

Введем следующие обозначения:

  • [math]\mathtt{root}[/math] — корень 2-3 дерева.

Каждый узел дерева обладает полями:

  • [math]\mathtt{parent}[/math] — родитель узла,
  • [math]\mathtt{sons}[/math] — сыновья узла,
  • [math]\mathtt{keys}[/math] — ключи узла,
  • [math]\mathtt{length}[/math] — количество сыновей.

Поиск

  • [math]x[/math] — искомое значение,
  • [math]t[/math] — текущая вершина в дереве.

Изначально [math]t = \mathtt{root}[/math]. Будем просматривать ключи в узлах, пока узел не является листом. Рассмотрим два случая:

  • у текущей вершины два сына. Если её значение меньше [math]x[/math], то [math]t = \mathtt{t.sons[1]}[/math], иначе [math]t = \mathtt{t.sons[0]}[/math].
  • у текущей вершины три сына. Если второе значение меньше [math]x[/math], то [math]t = \mathtt{t.sons[2]}[/math]. Если первое значение меньше [math]x[/math], то [math]t = \mathtt{t.sons[1]}[/math], иначе [math]t = \mathtt{t.sons[0]}[/math].
Node search(int x):
  Node t = root
  while (t не является листом)
    if (t.length == 2)
      if (t.keys[0] < x)
        t = t.sons[1]
      else t = t.sons[0]
    else
      if (t.keys[1] < x)
        t = t.sons[2]
      else
        if (t.keys[0] < x)
          t = t.sons[1]
        else t = t.sons[0]
  return t

Пример поиска в 2-3 дереве, так как элемент [math]6[/math] существует, то был возвращен корректный узел, так как элемента [math]10[/math] нет, возвращается некорректный узел. На основе этого можно сделать метод [math]\mathtt{exist}[/math], проверяющий наличии элемента в дереве.

23treesearch.png

Вставка элемента

  • [math]x[/math] — добавляемое значение,
  • [math]t[/math] — текущая вершина в дереве. Изначально [math]t = \mathtt{root}[/math].

Если корня не существует — дерево пустое, то новый элемент и будет корнем (одновременно и листом). Иначе поступим следующим образом:

Найдем сперва, где бы находился элемент, применив [math]\mathtt{search(x)}[/math]. Далее проверим есть ли у этого узла родитель, если его нет, то в дереве всего один элемент — лист. Возьмем этот лист и новый узел, и создадим для них родителя, лист и новый узел расположим в порядке возрастания.

Если родитель существует, то подвесим к нему ещё одного сына. Если сыновей стало [math]4[/math], то разделим родителя на два узла, и повторим разделение теперь для его родителя (перед разделением обновим ключи).

splitParent(Node t):
 if (t.length > 3) 
    Node a;
    a.sons[0] = t.sons[2]
    a.sons[1] = t.sons[3]
    t.sons[2].parent = a
    t.sons[3].parent = a
    a.keys[0] = t.keys[2]
    a.length = 2
    t.length = 2
    t.sons[2] = null
    t.sons[3] = null
    if (t.parent != null)
      t.parent[t.length] = a
      t.length++
      сортируем сыновей у t.parent
      splitParent(t.parent)
    else                   //мы расщепили корень, надо подвесить его к общему родителю, который будет новым корнем
     Node t = root
     root.sons[0] = t
     root.sons[1] = a
     t.parent = root
     a.parent = root
     root.length = 2
     сортируем сыновей у root

Если сыновей стало [math]3[/math], то ничего не делаем. Далее необходимо восстановить ключи на пути от новой вершины до корня:

updateKeys(Node t): 
  Node a = t.parent
  while (a != null)
   for i = 0 .. a.length - 1
     a.keys[i] = max(a.sons[i]) //max — возвращает максимальное значение в поддереве.
   a = a.parent                 //Примечание: max легко находить, если хранить максимум 
                                //правого поддерева в каждом узле — это значение и будет max(a.sons[i])

[math]\mathtt{updateKeys}[/math] необходимо запускать от нового узла. Добавление элемента:

insert(int x):
Node n = Node(x)
if (root == null) 
 root = n
 return
Node a = search(x)
if (a.parent == null) 
  Node t = root
  root.sons[0] = t
  root.sons[1] = n
  t.parent = root
  n.parent = root
  root.length = 2
  сортируем сыновей у root
else 
  Node p = a.parent
  p.sons[p.length] = n
  p.length++
  n.parent = p
  сортируем сыновей у p
  updateKeys(n) 
  split(n)
updateKeys(n) 

Так как мы спускаемся один раз, и поднимаемся вверх при расщеплении родителей не более одного раза, то [math]\mathtt{insert}[/math] работает за [math]O(\log{n})[/math].

Примеры добавления:

23treeinsert.png

23treeinsert2.png

23treeinsert3.png

Удаление элемента

  • [math]x[/math] — значение удаляемого узла,
  • [math]t[/math] — текущий узел.

Пусть изначально [math]t = \mathtt{search(x)}[/math] — узел, где находится [math]x[/math].

Если у [math]t[/math] не существует родителя, то это корень. Удалим его.

Если у [math]t[/math] существует родитель, и у него [math]3[/math] сына, то просто удалим [math]t[/math]. Обновим ключи, запустив [math]\mathtt{updateKeys}[/math] от любого брата [math]t[/math].

Если у родителя([math]\mathtt{t.parent}[/math]) [math]2[/math] сына, то удалим [math]t[/math], а его брата([math]b[/math] перецепим к родителю соседнего листа(обозначим его за [math]p[/math]). Вызовем [math]\mathtt{updateKeys}(b)[/math] и [math]\mathtt{splitParent}(p)[/math], так как у [math]p[/math] могло оказаться [math]4[/math] сына. Удалим теперь и [math]\mathtt{t.parent}[/math]. После возврата из рекурсии обновим все ключи с помощью [math]\mathtt{updateKeys()}[/math], запустившись от [math]b[/math]. Treedelete.png

Следующий и предыдущий

  • [math]x[/math] — поисковый параметр,
  • [math]t[/math] — текущий узел.

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

 Node next(int x)
 Node t = search(x)
 if (t.keys[0] > x) //x не было в дереве, и мы нашли следующий сразу
   return t
 while (t != null)
   t = t.parent
   if (можно свернуть направо вниз)
    в t помещаем вершину, в которую свернули
    while (пока t — не лист)
     t = t.sons[0]
     return t
 return t;
    
путь при нахождение следующего элемента за 2

Нахождение m следующих элементов

B+ деревья, поддерживают операцию [math]\mathtt{find}[/math], которая позволяет находить m следующих элементов. Наивная реализация выглядит следующим образом, будем вызывать [math]m[/math] раз поиск следующего элемента, такое решение работает за [math]O(m * \log{n})[/math]. Но 2-3 деревья, позволяют находить m следующих элементов за [math]O(m + \log{n})[/math], что значительно ускоряет поиск при больших [math]m[/math]. По построению, все листья у нас отсортированы в порядке возрастания, воспользуемся этим для нахождения m элементов. Нам необходимо связать листья, для этого модифицируем [math]\mathtt{insert}[/math] и [math]\mathtt{delete}[/math]. Добавим к узлам следующие поля:

  • [math]\mathtt{right}[/math] — указывает на правый лист,
  • [math]\mathtt{left}[/math] — указывает на левый лист.

Пусть [math]t[/math] - добавленный узел. Изменим [math]\mathtt{insert}[/math] следующим образом: в самом конце, после того как мы уже обновили все ключи, найдем [math]\mathtt{next(t)}[/math] и запишем ссылку на него в [math]\mathtt{t.right}[/math]. Аналогично с левым. Пусть [math]t[/math] - удаляемый узел. Изменим [math]\mathtt{insert}[/math] следующим образом: в самом начале, до удаления [math]t[/math], найдем следующий ([math]mathtt{next}[/math]) и запишем в ([math]mathtt{next.left}[/math]) правый лист относительно [math]t[/math]. С левым поступим аналогично.

В итоге, мы имеем двусвязный список в листьях, и чтобы нам вывести [math]m[/math] элементов, нам достаточно один раз найти нужный элемент и пробежаться вправо на [math]m[/math] элементов.


23treefind.png

См. также

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