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