Декартово дерево — различия между версиями
(→Высота в декартовом дереве) |
(→Построение декартово дерева за O(n) для заданного набора ключей) |
||
| Строка 128: | Строка 128: | ||
# Результат процедуры <tex>Merge</tex> ставим на место удаляемого элемента. | # Результат процедуры <tex>Merge</tex> ставим на место удаляемого элемента. | ||
| − | == Построение декартово дерева | + | == Построение декартово дерева из заданного набора элементов == |
Пусть нам известно из каких пар <tex>(x_i, y_i)</tex> требуется построить декартово дерево, причем также известно, что <tex>x_1 < x_2 < \ldots < x_n</tex>. | Пусть нам известно из каких пар <tex>(x_i, y_i)</tex> требуется построить декартово дерево, причем также известно, что <tex>x_1 < x_2 < \ldots < x_n</tex>. | ||
| + | === Простой алгоритм построения через рекурсию === | ||
| + | Рассмотрим набор <tex>y_1 , y_2 , \ldots , y_n</tex>, выберем максимум среди них, пусть это будет <tex>y_k</tex>, и сделаем <tex>(x_k, y_k)</tex> корнем дерева (по свойству пирамиды в корне должен быть элемент с максимальным приоритетом). Проделав тоже самое с <tex>y_1 , y_2 , \ldots , y_{k-1}</tex> и <tex>y_{k+1} , y_{k+2} , \ldots , y_n</tex>, получим соответственно левого и правого сына <tex>(x_k, y_k)</tex>. С полученными наборами поступаем аналогично. | ||
| − | Будем строить дерево слева направо, то есть начиная с <tex>(x_1, y_1)</tex> по <tex>(x_n, y_n)</tex>, при этом помнить последний добавленный элемент <tex>(x_k, y_k)</tex>. Он будет самым правым, так как у него будет максимальный ключ, а по ключам декартово дерево представляет собой [[Дерево поиска, наивная реализация|двоичное дерево поиска]]. При добавлении <tex>(x_{k+1}, y_{k+1})</tex>, пытаемся сделать его правым сыном <tex>(x_k, y_k)</tex>, это следует сделать если <tex>y_k | + | Данный алгоритм построения декартово дерева основан на рекурсии: находим в наборе максимальный <tex>y_k</tex> и назначаем его корнем, найденный <tex>y_k</tex> разбивает набор на два, для каждого из полученного непустого набора запускаем алгоритм построения декартово дерева. |
| + | |||
| + | === Алгоритм за O(n) === | ||
| + | Будем строить дерево слева направо, то есть начиная с <tex>(x_1, y_1)</tex> по <tex>(x_n, y_n)</tex>, при этом помнить последний добавленный элемент <tex>(x_k, y_k)</tex>. Он будет самым правым, так как у него будет максимальный ключ, а по ключам декартово дерево представляет собой [[Дерево поиска, наивная реализация|двоичное дерево поиска]]. При добавлении <tex>(x_{k+1}, y_{k+1})</tex>, пытаемся сделать его правым сыном <tex>(x_k, y_k)</tex>, это следует сделать если <tex>y_k > y_{k+1}</tex>, иначе делаем шаг ''по склону вверх'', то есть к предку последнего элемента и смотрим его значение <tex>y</tex>. Поднимаемся до тех пор, пока приоритет в рассматриваемом элементе меньше приоритета в добавляемом, после чего делаем <tex>(x_{k+1}, y_{k+1})</tex> его правым сыном, а предыдущего правого сына делаем левым сыном <tex>(x_{k+1}, y_{k+1})</tex>. | ||
Заметим, что каждую вершину мы посетим максимум дважды: при непосредственном добавлении и, поднимаясь по склону вверх (ведь после этого вершина будет лежать в чьем-то левом поддереве, а мы поднимаемся только по правому). Из этого следует, что построение происходит за <tex>O(n)</tex>. | Заметим, что каждую вершину мы посетим максимум дважды: при непосредственном добавлении и, поднимаясь по склону вверх (ведь после этого вершина будет лежать в чьем-то левом поддереве, а мы поднимаемся только по правому). Из этого следует, что построение происходит за <tex>O(n)</tex>. | ||
Версия 11:32, 17 апреля 2012
Эта статья про Курево
Декартово дерево — это структура данных, объединяющая в себе бинарное дерево поиска и бинарную кучу (отсюда и второе её название: treap (tree + heap) и дерамида (дерево + пирамида), так же существует название курево (куча + дерево).
Более строго, это бинарное дерево, в узлах которого хранится пары , где - это ключ, а - это приоритет. Также оно является двоичным деревом поиска по и пирамидой по . Предполагая, что все и все являются различными, получаем, что если некоторый элемент дерева содержит , то у всех элементов в левом поддереве , у всех элементов в правом поддереве , а также и в левом, и в правом поддереве имеем: .
Дерамиды были предложены Сиделем (Siedel) и Арагоном (Aragon) в 1996 г.
Содержание
Операции в декартовом дереве
Split
Операция (разрезать) позволяет сделать следующее: разрезать декартово дерево по ключу и получить два других декартовых дерева: и , причем в находятся все ключи дерева , не большие , а в — большие .
.
Эта операция устроена следующим образом.
Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня. Посмотрим, как будут устроены результирующие деревья и :
- : левое поддерево совпадёт с левым поддеревом . Для нахождения правого поддерева , нужно разрезать правое поддерево на и по ключу и взять .
- совпадёт с .
Случай, в котором требуется разрезать дерево по ключу, меньше либо равному ключа в корне, рассматривается симметрично.
Псевдокод:
Treap T // декартово дерево
int k // ключ по которому нужно разрезать декартово дерево
Split (Treap T, int k, Treap T1, Treap T2) { // T1, T2 - результат процедуры Split
if (T == NULL) {
T1 = T2 = NULL
}
else if (k > T.x) {
Split (T.right, k, T.right, T2)
T1 = T
}
else {
Split (T.left, k, T1, T.left)
T2 = T
}
}
Оценим время работы операции . Во время выполнения вызывается одна операция для дерева хотя бы на один меньшей высоты и делается ещё операция. Тогда итоговая трудоёмкость этой операции равна , где — высота дерева.
Merge
Рассмотрим вторую операцию с декартовыми деревьями — (слить).
С помощью этой операции можно слить два декартовых дерева в одно. Причем, все ключи в первом(левом) дереве должны быть меньше, чем ключи во втором(правом). В результате получается дерево, в котором есть все ключи из первого и второго деревьев.
Рассмотрим принцип работы этой операции. Пусть нужно слить деревья и . Тогда, очевидно, у результирующего дерева есть корень. Корнем станет вершина из или с наибольшим ключом . Но вершина с самым большим из всех вершин деревьев и может быть только либо корнем , либо корнем . Рассмотрим случай, в котором корень имеет больший , чем корень . Случай, в котором корень имеет больший , чем корень , симметричен этому.
Если корня больше корня , то он и будет являться корнем. Тогда левое поддерево совпадёт с левым поддеревом . Справа же нужно подвесить объединение правого поддерева и дерева .
Псевдокод:
Treap T // результат процедуры Merge
Treap T1, T2 // сливаемые деревья
Merge (Treap T, Treap T1, Treap T2) {
if (T1 == NULL or T2 == NULL) {
if (T1 != NULL) {
T = T1
}
else {
T = T2
}
}
else if (T1.y > T2.y) {
Merge (T1.right, T1.right, T2)
T = T1
}
else {
Merge (T2.left, T1, T2.left)
T = T2
}
}
Рассуждая аналогично операции приходим к выводу, что трудоёмкость операции равна , где — высота дерева.
Insert
Операция добавляет в дерево элемент , где — ключ, а — приоритет.
- Реализация №1
- Разобьём наше дерево по ключу, который мы хотим добавить, то есть .
- Сливаем первое дерево с новым элементом, то есть .
- Сливаем получившиеся дерево со вторым, то есть .
- Реализация №2
- Сначала спускаемся по дереву (как в обычном бинарном дереве поиска по ), но останавливаемся на первом элементе, в котором значение приоритета оказалось меньше .
- Теперь вызываем от найденного элемента (от элемента вместе со всем его поддеревом)
- Полученные и записываем в качестве левого и правого сына добавляемого элемента.
- Полученное дерево ставим на место элемента, найденного в первом пункте.
Remove
Операция удаляет из дерева элемент с ключом .
- Реализация №1
- Разобьём наше дерево по ключу, который мы хотим удалить, то есть .
- Теперь отделяем от первого дерева элемент , опять таки разбивая по ключу , то есть .
- Сливаем первое дерево со вторым, то есть .
- Реализация №2
- Спускаемся по дереву (как в обычном бинарном дереве поиска по ), ища удаляемый элемент.
- Найдя элемент, вызываем его левого и правого сыновей
- Результат процедуры ставим на место удаляемого элемента.
Построение декартово дерева из заданного набора элементов
Пусть нам известно из каких пар требуется построить декартово дерево, причем также известно, что .
Простой алгоритм построения через рекурсию
Рассмотрим набор , выберем максимум среди них, пусть это будет , и сделаем корнем дерева (по свойству пирамиды в корне должен быть элемент с максимальным приоритетом). Проделав тоже самое с и , получим соответственно левого и правого сына . С полученными наборами поступаем аналогично.
Данный алгоритм построения декартово дерева основан на рекурсии: находим в наборе максимальный и назначаем его корнем, найденный разбивает набор на два, для каждого из полученного непустого набора запускаем алгоритм построения декартово дерева.
Алгоритм за O(n)
Будем строить дерево слева направо, то есть начиная с по , при этом помнить последний добавленный элемент . Он будет самым правым, так как у него будет максимальный ключ, а по ключам декартово дерево представляет собой двоичное дерево поиска. При добавлении , пытаемся сделать его правым сыном , это следует сделать если , иначе делаем шаг по склону вверх, то есть к предку последнего элемента и смотрим его значение . Поднимаемся до тех пор, пока приоритет в рассматриваемом элементе меньше приоритета в добавляемом, после чего делаем его правым сыном, а предыдущего правого сына делаем левым сыном .
Заметим, что каждую вершину мы посетим максимум дважды: при непосредственном добавлении и, поднимаясь по склону вверх (ведь после этого вершина будет лежать в чьем-то левом поддереве, а мы поднимаемся только по правому). Из этого следует, что построение происходит за .
Случайные приоритеты
Мы уже выяснили, что сложность операций с декартовым деревом линейно зависит от его высоты. В действительности высота декартова дерева может быть линейной относительно его размеров. Например, высота декартова дерева, построенного по набору ключей , будет равна . Во избежание таких случаев, полезным оказывается выбирать приоритеты в ключах случайно.
Высота в декартовом дереве с случайными приоритетами
| Теорема: | ||||||
Декартово дерево из узлов, ключи которых являются независимыми случайными величинами одного и того же распределения, имеет высоту . | ||||||
| Доказательство: | ||||||
|
Для начала введем несколько обозначений:
В силу обозначений глубину вершины можно записать как количество предков:
Теперь можно выразить математическое ожидание глубины конкретной вершины:
Для подсчёта средней глубины вершин нам нужно сосчитать вероятность того, что вершина является предком вершины , то есть . Введем новое обозначение:
Так как каждая вершина среди может иметь минимальный приоритет, мы немедленно приходим к следующему равенству: Подставив последнее в нашу формулу с математическим ожиданием получим:
| ||||||