Изменения

Перейти к: навигация, поиск

2-3 дерево

10 501 байт добавлено, 19:39, 4 сентября 2022
м
rollbackEdits.php mass rollback
[[Файл:2_3tree23treemain.jpgpng|right|300px|thumb400px|Пример 2-3 дерева|thumb]]''' 2-3 дерево ''' (англ. ''2-3 tree'') — структура данных, предложенная в 1970 году Джоном Хопкрофтом,и представляющая собой [[B-сбалансированное дерево|B-дерево]] cтепени 1поиска, такое что из каждого узла может выходить две или три ветви; при этом требуетсяи глубина всех листьев одинакова. Является частным случаем [[B-дерево#B.2B-.D0.B4.D0.B5.D1.80.D0.B5.D0.B2.D0.BE|B+ дерева]].== Свойства ==2-3 дерево {{---}} сбалансированное дерево поиска, обладающее следующими свойствами:*нелистовые вершины имеют либо <tex>2</tex>, либо <tex>3</tex> сына,*нелистовая вершина, имеющая двух сыновей, хранит максимум левого поддерева. Нелистовая вершина, имеющая трех сыновей, хранит два значения. Первое значение хранит максимум левого поддерева, второе максимум центрального поддерева,*сыновья упорядочены по значению максимума поддерева сына, чтобы *все внешние узлы находились листья лежат на одном уровнеодной глубине,*высота 2-3 дерева <tex>O(\log{n})</tex>, где <tex> n </tex> {{---}} количество элементов в дереве.  == Операции ==Введем следующие обозначения:*<tex>\mathtt{root}</tex> {{---}} корень 2-3 дерева.Каждый внутренний узел содержит либо дерева обладает полями:*<tex>\mathtt{parent}</tex> {{---}} родитель узла,*<tex>\mathtt{sons}</tex> {{---}} сыновья узла, *<tex>\mathtt{keys}</tex> {{---}} ключи узла, *<tex>\mathtt{length}</tex> {{---}} количество сыновей.=== Поиск ===*<tex>x</tex> {{---}} искомое значение,*<tex>t</tex> {{---}} текущая вершина в дереве.  Изначально <tex>t = \mathtt{root}</tex>. Будем просматривать ключи в узлах, пока узел не является листом. Рассмотрим два случая:*у текущей вершины два сына. Если её значение меньше <tex>x</tex>, то <tex>t = \mathtt{t.sons[1]}</tex>, иначе <tex>t = \mathtt{t.sons[0]}</tex>. *у текущей вершины три сына. Если второе значение меньше <tex>x</tex>, то <tex>t = \mathtt{t.sons[2]}</tex>. Если первое значение меньше <tex>x</tex>, то <tex>t = \mathtt{t.sons[1]}</tex>, иначе <tex>t = \mathtt{t.sons[0]}</tex>.  '''T''' search('''T''' 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.keys[0] [[Файл:23treesearch.png|thumb|center|600px|Поиск элемента 19, оранжевые стрелки обозначают путь по дереву при поиске]] === Вставка элемента ===*<tex>x</tex> {{---}} добавляемое значение,*<tex>t</tex> {{---}} текущая вершина в дереве. Изначально <tex>t = \mathtt{root}</tex>.Если корня не существует {{---}} дерево пустое, то новый элемент и будет корнем (одновременно и листом). Иначе поступим следующим образом: Найдем сперва, где бы находился элемент, применив <tex>\mathtt{search(x)}</tex>. Далее проверим есть ли у этого узла родитель, если его нет, то в дереве всего одинэлемент {{---}} лист. Возьмем этот лист и новый узел, либо и создадим для них родителя, лист и новый узел расположим в порядке возрастания. Если родитель существует, то подвесим к нему ещё одного сына. Если сыновей стало <tex>4</tex>, то разделим родителя на два ключаузла, и повторим разделение теперь для его родителя, ведь у него тоже могло быть уже <tex>3</tex> сына, а мы разделили и у него стало на <tex>1</tex> сына больше. (перед разделением обновим ключи).  '''function''' splitParent('''Node''' t): '''if''' (t.length > 3) Node a = Node(sons = {t.sons[2], t.sons[3]}, keys = {t.keys[2]}, parent = t.parent, length = 2) t.sons[2].parent = a t.sons[3].parent = a 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''' <font color=green>// мы расщепили корень, надо подвесить его к общему родителю, который будет новым корнем</font> Node t = root root.sons[0] = t root.sons[1] = a t.parent = root a.parent = root root.length = 2 сортируем сыновей у rootЕсли сыновей стало <tex>3</tex>, то ничего не делаем. Далее необходимо восстановить ключи на пути от новой вершины до корня: '''function''' updateKeys('''Node''' t): Node a = t.parent '''while''' (a != ''null'') '''for''' i = 0 .. a.length - 1 a.keys[i] = max(a.sons[i]) <font color=green>// max {{---}} возвращает максимальное значение в поддереве.</font> a = a.parent <font color=green>// Примечание: max легко находить, если хранить максимум </font> <font color=green>// правого поддерева в каждом узле {{---}} это значение и будет max(a.sons[i])</font> <tex>\mathtt{updateKeys}</tex> необходимо запускать от нового узла.Добавление элемента: '''function''' insert('''T''' x): Node n = Node(x) '''if''' (root == ''null'') root = n '''return''' Node a = searchNode(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) Так как мы спускаемся один раз, и поднимаемся вверх при расщеплении родителей не более одного раза, то <tex>\mathtt{insert}</tex> работает за <tex>O(\log{n})</tex>. Примеры добавления: [[Файл:23treeinsert.png|thumb|center|Добавление элемента с ключом 6|600px]] === Удаление элемента ===*<tex>x</tex> {{---}} значение удаляемого узла,*<tex>t</tex> {{---}} текущий узел,*<tex>b</tex> {{---}} брат <tex>t</tex>,*<tex>p</tex> {{---}} отец <tex>t</tex>,*<tex>np</tex> {{---}} соседний брат <tex>p</tex>,*<tex>gp</tex> {{---}} отец <tex>p</tex>.Пусть изначально <tex>t = \mathtt{searchNode(x)}</tex> {{---}} узел, где находится <tex>x</tex>. Если у <tex>t</tex> не существует родителя, то это корень (одновременно и единственный элемент в дереве). Удалим его.
==Значения ==Все данные хранятся в листьяхЕсли <tex>p</tex> существует, в вершинах хранится вспомогательная информацияи у него строго больше <tex>2</tex> сыновей,необходимая для организации поиска по поддеревьям.Нелистовые вершины содержат 1 или 2 ключато просто удалим <tex>t</tex>, указывающие на диапазон значений в их поддеревьяха у <tex>p</tex> уменьшим количество детей.
== Структура ==Если у родителя <tex>t</tex> два сына, рассмотрим возможные случаи (сперва везде удаляем <tex>t</tex>):Информация в таких деревьях хранится в листьях*<tex>np</tex> не существует, тогда мы удаляем одного из сыновей корня, следовательно, другой сын становится новым корнем,*у <tex>gp</tex> оказалось <tex>2</tex> сына, у <tex>np</tex> оказалось <tex>2</tex> сына. Подвесим <tex>b</tex> к <tex>np</tex> и удалим <tex>p</tex>. Так как у <tex>gp</tex> {{---}} родителя <tex>p</tex>, оказалось тоже два сына, а остальные вершины содержат вспомогательную информацию повторяем для организации поиска<tex>p</tex> такие же рассуждения,*у <tex>gp</tex> оказалось <tex>2</tex> или <tex>3</tex> сына, у <tex>np</tex> оказалось <tex>3</tex> сына. Просто заберем ближайшего к нам сына у <tex>np</tex> и прицепим его к <tex>p</tex>.Восстановим порядок в сыновьях <tex>p</tex>. Теперь у <tex>p</tex> оказалось снова два сына и все узлы 2-3 деревья сбалансированыдерева корректны, то есть каждое левое*у <tex>gp</tex> оказалось <tex>3</tex> сына, у <tex>np</tex> оказалось <tex>2</tex> сына. Подвесим <tex>b</tex> к <tex>np</tex> и удалим <tex>p</tex>, правоеа у <tex>gp</tex> уменьшим количество детей. Так как у <tex>np</tex> оказалось три сына, и центральное поддерево одинаковой высотыа у <tex>gp</tex> все ещё больше одного сына, и таким образом содержат равное (или почти равное) число данныхто все узлы 2-3 дерева корректны.
== Свойства ==
* Все нелистовые вершины содержат один ключ и 2 поддерева или 2 ключа и 3 поддерева.
* Все листовые вершины находятся на одном уровне (на нижнем уровне) и содержат 1 или 2 ключа.
* Все данные отсортированы
* 2-3 дерево - сливаемое дерево
* Все пути от корня до любого листа имеют одинаковую длину
* Высота 2-3 дерева лежит между <tex> \log_{2} n </tex> и <tex> \log_{2} n </tex>, где <tex> n </tex> - количество элементов в дереве.
Поэтому операции над ним выполняются за время <tex>O(\log{n})</tex>.
== Операции ==
* ''' Поиск '''
Для поиска в 2-3 дереве необходимо последовательно просматривать ключи,
хранящиеся во внутренних ячейках, спускаясь от корня к листьям. Вначале ключ искомого
элемента сравнивается с первым ключом ячейки и, если искомый ключ не больше первого,
то осуществляется переход в левое поддерево. Иначе, сравниваем искомый ключ со вторым
ключом в ячейке (если второго ключа нет — поддерева всего два, то сразу переходим во
второе поддерево) и если наш ключ не превосходит второй ключ, то осуществляется переход
в среднее поддерево, а если превосходит, то идем в правое поддерево.
Обобщим алгоритм при удалении когда у родителя <tex>t</tex> два сына:* ''' Вставка элемента ''' Если <tex>np</tex> не существует, то оказывается, что мы сейчас удаляем какого-то из сыновей корня (для определенности далее левого, с правым аналогично). Тогда теперь правый сын становится корнем. На этом удаление заканчивается. *Если <tex>np</tex> существует, то удалим <tex>t</tex>, а его брата (<tex>b</tex>) перецепим к <tex>np</tex>. Теперь у <tex>np</tex> могло оказаться <tex>4</tex> сына, поэтому повторим аналогичные действия из <tex>\mathtt{insert}</tex>: вызовем <tex>\mathtt{updateKeys}(b)</tex> и <tex>\mathtt{splitParent}(np)</tex>. Теперь рекурсивно удалим <tex>p</tex>. Есть два варианта вставки в В результате мы получаем корректное по структуре 2-3 дерево, но у нас есть нарушение в ключах в узлах, исправим их с помощью <tex>\mathtt{updateKeys()}</tex>, запустившись от <tex>b</tex>.[[Файл:23treedelete.png|thumb|center|Удаление элемента с ключом 2|1150px]]
Чтобы поместить новый ключ === Следующий и предыдущий ===*<tex>x</tex> {{---}} поисковый параметр,*<tex>t</tex> {{---}} текущий узел.В силу того, что наши узлы отсортированы по максимуму в узелподдереве, то следующий объект {{---}} это соседний лист справа. Попасть туда можно следующим образом:будем подниматься вверх, пока у нас не появится первой возможности свернуть направо вниз. Как только мы свернули направо вниз, будем идти всегда влево. Таким образом, мы окажемся в соседнем листе. Если мы не смогли ни разу свернуть направо вниз, и пришли в корень, то следующего объекта не существует. Случай с предыдущим симметричен. '''T''' next('''T''' x): Node t = searchNode(x) '''if''' (t.keys[0] > x) <font color=green> //x не было в дереве, и мы нашли следующий сразу</font> '''return''' t.keys[0] '''while''' (t != ''null'') t = t.parent '''if''' (можно свернуть направо вниз) в котором содержится ровно один ключt помещаем вершину, необходимо просто добавить его как второй ключ к узлув которую свернули '''while''' (пока t {{---}} не лист) t = t.sons[0] '''return''' t '''return''' t.keys[0]
[[Файл:23treeadd323treenext.png|220pxthumb|border]] [[Файл:23treeadd4.pngcenter|200px400px|borderПуть при поиске следующего элемента после 2]]
Если же === Нахождение m следующих элементов ===B+ деревья, поддерживают операцию <tex>\mathtt{find}</tex>, которая позволяет находить m следующих элементов. Наивная реализация выглядит следующим образом: будем вызывать <tex>m</tex> раз поиск следующего элемента, такое решение работает за <tex>O(m \log{n})</tex>. Но 2-3 деревья, позволяют находить m следующих элементов за <tex>O(m + \log{n})</tex>, что значительно ускоряет поиск при больших <tex>m</tex>.По построению, все листья у нас отсортированы в узле уже содержатся два ключапорядке возрастания, воспользуемся этим для нахождения m элементов. Нам необходимо связать листья, делим его на два "одноключевых" узла для этого модифицируем<tex>\mathtt{insert}</tex> и вставляем средний ключ в родительский узел<tex>\mathtt{delete}</tex>.Это может привести Добавим к томуузлам следующие поля:*<tex>\mathtt{right}</tex> {{---}} указывает на правый лист, что придется делить родительский *<tex>\mathtt{left}</tex> {{---}} указывает на левый лист.Пусть <tex>t</tex> {{---}} добавленный узел. Тогда таким же Изменим <tex>\mathtt{insert}</tex> следующим образом проходим до корня дерева: в самом конце, после того как мы уже обновили все ключи, найдем <tex>\mathtt{next(t)}</tex> и запишем ссылку на него в <tex>\mathtt{t.right}</tex>. Аналогично с левым.
[[ФайлПусть <tex>t</tex> {{---}} удаляемый узел. Изменим <tex>\mathtt{delete}</tex> следующим образом:23treeadd1в самом начале, до удаления <tex>t</tex>, найдем следующий <tex>\mathtt{next}</tex> и запишем в <tex>\mathtt{next.png|245px|border]] [[Файл:23treeadd2left}</tex> правый лист относительно <tex>t</tex>. С левым поступим аналогично.png|200px|border]]
* ''' Удаление элемента '''При удалении ключа из узла возникают три вариантаВ итоге, мы имеем двусвязный список в листьях, и чтобы нам вывести <tex>m</tex> элементов, нам достаточно один раз найти нужный элемент и пробежаться вправо на <tex>m</tex> элементов.
Если до удаления ключа в узле содержалось два ключа, то после удаления ничего не меняется.
Если же у ключа после удаления остался один элемент, то проверяем количество потомков второго ребенка того узла, ребенком которого является узел с удаляемым ключом. Если у него два ребенка, то присваиваем ему оставшийся один элемент.
[[Файл:23treedel123treefindm.png|220pxthumb|border]] [[Файл:23treedel2.pngИзменение ссылок при добавлении нового элемента|thumb|200pxcenter|border800px]]
Иначе у него три ребенка. Тогда присваиваем узлу с одним ключом один из этих ключей, таким образом получая два узла с двумя ключами== См.также ==* [[B-дерево]]* [[Splay-дерево]]* [[АВЛ-дерево]]* [[Декартово дерево]]* [[Красно-черное дерево]]
== Источники информации ==* [http://is.ifmo.ru/vis/tree23/tree23_ru.html is.ifmo.ru {{---}} Визуализатор 2-3 дерева]* [Файлhttp:23treedel3//rain.ifmo.ru/cat/view.php/vis/trees/2-3-2002 rain.png|200px|borderifmo.ru {{---}} Визуализатор 2-3 дерева]] * [[Файлhttp:23treedel4//ru.png|215px|border]wikipedia.org/wiki/2-3-дерево Википедия {{---}} 2-3 дерево]* Д. Кнут «Искусство программирования. Сортировка и поиск» {{---}} стр. 508-509
* ''' Слияние двух деревьев (merge()) '''
Возможно два варианта слияния двух деревьев.
Если два дерева одной высоты, то слияние представляет собой добавление общей вершины.
Если два дерева разной высоты, то при слиянии меньшее добавляем как поддерево к одной из вершин большего.Если возникает ситуация,когда у необходимого узла уже есть три ребенка, то делим его на два узла с двумя поддеревьями [[Категория:Дискретная математика и проверяем родителя.Таким образом проходим по дереву вверх до полной сбалансировки.алгоритмы]][[Категория:Структуры данных]][[Категория:Деревья поиска]]
1632
правки

Навигация