Декартово дерево — различия между версиями
(→Split) |
(→Split) |
||
Строка 12: | Строка 12: | ||
Операция <tex>\mathrm{Split}</tex> (''разрезать'') позволяет сделать следующее: разрезать декартово дерево <tex>T</tex> по ключу | Операция <tex>\mathrm{Split}</tex> (''разрезать'') позволяет сделать следующее: разрезать декартово дерево <tex>T</tex> по ключу | ||
− | <tex> | + | <tex>k</tex> и получить два других декартовых дерева: <tex>T_1</tex> и <tex>T_2</tex>, причем в <tex>T_1</tex> |
− | находятся все ключи дерева <tex>T</tex>, не большие <tex> | + | находятся все ключи дерева <tex>T</tex>, не большие <tex>k</tex>, а в <tex>T_2</tex> {{---}} большие <tex>k</tex>. |
− | <tex>\mathrm{Split}(T, | + | <tex>\mathrm{Split}(T, k) \to \{T_1, T_2\}</tex>. |
Эта операция устроена следующим образом. | Эта операция устроена следующим образом. | ||
Строка 21: | Строка 21: | ||
Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня. | Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня. | ||
Посмотрим, как будут устроены результирующие деревья <tex>T_1</tex> и <tex>T_2</tex>: | Посмотрим, как будут устроены результирующие деревья <tex>T_1</tex> и <tex>T_2</tex>: | ||
− | * <tex>T_1</tex>: левое поддерево <tex>T_1</tex> совпадёт с левым поддеревом <tex>T</tex>. Для нахождения правого поддерева <tex>T_1</tex>, нужно разрезать правое поддерево <tex>T</tex> на <tex>T^R_1</tex> и <tex>T^R_2</tex> по ключу <tex> | + | * <tex>T_1</tex>: левое поддерево <tex>T_1</tex> совпадёт с левым поддеревом <tex>T</tex>. Для нахождения правого поддерева <tex>T_1</tex>, нужно разрезать правое поддерево <tex>T</tex> на <tex>T^R_1</tex> и <tex>T^R_2</tex> по ключу <tex>k</tex> и взять <tex>T^R_1</tex>. |
* <tex>T_2</tex> совпадёт с <tex>T^R_2</tex>. | * <tex>T_2</tex> совпадёт с <tex>T^R_2</tex>. | ||
Строка 29: | Строка 29: | ||
<pre> | <pre> | ||
Treap T // декартово дерево | Treap T // декартово дерево | ||
− | Node | + | Node k // ключ по которому нужно разрезать декартово дерево |
− | Split (Treap T, Node | + | Split (Treap T, Node k, Treap T1, Treap T2) { // T1, T2 - результат процедуры Split |
if (T == NULL) { | if (T == NULL) { | ||
T1 = T2 = NULL | T1 = T2 = NULL | ||
} | } | ||
− | else if (x | + | else if (k.x > T.x) { |
− | Split (T.right, | + | Split (T.right, k, T.right, T2) |
T1 = T | T1 = T | ||
} | } | ||
else { | else { | ||
− | Split (T.left, | + | Split (T.left, k, T1, T.left) |
T2 = T | T2 = T | ||
} | } |
Версия 00:01, 15 апреля 2012
Эта статья про Курево
Декартово дерево — это структура данных, объединяющая в себе бинарное дерево поиска и бинарную кучу (отсюда и второе её название: treap (tree + heap) и дерамида (дерево + пирамида), так же существует название курево (куча + дерево).
Более строго, это бинарное дерево, в узлах которого хранится пары двоичным деревом поиска по и пирамидой по . Предполагая, что все и все являются различными, получаем, что если некоторый элемент дерева содержит , то у всех элементов в левом поддереве , у всех элементов в правом поддереве , а также и в левом, и в правом поддереве имеем: .
, где - это ключ, а - это приоритет. Также оно являетсяДерамиды были предложены Сиделем (Siedel) и Арагоном (Aragon) в 1996 г.
Содержание
Операции в декартовом дереве
Split
Операция
(разрезать) позволяет сделать следующее: разрезать декартово дерево по ключу и получить два других декартовых дерева: и , причем в находятся все ключи дерева , не большие , а в — большие ..
Эта операция устроена следующим образом.
Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня. Посмотрим, как будут устроены результирующие деревья
и :- : левое поддерево совпадёт с левым поддеревом . Для нахождения правого поддерева , нужно разрезать правое поддерево на и по ключу и взять .
- совпадёт с .
Случай, в котором требуется разрезать дерево по ключу, меньше либо равному ключа в корне, рассматривается симметрично.
Псевдокод:
Treap T // декартово дерево Node k // ключ по которому нужно разрезать декартово дерево Split (Treap T, Node k, Treap T1, Treap T2) { // T1, T2 - результат процедуры Split if (T == NULL) { T1 = T2 = NULL } else if (k.x > T.x) { Split (T.right, k, T.right, T2) T1 = T } else { Split (T.left, k, T1, T.left) T2 = T } }
Оценим время работы операции
. Во время выполнения вызывается одна операция для дерева хотя бы на один меньшей высоты и делается ещё операция. Тогда итоговая трудоёмкость этой операции равна , где — высота дерева.Merge
Рассмотрим вторую операцию с декартовыми деревьями —
(слить).С помощью этой операции можно слить два декартовых дерева в одно. Причем, все ключи в первом(левом) дереве должны быть меньше, чем ключи во втором(правом). В результате получается дерево, в котором есть все ключи из первого и второго деревьев.
Рассмотрим принцип работы этой операции. Пусть нужно слить деревья
и . Тогда, очевидно, у результирующего дерева есть корень. Корнем станет вершина из или с наибольшим ключом . Но вершина с самым большим из всех вершин деревьев и может быть только либо корнем , либо корнем . Рассмотрим случай, в котором корень имеет больший , чем корень . Случай, в котором корень имеет больший , чем корень , симметричен этому.Если
корня больше корня , то он и будет являться корнем. Тогда левое поддерево совпадёт с левым поддеревом . Справа же нужно подвесить объединение правого поддерева и дерева .Рассуждая аналогично операции
приходим к выводу, что трудоёмкость операции равна , где — высота дерева.Insert
Операция
добавляет в дерево элемент , где — ключ, а — приоритет.- Реализация №1
- Разобьём наше дерево по ключу, который мы хотим добавить, то есть .
- Сливаем первое дерево с новым элементом, то есть .
- Сливаем получившиеся дерево со вторым, то есть .
- Реализация №2
- Сначала спускаемся по дереву (как в обычном бинарном дереве поиска по ), но останавливаемся на первом элементе, в котором значение приоритета оказалось меньше .
- Теперь вызываем от найденного элемента (от элемента вместе со всем его поддеревом)
- Полученные и записываем в качестве левого и правого сына добавляемого элемента.
- Полученное дерево ставим на место элемента, найденного в первом пункте.
Remove
Операция
удаляет из дерева элемент с ключом .- Реализация №1
- Разобьём наше дерево по ключу, который мы хотим удалить, то есть .
- Теперь отделяем от первого дерева элемент , опять таки разбивая по ключу , то есть .
- Сливаем первое дерево со вторым, то есть .
- Реализация №2
- Спускаемся по дереву (как в обычном бинарном дереве поиска по ), ища удаляемый элемент.
- Найдя элемент, вызываем его левого и правого сыновей
- Возвращаемое значение функции ставим на место удаляемого элемента.
Случайные ключи
Мы уже выяснили, что сложность операций с декартовым деревом линейно зависит от его высоты. В действительности высота декартова дерева может быть линейной относительно его размеров. Например, высота декартова дерева, построенного по набору ключей
, будет равна . Во избежание таких случаев, полезным оказывается выбирать приоритеты в ключах случайно.Высота в декартовом дереве
Теорема: | ||||||
Декартово дерево из случайными величинами одного и того же распределения, имеет высоту . узлов, ключи которых являются независимыми | ||||||
Доказательство: | ||||||
Для начала введем несколько обозначений:
В силу обозначений глубину вершины можно записать как количество предков:
Теперь можно выразить математическое ожидание глубины конкретной вершины:
Для подсчёта средней глубины вершин нам нужно сосчитать вероятность того, что вершина является предком вершины , то есть .Введем новое обозначение:
Так как каждая вершина среди может иметь минимальный приоритет, мы немедленно приходим к следующему равенству:Подставив последнее в нашу формулу с математическим ожиданием получим: | ||||||