Левосторонняя куча — различия между версиями
|  (→merge) |  (→merge) | ||
| Строка 20: | Строка 20: | ||
| ===merge=== | ===merge=== | ||
| Слияние двух куч.   | Слияние двух куч.   | ||
| − |    <code>merge(x,y) //x,y – корни двух деревьев | + |    <code>'''merge'''(x,y) //x,y – корни двух деревьев | 
| − |      if x == NULL return y | + |      '''if''' x == NULL '''return''' y | 
| − |      if y == NULL return x | + |      '''if''' y == NULL '''return''' x | 
| − |      if y.key < x.key : | + |      '''if''' y.key < x.key : | 
|        x <tex>\leftrightarrow</tex> y |        x <tex>\leftrightarrow</tex> y | ||
| − |      //Воспользуемся тем, что куча левосторонняя. Правая ветка — самая короткая и не длиннее | + |      //Воспользуемся тем, что куча левосторонняя. Правая ветка — самая короткая и не длиннее логарифма. | 
| − |      // | + |      //Пойдем направо и сольем правое поддерево с у. | 
| − |      x.R <tex>\leftarrow</tex> merge(x.R, y) | + |      x.R <tex>\leftarrow</tex> '''merge'''(x.R, y) | 
|      //Могло возникнуть  нарушение левостороннести кучи. |      //Могло возникнуть  нарушение левостороннести кучи. | ||
| − | + |      '''if''' dist(x.R) > dist(x.L): | |
|        x.L <tex>\leftrightarrow</tex> x.R |        x.L <tex>\leftrightarrow</tex> x.R | ||
| − |      update dist(x) //dist(x) = min(dist(x.L), dist(x.R)) + 1 | + |      '''update''' dist(x) //dist(x) = min(dist(x.L), dist(x.R)) + 1 | 
| − |      return x; | + |      '''return''' x; | 
|      //Каждый раз идет в уже существующей вершине только в правое поддерево — не более логарифма вызовов (по лемме).</code> |      //Каждый раз идет в уже существующей вершине только в правое поддерево — не более логарифма вызовов (по лемме).</code> | ||
Версия 19:26, 26 мая 2013
Содержание
Определение
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.
| Определение: | 
| Левосторонняя куча (leftist heap) — двоичное левосторонее дерево (не обязательно сбалансированное), но с соблюдением порядка кучи (heap order). | 
| Лемма (1): | 
| В двоичном дереве с  вершинами существует свободная позиция на глубине не более . | 
| Доказательство: | 
| Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более . | 
Левосторонняя куча накладывает на двоичное дерево дополнительное условие. Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо условия кучи выполняется следующее:
| Определение: | 
| Условие левосторонней кучи. Пусть – расстояние от вершины до ближайшей свободной позиции в ее поддереве. У пустых позиций . Тогда потребуем для любой вершины . | 
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за  поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
Поддерживаемые операции
merge
Слияние двух куч.
 merge(x,y) //x,y – корни двух деревьев
   if x == NULL return y
   if y == NULL return x
   if y.key < x.key :
     x  y
   //Воспользуемся тем, что куча левосторонняя. Правая ветка — самая короткая и не длиннее логарифма.
   //Пойдем направо и сольем правое поддерево с у.
   x.R  merge(x.R, y)
   //Могло возникнуть  нарушение левостороннести кучи.
   if dist(x.R) > dist(x.L):
     x.L  x.R
   update dist(x) //dist(x) = min(dist(x.L), dist(x.R)) + 1
   return x;
   //Каждый раз идет в уже существующей вершине только в правое поддерево — не более логарифма вызовов (по лемме).
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
insert
Вставка новой вершины в дерево. Новое левостороннее дерево, состоящее из одной вершины, сливается с исходным.
extractMin
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
delete
Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью . Уменьшаем ключ до , затем извлекаем минимальное значение.
decKey
| Лемма (2): | 
| У левостороннего дерева с правой ветвью длинны  количество узлов . | 
| Доказательство: | 
| Индукция по h. При – верно. При левое и правое поддеревья исходного дерева левосторонние, а от их корней больше либо равен .По индукции число узлов в каждом из них больше или равно , тогда во все дереве узлов. | 
Алгоритм
1. Найдем узел , вырежем поддерево с корнем в этом узле.
2. Пройдем от предка вырезанной вершины, при этом пересчитывая . Если левого сына вершины меньше правого, то меняем местами поддеревья.
| Лемма (3): | 
| Нужно транспонировать не более  поддеревьев. | 
| Доказательство: | 
| Длина пути от вершины до корня может быть и , но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левостороннести уже выполнено. Транспонируем только если , но . На каждом шаге, если нужно транспонируем и увеличиваем , тогда увеличится до и обменов уже не надо будет делать. | 
Таким образом, мы восстановили левостороннесть кучи за .
3. Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное.
Построение кучи за
Храним список левосторонних куч. Пока их количество больше , из начала списка достаем две кучи, сливаем их и кладем в конец списка.
Преимущества левосторонней кучи
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в . Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация merge является персистентной.
