748
правок
Изменения
B-дерево
,Нет описания правки
B-дерево было впервые предложено Р. Бэйером и Е. МакКрейтом в 1970 году.</wikitex>
== Структура ==
B-дерево имеет следующие свойства ($t$ — параметр дерева, называемый ''минимальной степенью'' B-дерева, не меньший $2$.):
* Каждый узел, кроме корня, содержит не менее $t - 1$ ключей, и каждый внутренний узел имеет по меньшей мере $t$ дочерних узлов. Если дерево не является пустым, корень должен содержать как минимум один ключ.
* Каждый узел, кроме корня, содержит не более $2t - 1$ ключей и не более чем $2t$ сыновей во внутренних узлах
* Корень содержит от $1$ до $2t - 1$ ключей, если дерево не пусто и от $2$ до $2t$ детей при высоте большей 0.
* Каждый узел дерева, кроме листьев, содержащий ключи $k_1, ..., k_n$, имеет $n + 1$ сына. $i$-й сын содержит ключи из отрезка $[k_{i - 1}; k_i],\: k_0 = -\infty,\: k_{n + 1} = \infty$.</wikitex>
* Ключи в каждом узле упорядочены по неубыванию.
* Все листья находятся на одном уровне.
'''Node''' root <span style="color:#008000"> // указатель на корень дерева</span>
== Назначение ==
== Структуры данных во внешней памяти ==
Для того чтобы снизить время ожидания, связанное с механическим перемещением, при обращении к диску выполняется обращение одновременно сразу к нескольким элементам, хранящимся на диске. Информация разделяется на несколько страниц одинакового размера, которые хранятся последовательно друг за другом в пределах одного цилиндра (набора дорожек на дисках на одном расстоянии от центра), и каждая операция чтения или записи работает сразу с несколькими страницами. Для типичного диска размер страницы варьируется от $2$ до $16$ КБайт. После того, как головка установлена на нужную дорожку, а диск поворачивается так, что головка становится на начало интересующей нас страницы, чтение и запись становятся полностью электронными процессами, не зависящими от поворота диска, и диск может быстро читать или писать крупные объёмы данных.
Система в состоянии поддерживать в процессе работы в оперативной памяти только ограниченное количество страниц. Мы будем считать, что страницы, которые более не используются, удаляются из оперативной памяти системой; наши алгоритмы работы с В-деревьями не будут заниматься этим самостоятельно. Поскольку в большинстве систем время выполнения алгоритма, работающего с В-деревьями, зависит в первую очередь от количества выполняемых операций чтения/записи с диском, желательно минимизировать их количество и за один раз считывать и записывать как можно больше информации. Таким образом, размер узла В-дерева обычно соответствует дисковой странице. Количество потомков узла В-дерева, таким образом, ограничивается размером дисковой страницы.
Для больших В-деревьев, хранящихся на диске, степень ветвления обычно находится между <tex>50</tex> и <tex>2000</tex>, в зависимости от размера ключа относительно размера страницы. Большая степень ветвления резко снижает как высоту дерева, так и количество обращений к диску для поиска ключа. Например, если есть миллиард ключей, и $t=1001$, то поиск ключа займёт две дисковые операции. </wikitex>
== Высота ==
{{Теорема|statement=Если $n \geqslant 1$, то для B-дерева $T$ c $n$ узлами и минимальной степенью $t \geqslant 2$ имеется следующее неравенство:
:$h \leqslant$ $\log_t\dfrac{n+1}{2}$
}}
Здесь мы видим преимущества B-деревьев над красно-черными деревьями. Хотя высота деревьев растет как $O(\log t)$ в обоих случаях (вспомним, что t — константа), в случае B-деревьев основание логарифмов имеет гораздо большее значение. Таким образом, В-деревья требуют исследования примерно в $\log t$ раз меньшего количества узлов по сравнению с красно-черными деревьями в большинстве операций. Поскольку исследование узла дерева обычно требует обращения к диску, количество дисковых операций при работе с В-деревьями оказывается существенно сниженным.</wikitex>
== Операции ==
B-деревья представляют собой сбалансированные деревья, поэтому время выполнения стандартных операций в них пропорционально высоте. Однако, как уже было упомянуто выше, алгоритмы B-дерева созданы специально для работы с дисками (или другими носителями информации) и базами данных (или иными видами представления большого количества информация), минимизируя количество операций ввода-вывода.
Если ключ содержится в текущем узле, возвращаем его. Иначе определяем интервал и переходим к соответствующему сыну. Повторяем пока ключ не найден или не дошли до листа.
=== Добавление ключа ===
[[Файл:B3inssp.png|550px|Разбиение корня с t = 4. Корневой узел r разделяется на два узла, и создаётся новый корень s. Новый корень содержит средний ключ r и две половины r в качестве детей. Разбиением узла высота дерева увеличивается на единицу]]
Вставка ключа в B-дерево $T$ высоты $h$ за один нисходящий проход по дереву потребует $O(h)$ обращений к диску и $O(th)=O(t \log_t n)$ процессорного времени.
'''void''' B-Tree-Insert(T: '''BTree''', k: '''int'''):
r = T.root
'''else'''
B-Tree-Insert-Nonfull(r, k, T.t)
'''void''' B-Tree-Insert-Nonfull(x: '''Node''', k: '''int''', t: '''int'''):
i = x.n
i = i + 1
B-Tree-Insert-Nonfull(x.c[i], k, t)
Функция $\operatorname{B-Tree-Insert-Nonfull}$ вставляет ключ $k$ в узел $x$, который должен быть незаполненным при вызове.
Использование функции $\operatorname{B-Tree-Split-Child}$ гарантирует, что рекурсия не встретится с заполненным узлом.
Ниже показана вставка ключей B, Q, L и F в дерево с $t = 3$, т.е. узлы могут содержать не более 5 ключей
[[Файл:B3insa.png|550px]]
=== Разбиение узла ===
[[Файл:B3splt.PNG|550px|Разбиение узла B-дерева с t=4]]
'''void''' B-Tree-Split-Child(x: '''Node''', t: '''int''', i: '''int'''):
z = Allocate-Node()
Disk-Write(z)
Disk-Write(x)
=== Удаление ключа ===
==== Удаление ключа из листа ====
[[Файл:B3dell.PNG|550px|Удаление F из листа]]
В противном случае, если существует соседний лист с тем же родителем, который содержит больше $t - 1$ ключа, выберем ключ-разделитель из соседа разделяющий оставшиеся ключи соседа и ключи исходного узла (то есть не больше всех из одной группы и не меньше всех из другой). Обозначим этот ключ как $k_1$. Выберем другой ключ из родительского узла, разделяющий исходный узел и его соседа, который был выбран ранее. Этот ключ обозначим $k_2$. Удалим из исходного узла лист, который нужно было удалить, спустим в этот узел $k_2$, а вместо $k_2$ в родительском узле поставим $k_1$. Если все соседи содержат по $t - 1$ ключу, то [[B-дерево#.D0.A1.D0.BB.D0.B8.D1.8F.D0.BD.D0.B8.D0.B5|объединяем]] узел с каким-либо из соседей, удаляем ключ, и ключ из родительского узла, который был разделителем разделённых соседей, [[B-дерево#.D0.9F.D0.B5.D1.80.D0.B5.D0.BC.D0.B5.D1.89.D0.B5.D0.BD.D0.B8.D0.B5_.D0.BA.D0.BB.D1.8E.D1.87.D0.B0|переместим]] в новый узел.</wikitex>
==== Удаление ключа из внутреннего узла ====
[[Файл:B3delin.png|550px|Удаление M и G из внутренних узлов]]</wikitex>
==== Перемещение ключа ====
[[Файл:BTMv.png|450px|Перемещение ключа в B-дереве]]
Легко убедиться в том, что эти повороты поддерживают структуру B-дерева: для всех ключей $k$ на отложенном поддереве до и после перенесения выполняется условие $x.k_2 \leqslant k \leqslant x.c_3.k_1$. Симметричная операция может производиться для перенесения ключа из предшествующего брата.</wikitex>
==== Слияние ====
Итак, если выбранное для спуска поддерево $x.c_2$ и предшествующий и следующий узел-брат содержит минимальное количество ключей, то перемещение не возможно. На иллюстрации приводится слияние выбранного поддерева с предшествующим или следующим братом для такого случая. Для этого откладывается ключ из родительского узла $x$, который разделяет ключи на два сливаемых узла, в то время средний ключ перемещается в слитый узел. Ссылки на слитые дочерние узлы заменяются ссылкой на новый узел.
[[Файл:BTMg.png|450px|Слияние узла с братом]]
Так как алгоритм гарантирует, что узел, в который будет совершаться спуск, содержит по меньшей мере $t$ ключей вместо требуемых условиями B-дерева $t - 1$ ключей, родительский узел $x$ содержит достаточное количество ключей, чтобы выделить ключ для слияния. Это условие может быть нарушено, только в том случае, если два ребенка корня сливаются, так как поиск начинается с этого узла. По условиям B-дерева у корня должен быть как минимум один ключ, если дерево не пусто. При слиянии двух последних детей корня последний ключ перемещается во вновь возникшего единственного ребёнка, что приводит к пустому корневому узлу в не пустом дереве. В этом случае пустой узел корня удаляется и заменяется на единственного ребенка.</wikitex>
== Вариации B-дерева ==