1632
правки
Изменения
B-дерево
,rollbackEdits.php mass rollback
B-дерево было впервые предложено Р. Бэйером и Е. МакКрейтом в <tex>1970 году.</wikitextex>году.
== Структура ==
* Ключи в каждом узле упорядочены по неубыванию.
* Все листья находятся на одном уровне.
=== Структура узла ===
'''struct''' Node
'''bool''' leaf <span style="color:#008000"> // является ли узел листом</span>
'''int''' n <span style="color:#008000"> // количество ключей узла</span>
'''int''' key[] <span style="color:#008000"> // ключи узла</span>
'''Node''' c[] <span style="color:#008000"> // указатели на детей узла</span>
=== Структура дерева ===
'''struct''' BTree
'''int''' t <span style="color:#008000"> // минимальная степень дерева</span>
'''Node''' root <span style="color:#008000"> // указатель на корень дерева</span>
== Назначение ==
== Структуры данных во внешней памяти ==
Для того чтобы снизить время ожидания, связанное с механическим перемещением, при обращении к диску выполняется обращение одновременно сразу к нескольким элементам, хранящимся на диске. Информация разделяется на несколько страниц одинакового размера, которые хранятся последовательно друг за другом в пределах одного цилиндра (набора дорожек на дисках на одном расстоянии от центра), и каждая операция чтения или записи работает сразу с несколькими страницами. Для типичного диска размер страницы варьируется от $<tex>2$ </tex> до $<tex>16$ </tex> КБайт. После того, как головка установлена на нужную дорожку, а диск поворачивается так, что головка становится на начало интересующей нас страницы, чтение и запись становятся полностью электронными процессами, не зависящими от поворота диска, и диск может быстро читать или писать крупные объёмы данных.
В типичном приложении с B-деревом, объём хранимой информации так велик, что вся она просто не может храниться в основной памяти единовременно. Алгоритмы B-дерева копируют выбранные страницы с диска в основную память по мере надобности и записывает обратно на диск страницы, которые были изменены. Алгоритмы B-дерева хранят лишь определённое количество страниц в основной памяти в любой момент времени; таким образом, объём основной памяти не ограничивает размер B-деревьев, которые можно создавать.
Система в состоянии поддерживать в процессе работы в оперативной памяти только ограниченное количество страниц. Мы будем считать, что страницы, которые более не используются, удаляются из оперативной памяти системой; наши алгоритмы работы с В-деревьями не будут заниматься этим самостоятельно. Поскольку в большинстве систем время выполнения алгоритма, работающего с В-деревьями, зависит в первую очередь от количества выполняемых операций чтения/записи с диском, желательно минимизировать их количество и за один раз считывать и записывать как можно больше информации. Таким образом, размер узла В-дерева обычно соответствует дисковой странице. Количество потомков узла В-дерева, таким образом, ограничивается размером дисковой страницы.
Для больших В-деревьев, хранящихся на диске, степень ветвления обычно находится между <tex>50 </tex> и <tex>2000</tex>, в зависимости от размера ключа относительно размера страницы. Большая степень ветвления резко снижает как высоту дерева, так и количество обращений к диску для поиска ключа. Например, если есть миллиард ключей, и $<tex>t=1001$</tex>, то поиск ключа займёт две дисковые операции. </wikitex>
== Высота ==
|proof=
Корень B-дерева $<tex>T$ </tex> содержит по меньшей мере один ключ, а все остальные узлы — хотя бы $<tex>t - 1$ </tex> ключей. Так, <tex>T</tex>, высота которого $<tex>h$</tex>, имеет хотя бы $<tex>2$ </tex> узла на глубине $<tex>1$</tex>, хотя бы $<tex>2t$ </tex> узла на глубине $<tex>2$</tex>, хотя бы $<tex>2t^2$ </tex> узла на глубине $<tex>3$</tex>, и так далее, до глубины $<tex>h$</tex>, оно имеет по меньшей мере $<tex>2t^{h-1}$ </tex> узлов. Так, число ключей $<tex>n$ </tex> удовлетворяет неравенству:::$<tex>n \geqslant 1+(t-1)\sum\limits_{i = 1}^h 2t^{i-1} $</tex>:::$<tex>=1+2(t-1)(\fracdfrac{t^h-1}{t-1})$</tex>:::$<tex>=2t^h-1$</tex>.
Простейшее преобразование дает нам неравенство $<tex>t^h \leqslant (n+1)/2$</tex>. Логарифмирование по основанию $<tex>t$ </tex> обеих частей неравенства доказывает теорему
}}
Здесь мы видим преимущества B-деревьев над красно-черными деревьями. Хотя высота деревьев растет как $<tex>O(\log t)$ </tex> в обоих случаях (вспомним, что <tex>t </tex> — константа), в случае B-деревьев основание логарифмов имеет гораздо большее значение. Таким образом, В-деревья требуют исследования примерно в $<tex>\log t$ </tex> раз меньшего количества узлов по сравнению с красно-черными деревьями в большинстве операций. Поскольку исследование узла дерева обычно требует обращения к диску, количество дисковых операций при работе с В-деревьями оказывается существенно сниженным.</wikitex>
== Операции ==
B-деревья представляют собой сбалансированные деревья, поэтому время выполнения стандартных операций в них пропорционально высоте. Однако, как уже было упомянуто выше, алгоритмы B-дерева созданы специально для работы с дисками (или другими носителями информации) и базами данных (или иными видами представления большого количества информация), минимизируя количество операций ввода-вывода.
Если ключ содержится в текущем узле, возвращаем его. Иначе определяем интервал и переходим к соответствующему сыну. Повторяем пока ключ не найден или не дошли до листа.
=== Добавление ключа ===
Добавление ключа в B-дереве может быть осуществлена за один нисходящий проход от корня к листу. Для этого не нужно выяснять, требуется ли разбить узел, в который должен вставляться новый ключ. При проходе от корня к листьям в поисках места для нового ключа будут разбиваться все заполненные узлы, которые будут пройдены (включая и сам лист). Таким образом, если надо разбить какой-то полный узел, гарантируется, что его родительский узел не будет заполнен.
r = T.root
'''if ''' r.n == 2t 2T.t - 1
s = Allocate-Node()
T.root = s
s.leaf = FALSE''false''
s.n = 0
s.c[1] = r
B-Tree-Split-Child(s, T.t, 1) B-Tree-Insert-Nonfull(s, k, T.t) '''else''' B-Tree-Insert-Nonfull(r, k, T.t)
'''void''' B-Tree-Insert-Nonfull(x: '''Node''', k: '''int''', t: '''int'''):
i = x.n
'''if ''' x.leaf '''while (''' i >= 1) '''and (''' k < x.key[i]):
x.key[i+1] = x.key[i]
i = i - 1
x.n = x.n + 1
Disk-Write(x)
'''else ''' '''while (''' i >= 1) '''and (''' k < x.key[i]):
i = i - 1
i = i + 1
Disk-Read(x.c[i])
'''if ''' x.c[i].n == 2t - 1 B-Tree-Split-Child(x, t, i) '''if ''' k > x.key[i]
i = i + 1
B-Tree-Insert-Nonfull(x.c[i], k, t)
Функция $<tex>\operatorname{B-Tree-Insert-Nonfull}$ </tex> вставляет ключ $<tex>k$ </tex> в узел $<tex>x$</tex>, который должен быть незаполненным при вызове. Использование функции $<tex>\operatorname{B-Tree-Split-Child}$ </tex> гарантирует, что рекурсия не встретится с заполненным узлом.Ниже показана вставка ключей <tex>B</tex>, <tex>Q</tex>, <tex>L</tex> и <tex>F</tex> в дерево с <tex>t = 3</tex>, т.е. узлы могут содержать не более <tex>5</tex> ключей[[Файл:B3insa.png|550px]]
=== Разбиение узла ===
Функция <wikitextex>Функция $\operatorname{B-Tree-Split-Child}$ </tex> получает в качестве входного параметра незаполненный внутренний узел $<tex>x$ </tex> (находящийся в оперативной памяти), индекс $<tex>t$ </tex> и узел $<tex>y$ </tex> (также находящийся в оперативной памяти), такой что $<tex>y = c_i[x]$ </tex> является заполненным дочерним узлом $<tex>x$</tex>. Процедура разбивает дочерний узел на два и соответствующим образом обновляет поля $<tex>x$</tex>, внося в него информацию о новом дочернем узле. Для разбиения заполненного корневого узла мы сначала делаем корень дочерним узлом нового пустого корневого узла, после чего можно вызвать функцию. При этом высота дерева увеличивается на <tex>1</tex>. Отметим, что увеличить высоту B-дерева можно только разбиением.
[[Файл:B3splt.PNG|550px|Разбиение узла B-дерева с t=4]]
'''void''' B-Tree-Split-Child(x: '''Node''', t: '''int''', i: '''int'''):
z = Allocate-Node()
y = x.c[i]
z.leaf = y.leaf
z.n = t - 1
'''for ''' j = 1 '''to ''' t - 1:
z.key[j] = y.key[j+t]
'''if ''' not y.leaf '''for ''' j = 1 '''to ''' t:
z.c[j] = y.c[j+t]
y.n = t - 1
'''for ''' j = x.n + 1 '''to ''' i + 1:
x.c[j+1] = x.c[j]
x.c[i+1] = z
'''for ''' j = x.n '''to ''' i:
x.key[j+1] = x.key[j]
x.key[i] = y.key[t]
Disk-Write(z)
Disk-Write(x)
=== Удаление ключа ===
==== Удаление ключа из листа ====
[[Файл:B3dell.PNG|500px550px|Удаление <tex>F </tex> из листа]]В противном случае, если существует соседний лист с тем же родителем, который содержит больше $<tex>t - 1$ </tex> ключа, выберем ключ-разделитель из соседа разделяющий оставшиеся ключи соседа и ключи исходного узла (то есть не больше всех из одной группы и не меньше всех из другой). Обозначим этот ключ как $<tex>k_1$</tex>. Выберем другой ключ из родительского узла, разделяющий исходный узел и его соседа, который был выбран ранее. Этот ключ обозначим $<tex>k_2$</tex>. Удалим из исходного узла листключ, который нужно было удалить, спустим в этот узел $<tex>k_2$</tex>, а вместо $<tex>k_2$ </tex> в родительском узле поставим $<tex>k_1$</tex>. Если все соседи содержат по $<tex>t - 1$ </tex> ключу, то [[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|450px550px|Удаление M и G из внутренних узлов]]</wikitex>
==== Перемещение ключа ====
==== Слияние ====
== Вариации B-дерева ==
=== B+-дерево ===
В B-дереве вместе с ключом может храниться только указатель на другую дисковую страницу, содержащую сопутствующую информацию для данного ключа. Существует распространённая модификация B-дерева, называемая [[B+-дерево|B+-деревом]], в которой, вся сопутствующая информация хранится в листьях, а во внутренних узлах хранятся только ключи и указатели на дочерние узлы. Таким образом удается получить максимально возможную степень ветвления во внутренних узлах.
=== B*-дерево ===
Распространённая модификация B-дерева, в которой каждый внутренний узел должен быть заполнен как минимум на две трети, а не наполовину, как в случае со стандартным B-деревом. Используется в файловых системах HFS и Reiser4. В отличие от B+-деревьев, узел не разбивается на <tex>2 </tex> узла, если полностью заполнен. Вместо этого ищется место в уже существующем соседнем узле, и только после того, как оба узла будут заполнены, они разделяются на три узла.
=== 2-3 дерево ===
Производное от B+-дерева. Каждый узел может иметь либо <tex>2</tex>, либо <tex>3 </tex> ребёнка.==== См.также ====:*[[2-3 дерево]]* [[B+-дерево]]* [[Splay-дерево]]* [[АВЛ-дерево]]* [[Красно-черное дерево]]
== Ссылки Источники информации ==
* T. H. Cormen «Introduction to Algorithms» third edition, Chapter 18
* Т. Кормен «Алгоритмы: построение и анализ» второе издание, глава 18
* Д. Кнут «Искусство программирования. Сортировка и поиск», часть 6.2.4
* [http://habrahabr.ru/post/114154/ Хабрахабрhabrahabr. ru {{---}} B-tree]* [http://de.wikipedia.org/wiki/B-Baum Wikipedia {{---}} B-Baum]
* [http://citforum.ru/programming/theory/sorting/sorting2.shtml#5 Методы сортировки и поиска. Методы поиска во внешней памяти]
* [http://www.ibm.com/developerworks/ru/library/l-data_structures_10/ IBM. developerWorks. «Работа со структурами данных в языках Си и Python: Часть 10. B-деревья и TRIE-деревья»]
* [http://www.minet.uni-jena.de/dbis/lehre/ws2005/dbs1/Bayer_hist.pdf R. Bayer, E. McCreight «Organization and Maintenance of Large Ordered Indexes», Acta Informatica, 1972]
[[Категория:Дискретная математика и алгоритмы]]
[[Категория: Структуры данных]]
[[Категория:Деревья поиска]]