Изменения

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

Декартово дерево

1654 байта добавлено, 19:35, 4 сентября 2022
м
rollbackEdits.php mass rollback
''Эта статья про Куревокурево''
'''Декартово деревоили дерамида''' (англ. ''Treap'') {{---}} это структура данных, объединяющая в себе [[Дерево поиска, наивная реализация|бинарное дерево поиска ]] и [[Двоичная куча|бинарную кучу ]] (отсюда и второе её название: treap (tree + heap) и дерамида (дерево + пирамида), так же также существует название курево (куча + дерево).
Более строго, это бинарное дерево, в узлах которого хранится хранятся пары <tex> (x,y) </tex>, где <tex>x</tex> {{- --}} это ключ, а <tex>y</tex> {{--- }} это приоритет. Также оно является [[Дерево поиска, наивная реализация|двоичным деревом поиска]] по <tex>x</tex> и [[Двоичная куча|пирамидой]] по <tex>y</tex>. Предполагая, что все <tex>x</tex> и все <tex>y</tex> являются различными, получаем, что если некоторый элемент дерева содержит <tex>(x_0,y_0)</tex>, то у всех элементов в левом поддереве <tex>x < x_0</tex>, у всех элементов в правом поддереве <tex> x > x_0</tex>, а также и в левом, и в правом поддереве имеем: <tex> y < y_0</tex>.
Дерамиды были предложены Сиделем (Siedel) и Арагоном Арагон (Aragon) в 1996 г.
== Операции в декартовом дереве ==
=== Split split ===
[[file:split.png|thumb|400px|Операция split]]
Операция <tex>\mathrm{Splitsplit}</tex> (''разрезать'') позволяет сделать следующее: разрезать декартово исходное дерево <tex>T</tex> по ключу <tex>k</tex> и получить два других декартовых дерева: . Возвращать она будет такую пару деревьев <tex>\langle T_1</tex> и <tex>, T_2\rangle </tex>, причем что в дереве <tex>T_1</tex>находятся все ключи дерева <tex>T</tex>, не большие меньше <tex>k</tex>, а в дереве <tex>T_2</tex> {{---}} большие <tex>k</tex>. все остальные: <tex>\mathrm{Splitsplit}(T, k) \to \{langle T_1, T_2\}rangle </tex>.
Эта операция устроена следующим образом.
Случай, в котором требуется разрезать дерево по ключу, меньше либо равному ключа в корне, рассматривается симметрично.
=== Псевдокод:===  '''<pretex>Split\langle</tex>Treap, Treap<tex>\rangle</tex>''' split(t: '''Treap t''', k: '''int k, Treap &t1, Treap &t2'''): '''if ''' t == NULL <tex> \varnothing </tex> '''return''' <tex>\langle</tex><tex> \varnothing </tex>, <tex> \varnothing </tex><tex>\rangle</tex> t1 = t2 = NULL; '''else if ''' k > Tt.x Split <tex>\langle</tex>t1, t2<tex>\rangle</tex> = split(t.right, k, ) t.right= t1 '''return''' <tex>\langle</tex>t, t2);<tex>\rangle</tex> '''else''' <tex>\langle</tex>t1 , t2<tex>\rangle</tex> = t; else Splitsplit(t.left, k, t1, ) t.left);= t2 t2 = '''return''' <tex>\langle</tex>t1, t;<tex>\rangle</pretex=== Время работы ===
Оценим время работы операции <tex>\mathrm{Splitsplit}</tex>. Во время выполнения вызывается одна операция <tex>\mathrm{Splitsplit}</tex> длядерева хотя бы на один меньшей высоты и делается ещё <tex>O(1)</tex> операцияопераций. Тогда итоговая трудоёмкость этой операции
равна <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
=== Merge merge ===
[[file:merge.png|thumb|400px|Операция merge]]
Рассмотрим вторую операцию с декартовыми деревьями {{---}} <tex>\mathrm{Mergemerge}</tex> (''слить'').
С помощью этой операции можно слить два декартовых дерева в одно.
ПричемПричём, все ключи в первом(''левом'') дереве должны быть меньше, чемключи во втором(''правом''). В результате получается дерево, в котором есть все ключи из первого и второго деревьев. : <tex>\mathrm{Mergemerge}(T_1, T_2) \to \{T\}</tex>
Рассмотрим принцип работы этой операции. Пусть нужно слить деревья <tex>T_1</tex> и <tex>T_2</tex>.
Тогда, очевидно, у результирующего дерева <tex>T</tex> есть корень.
Корнем станет вершина из <tex>T_1</tex> или <tex>T_2</tex> с наибольшим ключом приоритетом <tex>y</tex>. Но вершина с самым большим <tex>y</tex> из всех вершин деревьев
<tex>T_1</tex> и <tex>T_2</tex> может быть только либо корнем <tex>T_1</tex>, либо корнем <tex>T_2</tex>.
Рассмотрим случай, в котором корень <tex>T_1</tex> имеет больший <tex>y</tex>, чем корень <tex>T_2</tex>.
<tex>T_1</tex> и дерева <tex>T_2</tex>.
=== Псевдокод:===<pre>Merge '''Treap''' merge(t1: '''Treap &t, Treap t1''', t2: '''Treap t2'''): '''if t1 == NULL or ''' t2 == NULL <tex> \varnothing </tex> '''return''' t1 '''if ''' t1 != NULL t = t1; else<tex> \varnothing </tex> t = '''return''' t2; '''else if ''' t1.y > t2.y Merge( t1.right, = merge(t1.right, t2); t = '''return''' t1; '''else ''' Merge( t2.left, = merge(t1, t2.left); t = '''return''' t2;</pre>=== Время работы ===
Рассуждая аналогично операции <tex>\mathrm{Splitsplit}</tex> , приходим к выводу, что трудоёмкость операции <tex>\mathrm{Mergemerge}</tex>
равна <tex>O(h)</tex>, где <tex>h</tex> {{---}} высота дерева.
=== Insert insert ===Операция <tex>\mathrm{Insertinsert}(T, k)</tex> добавляет в дерево <tex>T</tex> элемент <tex>k</tex>, где <tex>k.x</tex> {{---}} ключ, а <tex>k.y</tex>{{---}} приоритет.
Представим что элемент <tex>k</tex>, это декартово дерево из одного элемента, и для того чтобы его добавить в наше декартово дерево <tex>T</tex>, очевидно, нам нужно их слить. Но <tex>T</tex> может содержать ключи как меньше, так и больше ключа <tex>k.x</tex>, поэтому сначала нужно разрезать <tex>T</tex> по ключу <tex>k.x</tex>.
* Реализация №1
# Разобьём наше дерево по ключу, который мы хотим добавить, то есть <tex>\mathrm{Splitsplit}(T, k.x) \to \{langle T_1, T_2\}rangle</tex>.# Сливаем первое дерево с новым элементом, то есть <tex>\mathrm{Mergemerge}(T_1, k) \to T_1</tex>.# Сливаем получившиеся дерево со вторым, то есть <tex>\mathrm{Mergemerge}(T_1, T_2) \to T</tex>.
* Реализация №2
# Сначала спускаемся по дереву (как в обычном бинарном дереве поиска по <tex>k.x</tex>), но останавливаемся на первом элементе, в котором значение приоритета оказалось меньше <tex>k.y</tex>.
# Теперь вызываем <tex>\mathrm{Splitsplit}(T, k.x) \to \{langle T_1, T_2\}rangle</tex> от найденного элемента (от элемента вместе со всем его поддеревом)
# Полученные <tex>T_1</tex> и <tex>T_2</tex> записываем в качестве левого и правого сына добавляемого элемента.
# Полученное дерево ставим на место элемента, найденного в первом пункте.
В первой реализации два раза используется <tex>\mathrm{Mergemerge}</tex>, а во второй реализации слияние вообще не используется.
=== Remove remove ===Операция <tex>\mathrm{Removeremove}(T, x)</tex> удаляет из дерева <tex>T</tex> элемент с ключом <tex>x</tex>.
* Реализация №1
# Разобьём наше дерево по ключу, который мы хотим удалить, то есть <tex>\mathrm{Split split }(T, k.x) \to \{langle T_1, T_2\}rangle</tex>.# Теперь отделяем от первого дерева элемент <tex>x</tex>, опять таки разбивая по ключу <tex>x</tex>, то есть самого левого ребёнка дерева <tex>\mathrm{Split }(T_1, k.x - \varepsilon) \to \{T_1, T_3\}T_2 </tex>.# Сливаем первое дерево со вторым, то есть <tex>\mathrm{Merge merge }(T_1, T_2) \to T</tex>.
* Реализация №2
# Спускаемся по дереву (как в обычном бинарном дереве поиска по <tex>x</tex>), ища и ищем удаляемый элемент. # Найдя элемент, вызываем <tex>\mathrm{Mergemerge}</tex> его левого и правого сыновей# Результат процедуры <tex>\mathrm{Mergemerge}</tex> ставим на место удаляемого элемента.
В первой реализации два раза один раз используется <tex>\mathrm{Splitsplit}</tex>, а во второй реализации разрезание вообще не используется.
== Построение декартова дерева ==
Пусть нам известно из каких пар <tex>(x_i, y_i)</tex> требуется построить декартово дерево, причем причём также известно, что <tex>x_1 < x_2 < \ldots < x_n</tex>.=== Рекурсивный алгоритм Алгоритм за <tex>O(n\log n)</tex> ===Рассмотрим Отсортируем все приоритеты по убыванию за <tex>y_1 , y_2 , O(n\ldots , y_nlog n) </tex> и выберем максимум среди первый из них, пусть это будет <tex>y_k</tex>, и сделаем . Сделаем <tex>(x_k, y_k)</tex> корнем дерева (по свойству [[Двоичная куча|пирамиды]] в корне должен быть элемент с максимальным приоритетом). Проделав то же самое с остальными вершинами получим левого и правого сына <tex>y_1 , y_2 (x_k, y_k)</tex>. В среднем высота Декартова дерева <tex>\ldots , y_{k-1}log n</tex> (см. далее) и на каждом уровне мы сделали <tex>y_{k+1} , y_{k+2} , \ldots , y_nO(n)</tex>, получим соответственно левого и правого сына операций. Значит такой алгоритм работает за <tex>O(x_k, y_kn\log n)</tex>. 
Такой === Другой алгоритм работает за <tex>O(n^\log n)</tex> ===Отсортируем пары <tex>(x_i, y_i)</tex> по убыванию <tex>x_i</tex> и положим их в очередь. Сперва достанем из очереди первые <tex>2</tex> элемента и сольём их в дерево и положим в конец очереди, затем сделаем то же самое со следующими двумя и т.д. Таким образом, мы сольём сначала <tex>n</tex> деревьев размера <tex>1</tex>, затем <tex>\dfrac{n}{2}</tex> деревьев размера <tex>2</tex> и так далее. При этом на уменьшение размера очереди в два раза мы будем тратить суммарно <tex>O(n)</tex> время на слияния, а всего таких уменьшений будет <tex>\log n</tex>. Значит полное время работы алгоритма будет <tex>O(n\log n)</tex>.
=== Алгоритм за <tex>O(n) </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 > 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>y</tex> попарно различны.
Для начала введем введём несколько обозначений:
* <tex>x_k</tex> {{---}} вершина с <tex>k</tex>-ым по величине ключом;
* индикаторная величина <tex>A_{i, j} = \left\{\begin{array}{lllc} 1 ,&&, x_i\ \text{is ancestor of} \ x_j\\ 0 ,&&, \text{otherwise}\\
\end{array}\right.
</tex>
* <tex>d(v)</tex> {{- --}} глубина вершины <tex>v</tex>;
В силу обозначений глубину вершины можно записать как количество предков:
Для подсчёта средней глубины вершин нам нужно сосчитать вероятность того, что вершина <tex>x_i</tex> является предком вершины <tex>x_k</tex>, то есть <tex>Pr[A_{i,k} = 1]</tex>.
Введем Введём новое обозначение:
* <tex>X_{i, k}</tex> {{---}} множество ключей <tex>\{x_i, \ldots, x_k\}</tex> или <tex>\{x_k, \ldots, x_i\}</tex>, в зависимости от <tex>i < k</tex> или <tex>i > k</tex>. <tex>X_{i, k}</tex> и <tex>X_{k, i}</tex> обозначают одно и тоже, их мощность равна <tex>|k - i| + 1</tex>.
{{Лемма
|statement=Для любых <tex>i \ne k</tex> , <tex>x_i</tex> является предком <tex>x_k</tex> тогда и только тогда, когда <tex>x_i</tex> имеет наименьший наибольший приоритет среди <tex>X_{i, k}</tex>.|proof=Если <tex>x_i</tex> является корнем, то оно является предком <tex>x_k</tex> и по определению имеет минимальный максимальный приоритет среди всех вершин, следовательно, и среди <tex>X_{i, k}</tex>.
С другой стороны, если <tex>x_k</tex> {{---}} корень, то <tex>x_i</tex> {{---}} не предок <tex>x_k</tex>, и <tex>x_k</tex> имеет минимальный максимальный приоритет в декартовом дереве; следовательно, <tex>x_i</tex> не имеет наименьший наибольший приоритет среди <tex>X_{i, k}</tex>.
Теперь предположим, что какая-то другая вершина <tex>x_m</tex> {{---}} корень. Тогда, если <tex>x_i</tex> и <tex>x_k</tex> лежат в разных поддеревьях, то <tex>i < m < k</tex> или <tex>i > m > k</tex>, следовательно, <tex>x_m</tex> содержится в <tex>X_{i , k}</tex>. В этом случае <tex>x_i</tex> {{---}} не предок <tex>x_k</tex>, и наименьший наибольший приоритет среди <tex>X_{i, k}</tex> имеет вершина с номером <tex>m</tex>.
Наконец, если <tex>x_i</tex> и <tex>x_k</tex> лежат в одном поддереве, то доказательство применяется по индукции: пустое декартово дерево есть тривиальная база, а рассматриваемое поддерево является меньшим декартовым деревом.
}}
Так как распределение приоритетов равномерное, каждая вершина среди <tex>X_{i, k}</tex> может иметь минимальный максимальный приоритет, мы немедленно приходим к следующему равенству: : <tex>Pr[A_{i, jk} = 1] = \left\{\begin{array}{lllc} \fracdfrac{1}{k - i + 1} ,&&, k \ > \ i\\ 0 ,&&, k\ =\ i\\\fracdfrac{1}{i - k + 1} ,&&, k \ < \ i\\
\end{array}\right.
</tex>
Подставив последнее в нашу формулу с математическим ожиданием получим:: <tex>E(d(x_k)) = \sum\limits_{i = 1}^{n} Pr[A_{i,k} = 1] = \sum\limits_{i = 1}^{k - 1} \fracdfrac{1}{k - i + 1} + \sum\limits_{i = k + 1}^{n} \fracdfrac{1}{i - k + 1} \le leqslant </tex><tex>\leqslant \ln(k) + \ln(n-k) + 2</tex> (здесь мы использовали неравенство <tex>\sum\limits_{i = 1}^{n} \fracdfrac{1}{i} \le leqslant \ln(n) + 1</tex>)
: <tex>\log(n)</tex> отличается от <tex>\ln(n)</tex> в константу раз, поэтому <tex>\log(n) = O(\ln(n))</tex>.
}}
Таким образом, среднее время работы операций <tex>\mathrm{Splitsplit}</tex> и <tex>\mathrm{Mergemerge}</tex> будет <tex>O(\log(n))</tex>.
== См. также ==
* [[Декартово дерево по неявному ключу]]
== Ссылки Источники информации ==
*[http://ru.wikipedia.org/wiki/Декартово_дерево Декартово дерево — Википедия]
*[http://rain.ifmo.ru/cat/data/theory/trees/treaps-2006/article.pdf Treaps и T-Treaps]
1632
правки

Навигация