Декартово дерево — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(insert)
(split)
Строка 11: Строка 11:
 
[[file:split.png|thumb|400px|Операция split]]
 
[[file:split.png|thumb|400px|Операция split]]
  
Операция <tex>\mathrm{split}</tex> (''разрезать'') позволяет сделать следующее: разрезать декартово дерево <tex>T</tex> по ключу  
+
Операция <tex>\mathrm{split}</tex> (''разрезать'') позволяет сделать следующее, разрезать исходное дерево <tex>T</tex> по ключу <tex>k</tex>. Возвращать она будет такую пару деревьев <tex>\langle T_1, T_2\rangle </tex>, что в дереве <tex>T_1</tex> ключи меньше <tex>k</tex>, а в дереве <tex>T_2</tex> все остальные:  <tex>\mathrm{split}(T, k) \to \langle T_1, T_2\rangle </tex>.  
<tex>k</tex> и получить два других декартовых дерева: <tex>T_1</tex> и <tex>T_2</tex>, причем в <tex>T_1</tex>
 
находятся все ключи дерева <tex>T</tex>, не большие <tex>k</tex>, а в <tex>T_2</tex> {{---}} большие <tex>k</tex>.
 
 
 
Эта операция будет принимать исходное дерево <tex>T</tex> и ключ <tex>k</tex>, по которому нужно его разделить. Возвращать она будет такую пару деревьев <tex>\langle T_1, T_2\rangle </tex>, что в дереве <tex>T_1</tex> ключи меньше <tex>k</tex>, а в дереве <tex>T_2</tex> все остальные:  <tex>\mathrm{split}(T, k) \to \langle T_1, T_2\rangle </tex>.  
 
  
 
Эта операция устроена следующим образом.
 
Эта операция устроена следующим образом.

Версия 00:34, 23 января 2016

Эта статья про курево

Декартово дерево или дерамида(англ. Treap) — это структура данных, объединяющая в себе бинарное дерево поиска и бинарную кучу (отсюда и второе её название: treap (tree + heap) и дерамида (дерево + пирамида), также существует название курево (куча + дерево).

Более строго, это бинарное дерево, в узлах которого хранится пары (x,y), где x — это ключ, а y — это приоритет. Также оно является двоичным деревом поиска по x и пирамидой по y. Предполагая, что все x и все y являются различными, получаем, что если некоторый элемент дерева содержит (x0,y0), то у всех элементов в левом поддереве x<x0, у всех элементов в правом поддереве x>x0, а также и в левом, и в правом поддереве имеем: y<y0.

Дерамиды были предложены Сиделем (Siedel) и Арагоном (Aragon) в 1996 г.

Операции в декартовом дереве

split

Операция split

Операция split (разрезать) позволяет сделать следующее, разрезать исходное дерево T по ключу k. Возвращать она будет такую пару деревьев T1,T2, что в дереве T1 ключи меньше k, а в дереве T2 все остальные: split(T,k)T1,T2.

Эта операция устроена следующим образом.

Рассмотрим случай, в котором требуется разрезать дерево по ключу, большему ключа корня. Посмотрим, как будут устроены результирующие деревья T1 и T2:

  • T1: левое поддерево T1 совпадёт с левым поддеревом T. Для нахождения правого поддерева T1, нужно разрезать правое поддерево T на TR1 и TR2 по ключу k и взять TR1.
  • T2 совпадёт с TR2.

Случай, в котором требуется разрезать дерево по ключу, меньше либо равному ключа в корне, рассматривается симметрично.

Псевдокод

Treap, Treap split(t: Treap, k: int):
  if t == 
    return <, >     
  else if k > t.x 
    <t1, t2> = split(t.right, k)
    t.right = t1
    return <t1, t2>
  else 
    <t1, t2> = split(t.left, k)
    t.left = t2
    return <t1, t2>

Время работы

Оценим время работы операции split. Во время выполнения вызывается одна операция split для дерева хотя бы на один меньшей высоты и делается ещё O(1) операций. Тогда итоговая трудоёмкость этой операции равна O(h), где h — высота дерева.

merge

Операция merge

Рассмотрим вторую операцию с декартовыми деревьями — merge (слить).

С помощью этой операции можно слить два декартовых дерева в одно. Причем, все ключи в первом(левом) дереве должны быть меньше, чем ключи во втором(правом). В результате получается дерево, в котором есть все ключи из первого и второго деревьев.

Операция merge должна уметь сливать два дерева T1 и T2 в дерево T: merge(T1,T2){T}

Рассмотрим принцип работы этой операции. Пусть нужно слить деревья T1 и T2. Тогда, очевидно, у результирующего дерева T есть корень. Корнем станет вершина из T1 или T2 с наибольшим приоритетом y. Но вершина с самым большим y из всех вершин деревьев T1 и T2 может быть только либо корнем T1, либо корнем T2. Рассмотрим случай, в котором корень T1 имеет больший y, чем корень T2. Случай, в котором корень T2 имеет больший y, чем корень T1, симметричен этому.

Если y корня T1 больше y корня T2, то он и будет являться корнем. Тогда левое поддерево T совпадёт с левым поддеревом T1. Справа же нужно подвесить объединение правого поддерева T1 и дерева T2.

Псевдокод

Treap merge(t1: Treap, t2: Treap):
  if t1 ==  or t2 == 
    return t2 ==  ? t1 : t2
  else if t1.y > t2.y
    t1.right = merge(t1.right, t2)
    return t1
  else 
    t2.left = merge(t1, t2.left)
    return t2

Время работы

Рассуждая аналогично операции split приходим к выводу, что трудоёмкость операции merge равна O(h), где h — высота дерева.

insert

Операция insert(T,k) добавляет в дерево T элемент k, где k.x — ключ, а k.y— приоритет.

Представим что элемент k, это декартово дерево из одного элемента, и для того чтобы его добавить в наше декартово дерево T, очевидно, нам нужно их слить. Но T может содержать ключи как меньше, так и больше ключа k.x, поэтому сначала нужно разрезать T по ключу k.x.

  • Реализация №1
  1. Разобьём наше дерево по ключу, который мы хотим добавить, то есть split(T,k.x)T1,T2.
  2. Сливаем первое дерево с новым элементом, то есть merge(T1,k)T1.
  3. Сливаем получившиеся дерево со вторым, то есть merge(T1,T2)T.
  • Реализация №2
  1. Сначала спускаемся по дереву (как в обычном бинарном дереве поиска по k.x), но останавливаемся на первом элементе, в котором значение приоритета оказалось меньше k.y.
  2. Теперь вызываем split(T,k.x)T1,T2 от найденного элемента (от элемента вместе со всем его поддеревом)
  3. Полученные T1 и T2 записываем в качестве левого и правого сына добавляемого элемента.
  4. Полученное дерево ставим на место элемента, найденного в первом пункте.

В первой реализации два раза используется merge, а во второй реализации слияние вообще не используется.

remove

Операция remove(T,x) удаляет из дерева T элемент с ключом x.

  • Реализация №1
  1. Разобьём наше дерево по ключу, который мы хотим удалить, то есть split(T,k.x)T1,T2.
  2. Теперь отделяем от первого дерева элемент x, опять таки разбивая по ключу x, то есть split(T1,k.x+1)T1,T2.
  3. Сливаем первое дерево со вторым, то есть merge(T1,T2)T.
  • Реализация №2
  1. Спускаемся по дереву (как в обычном бинарном дереве поиска по x), ища удаляемый элемент.
  2. Найдя элемент, вызываем merge его левого и правого сыновей
  3. Результат процедуры merge ставим на место удаляемого элемента.

В первой реализации два раза используется split, а во второй реализации разрезание вообще не используется.

Построение декартова дерева

Пусть нам известно из каких пар (xi,yi) требуется построить декартово дерево, причем также известно, что x1<x2<<xn.

Рекурсивный алгоритм

Рассмотрим приоритеты y1,y2,,yn и выберем максимум среди них, пусть это будет yk, и сделаем (xk,yk) корнем дерева (по свойству пирамиды в корне должен быть элемент с максимальным приоритетом). Проделав то же самое с y1,y2,,yk1 и yk+1,yk+2,,yn, получим соответственно левого и правого сына (xk,yk).

Такой алгоритм работает за O(n×log(n)).

Алгоритм за O(n)

Будем строить дерево слева направо, то есть начиная с (x1,y1) по (xn,yn), при этом помнить последний добавленный элемент (xk,yk). Он будет самым правым, так как у него будет максимальный ключ, а по ключам декартово дерево представляет собой двоичное дерево поиска. При добавлении (xk+1,yk+1), пытаемся сделать его правым сыном (xk,yk), это следует сделать если yk>yk+1, иначе делаем шаг к предку последнего элемента и смотрим его значение y. Поднимаемся до тех пор, пока приоритет в рассматриваемом элементе меньше приоритета в добавляемом, после чего делаем (xk+1,yk+1) его правым сыном, а предыдущего правого сына делаем левым сыном (xk+1,yk+1).

Заметим, что каждую вершину мы посетим максимум дважды: при непосредственном добавлении и, поднимаясь вверх (ведь после этого вершина будет лежать в чьем-то левом поддереве, а мы поднимаемся только по правому). Из этого следует, что построение происходит за O(n).

Случайные приоритеты

Мы уже выяснили, что сложность операций с декартовым деревом линейно зависит от его высоты. В действительности высота декартова дерева может быть линейной относительно его размеров. Например, высота декартова дерева, построенного по набору ключей (1,1),,(n,n), будет равна n. Во избежание таких случаев, полезным оказывается выбирать приоритеты в ключах случайно.

Высота в декартовом дереве с случайными приоритетами

Теорема:
В декартовом дереве из n узлов, приоритеты y которого являются случайными величинами c равномерным распределением, средняя глубина вершины O(logn).
Доказательство:

Будем считать, что все выбранные приоритеты y попарно различны.

Для начала введем несколько обозначений:

  • xk — вершина с k-ым по величине ключом;
  • индикаторная величина Ai,j={1,xi is ancestor of xj0,otherwise
  • d(v) — глубина вершины v;

В силу обозначений глубину вершины можно записать как количество предков:

d(xk)=ni=1Ai,k.

Теперь можно выразить математическое ожидание глубины конкретной вершины:

E(d(xk))=ni=1Pr[Ai,k=1] — здесь мы использовали линейность математического ожидания, и то что E(X)=Pr[X=1] для индикаторной величины X (Pr[A] — вероятность события A).

Для подсчёта средней глубины вершин нам нужно сосчитать вероятность того, что вершина xi является предком вершины xk, то есть Pr[Ai,k=1].

Введем новое обозначение:

  • Xi,k — множество ключей {xi,,xk} или {xk,,xi}, в зависимости от i<k или i>k. Xi,k и Xk,i обозначают одно и тоже, их мощность равна |ki|+1.
Лемма:
Для любых ik , xi является предком xk тогда и только тогда, когда xi имеет наибольший приоритет среди Xi,k.
Доказательство:

Если xi является корнем, то оно является предком xk и по определению имеет максимальный приоритет среди всех вершин, следовательно, и среди Xi,k.

С другой стороны, если xk — корень, то xi — не предок xk, и xk имеет максимальный приоритет в декартовом дереве; следовательно, xi не имеет наибольший приоритет среди Xi,k.

Теперь предположим, что какая-то другая вершина xm — корень. Тогда, если xi и xk лежат в разных поддеревьях, то i<m<k или i>m>k, следовательно, xm содержится в Xi,k. В этом случае xi — не предок xk, и наибольший приоритет среди Xi,k имеет вершина с номером m.

Наконец, если xi и xk лежат в одном поддереве, то доказательство применяется по индукции: пустое декартово дерево есть тривиальная база, а рассматриваемое поддерево является меньшим декартовым деревом.

Так как распределение приоритетов равномерное, каждая вершина среди Xi,k может иметь максимальный приоритет, мы немедленно приходим к следующему равенству:

Pr[Ai,j=1]={1ki+1,k > i0,k = i1ik+1,k < i

Подставив последнее в нашу формулу с математическим ожиданием получим:

E(d(xk))=ni=1Pr[Ai,k=1]=k1i=11ki+1+ni=k+11ik+1ln(k)+ln(nk)+2 (здесь мы использовали неравенство ni=11iln(n)+1)
log(n) отличается от ln(n) в константу раз, поэтому log(n)=O(ln(n)).
В итоге мы получили что E(d(xk))=O(log(n)).

Таким образом, среднее время работы операций split и merge будет O(log(n)).

См. также

Источники информации