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

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 6: Строка 6:
 
|id=lemma1
 
|id=lemma1
 
|about=1
 
|about=1
|statement=В двоичном дереве с n вершинами существует свободная позиция на глубине не более logn.
+
|statement=В двоичном дереве с <tex>n</tex> вершинами существует свободная позиция на глубине не более <tex>\log{n}</tex>.
|proof=Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более n. }}
+
|proof=Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более <tex>n</tex>. }}
  
 
Левосторонняя куча накладывает на двоичное дерево дополнительное условие.
 
Левосторонняя куча накладывает на двоичное дерево дополнительное условие.
Строка 13: Строка 13:
  
 
{{Определение
 
{{Определение
|definition=Условие левосторонней кучи. Пусть dist(х) – расстояние от вершины u до ближайшей свободной позиции в ее поддереве. У пустых позиций dist = 0. Тогда потребуем для любой вершины х: dist(x.L)>= dict(x.R).}}
+
|definition=Условие левосторонней кучи. Пусть <tex>dist(u)</tex> – расстояние от вершины <tex>u</tex> до ближайшей свободной позиции в ее поддереве. У пустых позиций <tex>dist = 0</tex>. Тогда потребуем для любой вершины <tex>u : dist(u.L)\ge dict(u.R)</tex>.}}
  
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за О(1) поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
+
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за <tex>O(1)</tex> поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
  
 
==Поддерживаемые операции==
 
==Поддерживаемые операции==
 
===merge===
 
===merge===
Слияние двух куч.
+
Слияние двух куч.  
 
+
   <code>merge(x,y) //x,y – корни двух деревьев
   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
Строка 34: Строка 33:
 
     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>
  
 
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
 
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
Строка 42: Строка 41:
 
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
 
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
 
===delete===
 
===delete===
Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью decrease key. Уменьшаем ключ до -inf, затем извлекаем минимальное значение.  
+
Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью <tex>decrease key</tex>. Уменьшаем ключ до <tex>-\infty</tex>, затем извлекаем минимальное значение.  
 
===decKey===
 
===decKey===
 
{{Лемма
 
{{Лемма
 
|id=lemma2
 
|id=lemma2
 
|about=2
 
|about=2
|statement=У левостороннего дерева с правой ветвью длинны h количество узлов n>= 2^h 1.
+
|statement=У левостороннего дерева с правой ветвью длинны <tex>h</tex> количество узлов <tex>n \ge 2^{h} - 1</tex>.
 
|proof=Индукция по h.
 
|proof=Индукция по h.
  
При h = 1 – верно.
+
При <tex>h = 1</tex> – верно.
  
При h > 1 левое и правое поддеревья исходного дерева левосторонние, а dist от их корней >= h 1.
+
При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>.
  
По индукции число узлов в каждом из них >=2^(h - 1) – 1, тогда во все дереве n >= (2^(h 1) – 1) + (2^(h 1) – 1) +1 = 2^h 1 узлов.}}
+
По индукции число узлов в каждом из них больше или равно <tex>2^{h - 1} - 1</tex>, тогда во все дереве <tex>n \ge (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{h} - 1</tex> узлов.}}
 
====Алгоритм====
 
====Алгоритм====
1. Найдем узел х, вырежем поддерево с корнем в этом узле.
+
1. Найдем узел <tex>x</tex>, вырежем поддерево с корнем в этом узле.
  
2. Пройдем от предка вырезанной вершины, при этом пересчитывая dist. Если dist левого сына вершины меньше dist правого, то меняем местами поддеревья.
+
2. Пройдем от предка вырезанной вершины, при этом пересчитывая <tex>dist</tex>. Если <tex>dist</tex> левого сына вершины меньше <tex>dist</tex> правого, то меняем местами поддеревья.
  
 
{{Лемма
 
{{Лемма
 
|id=lemma3
 
|id=lemma3
 
|about=3
 
|about=3
|statement= Нужно транспонировать не более logn поддеревьев.  
+
|statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев.  
|proof=Длина пути от вершины до корня может быть и O(n), но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левостороннести уже выполнено. Транспонируем только если dist(x.L) < dist(x.R), но dist(x.R) <= logn. На каждом шаге, если нужно транспонируем и увеличиваем dist++, тогда  dist увеличится до logn и обменов уже не надо будет делать.}}
+
|proof=Длина пути от вершины до корня может быть и <tex>O(n)</tex>, но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левостороннести уже выполнено. Транспонируем только если <tex>dist(x.L) < dist(x.R)</tex>, но <tex>dist(x.R) \le \log{n}</tex>. На каждом шаге, если нужно транспонируем и увеличиваем <tex>dist++</tex>, тогда  <tex>dist</tex> увеличится до <tex>\log{n}</tex> и обменов уже не надо будет делать.}}
Таким образом, мы восстановили левостороннесть кучи за O(logn)
+
Таким образом, мы восстановили левостороннесть кучи за <tex>O(\log{n})</tex>.
  
3. Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. O(logn)
+
3. Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. <tex>O(\log{n})</tex>
==Построение кучи за О(n)==
+
==Построение кучи за <tex>O(n)</tex>==
Храним список левосторонних куч. Пока их количество больше >1, из начала списка достаем две кучи, сливаем их и кладем в конец списка.  
+
Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка.  
 
==Преимущества левосторонней кучи==
 
==Преимущества левосторонней кучи==
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в merge. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация merge является персистентной.
+
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация merge является персистентной.

Версия 18:38, 26 мая 2013

Определение

Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.

Определение:
Левосторонняя куча (leftist heap) — двоичное левосторонее дерево (не обязательно сбалансированное), но с соблюдением порядка кучи (heap order).
Лемма (1):
В двоичном дереве с [math]n[/math] вершинами существует свободная позиция на глубине не более [math]\log{n}[/math].
Доказательство:
[math]\triangleright[/math]
Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более [math]n[/math].
[math]\triangleleft[/math]

Левосторонняя куча накладывает на двоичное дерево дополнительное условие. Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо условия кучи выполняется следующее:


Определение:
Условие левосторонней кучи. Пусть [math]dist(u)[/math] – расстояние от вершины [math]u[/math] до ближайшей свободной позиции в ее поддереве. У пустых позиций [math]dist = 0[/math]. Тогда потребуем для любой вершины [math]u : dist(u.L)\ge dict(u.R)[/math].


Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за [math]O(1)[/math] поменять местами левого и правого ребенка, что не повлияет на порядок кучи.

Поддерживаемые операции

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

Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью [math]decrease key[/math]. Уменьшаем ключ до [math]-\infty[/math], затем извлекаем минимальное значение.

decKey

Лемма (2):
У левостороннего дерева с правой ветвью длинны [math]h[/math] количество узлов [math]n \ge 2^{h} - 1[/math].
Доказательство:
[math]\triangleright[/math]

Индукция по h.

При [math]h = 1[/math] – верно.

При [math]h \gt 1[/math] левое и правое поддеревья исходного дерева левосторонние, а [math]dist[/math] от их корней больше либо равен [math]h - 1[/math].

По индукции число узлов в каждом из них больше или равно [math]2^{h - 1} - 1[/math], тогда во все дереве [math]n \ge (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{h} - 1[/math] узлов.
[math]\triangleleft[/math]

Алгоритм

1. Найдем узел [math]x[/math], вырежем поддерево с корнем в этом узле.

2. Пройдем от предка вырезанной вершины, при этом пересчитывая [math]dist[/math]. Если [math]dist[/math] левого сына вершины меньше [math]dist[/math] правого, то меняем местами поддеревья.

Лемма (3):
Нужно транспонировать не более [math]\log{n}[/math] поддеревьев.
Доказательство:
[math]\triangleright[/math]
Длина пути от вершины до корня может быть и [math]O(n)[/math], но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левостороннести уже выполнено. Транспонируем только если [math]dist(x.L) \lt dist(x.R)[/math], но [math]dist(x.R) \le \log{n}[/math]. На каждом шаге, если нужно транспонируем и увеличиваем [math]dist++[/math], тогда [math]dist[/math] увеличится до [math]\log{n}[/math] и обменов уже не надо будет делать.
[math]\triangleleft[/math]

Таким образом, мы восстановили левостороннесть кучи за [math]O(\log{n})[/math].

3. Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. [math]O(\log{n})[/math]

Построение кучи за [math]O(n)[/math]

Храним список левосторонних куч. Пока их количество больше [math]1[/math], из начала списка достаем две кучи, сливаем их и кладем в конец списка.

Преимущества левосторонней кучи

Нигде не делается уничтожающих присваиваний. Не создается новых узлов в [math]merge[/math]. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация merge является персистентной.