Левосторонняя куча — различия между версиями
(→Построение кучи за O(n)) |
Shersh (обсуждение | вклад) |
||
Строка 2: | Строка 2: | ||
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого. | Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого. | ||
{{Определение | {{Определение | ||
− | |definition='''Левосторонняя куча (leftist heap)''' | + | |definition='''Левосторонняя куча (leftist heap)''' {{---}} двоичное левосторонее [[Дерево, эквивалентные определения|дерево]] (не обязательно сбалансированное), но с соблюдением [[Двоичная куча#Определение|порядка кучи]] (heap order).}} |
{{Лемма | {{Лемма | ||
|id=lemma1 | |id=lemma1 | ||
Строка 10: | Строка 10: | ||
Левосторонняя куча накладывает на двоичное дерево дополнительное условие. | Левосторонняя куча накладывает на двоичное дерево дополнительное условие. | ||
− | Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо условия кучи выполняется следующее: | + | Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее: |
{{Определение | {{Определение | ||
− | |definition=Условие левосторонней кучи. Пусть <tex>dist(u)</tex> | + | |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> поменять местами левого и правого ребенка, что не повлияет на порядок кучи. | Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за <tex>O(1)</tex> поменять местами левого и правого ребенка, что не повлияет на порядок кучи. | ||
Строка 20: | Строка 20: | ||
===merge=== | ===merge=== | ||
Слияние двух куч. | Слияние двух куч. | ||
− | + | ||
− | '''if''' x == | + | '''merge'''(x, y) // x, y {{---}} корни двух деревьев |
− | '''if''' y == | + | '''if''' x == <tex> \varnothing </tex>: '''return''' y |
− | '''if''' y.key < x.key : | + | '''if''' y == <tex> \varnothing </tex>: '''return''' x |
+ | '''if''' y.key < x.key: | ||
x <tex>\leftrightarrow</tex> y | x <tex>\leftrightarrow</tex> y | ||
− | //Воспользуемся тем, что куча левосторонняя. Правая ветка | + | // Воспользуемся тем, что куча левосторонняя. Правая ветка {{---}} самая короткая и не длиннее логарифма. |
− | //Пойдем направо и сольем правое поддерево с у. | + | // Пойдем направо и сольем правое поддерево с у. |
− | x.R | + | x.R = '''merge'''(x.R, y) |
− | //Могло возникнуть нарушение левостороннести кучи. | + | // Могло возникнуть нарушение левостороннести кучи. |
'''if''' dist(x.R) > dist(x.L): | '''if''' dist(x.R) > dist(x.L): | ||
x.L <tex>\leftrightarrow</tex> x.R | x.L <tex>\leftrightarrow</tex> x.R | ||
− | + | dist(x) = min(dist(x.L), dist(x.R)) + 1 // пересчитаем расстояние до ближайшей свободной позиции | |
− | '''return''' x | + | '''return''' x |
− | //Каждый раз | + | // Каждый раз идем из уже существующей вершины только в правое поддерево {{---}} не более логарифма вызовов (по лемме) |
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния. | Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния. | ||
Строка 42: | Строка 43: | ||
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи. | Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи. | ||
===delete=== | ===delete=== | ||
− | Аналогично удаляется любой элемент | + | Аналогично удаляется любой элемент {{---}} на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя {{---}} на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью <tex>decrease key</tex>. Уменьшаем ключ до <tex>-\infty</tex>, затем извлекаем минимальное значение. |
− | === | + | ===decreaseKey=== |
{{Лемма | {{Лемма | ||
|id=lemma2 | |id=lemma2 | ||
Строка 50: | Строка 51: | ||
|proof=Индукция по h. | |proof=Индукция по h. | ||
− | При <tex>h = 1</tex> | + | При <tex>h = 1</tex> {{---}} верно. |
При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>. | При <tex>h > 1</tex> левое и правое поддеревья исходного дерева левосторонние, а <tex>dist</tex> от их корней больше либо равен <tex>h - 1</tex>. | ||
Строка 56: | Строка 57: | ||
По индукции число узлов в каждом из них больше или равно <tex>2^{h - 1} - 1</tex>, тогда во все дереве <tex>n \ge (2^{h - 1} - 1) + (2^{h - 1} - 1) + 1 = 2^{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> узлов.}} | ||
====Алгоритм==== | ====Алгоритм==== | ||
− | + | * Найдем узел <tex>x</tex>, вырежем поддерево с корнем в этом узле. | |
− | + | * Пройдем от предка вырезанной вершины, при этом пересчитывая <tex>dist</tex>. Если <tex>dist</tex> левого сына вершины меньше <tex>dist</tex> правого, то меняем местами поддеревья. | |
− | + | * Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное. | |
− | |||
{{Лемма | {{Лемма | ||
|id=lemma3 | |id=lemma3 | ||
|about=3 | |about=3 | ||
|statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев. | |statement= Нужно транспонировать не более <tex>\log{n}</tex> поддеревьев. | ||
− | |proof=Длина пути от вершины до корня может быть и <tex>O(n)</tex>, но нам не нужно подниматься до корня | + | |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> и обменов уже не надо будет делать.}} |
− | Таким образом, мы восстановили левостороннесть кучи за <tex>O(\log{n})</tex>. | + | Таким образом, мы восстановили левостороннесть кучи за <tex>O(\log{n})</tex>. Поэтому асимптотика операции <tex> decreaseKey </tex> {{---}} <tex>O(\log{n})</tex>. |
+ | |||
− | |||
==Построение кучи за O(n)== | ==Построение кучи за O(n)== | ||
Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка. | Храним список левосторонних куч. Пока их количество больше <tex>1</tex>, из начала списка достаем две кучи, сливаем их и кладем в конец списка. | ||
Строка 73: | Строка 73: | ||
==Преимущества левосторонней кучи== | ==Преимущества левосторонней кучи== | ||
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация <tex>merge</tex> является персистентной. | Нигде не делается уничтожающих присваиваний. Не создается новых узлов в <tex>merge</tex>. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация <tex>merge</tex> является персистентной. | ||
+ | |||
+ | [[Категория: Дискретная математика и алгоритмы]] | ||
+ | [[Категория: Приоритетные очереди]] |
Версия 10:33, 27 мая 2013
Содержание
Определение
Левосторонние деревья были изобретены Кларком Крейном (Clark Allan Crane), свое название они получили из-за того, что левое поддерево обычно длиннее правого.
Определение: |
Левосторонняя куча (leftist heap) — двоичное левосторонее дерево (не обязательно сбалансированное), но с соблюдением порядка кучи (heap order). |
Лемма (1): |
В двоичном дереве с вершинами существует свободная позиция на глубине не более . |
Доказательство: |
Если бы все свободные позиции были на глубине более логарифма, то мы получили бы полное дерево с количеством вершин более | .
Левосторонняя куча накладывает на двоичное дерево дополнительное условие. Ближайшая свободная позиция должна быть самой правой позицией в дереве. То есть помимо обычного условия кучи выполняется следующее:
Определение: |
Условие левосторонней кучи. Пусть | — расстояние от вершины до ближайшей свободной позиции в ее поддереве. У пустых позиций . Тогда потребуем для любой вершины .
Если для какой- то вершины это свойство не выполняется, то это легко устраняется: можно за поменять местами левого и правого ребенка, что не повлияет на порядок кучи.
Поддерживаемые операции
merge
Слияние двух куч.
merge(x, y) // x, y — корни двух деревьев if x ==: return y if y == : 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 dist(x) = min(dist(x.L), dist(x.R)) + 1 // пересчитаем расстояние до ближайшей свободной позиции return x // Каждый раз идем из уже существующей вершины только в правое поддерево — не более логарифма вызовов (по лемме)
Левосторонняя куча относится к сливаемым кучам: остальные операции легко реализуются с помощью операции слияния.
insert
Вставка новой вершины в дерево. Новое левостороннее дерево, состоящее из одной вершины, сливается с исходным.
extractMin
Как и у любой другой двоичной кучи, минимум хранится в корне. Извлекаем минимальное значение, удаляем корень, сливаем левое и правое поддерево корня. Возвращает пару из извлеченной вершины и новой кучи.
delete
Аналогично удаляется любой элемент — на его место ставится результат слияния его детей. Но так просто любой элемент удалить нельзя — на пути от этого элемента к корню может нарушиться левостороннесть кучи. А до корня мы дойти не можем, так как элемент может находиться на линейной глубине. Поэтому удаление реализуется с помощью
. Уменьшаем ключ до , затем извлекаем минимальное значение.decreaseKey
Лемма (2): |
У левостороннего дерева с правой ветвью длинны количество узлов . |
Доказательство: |
Индукция по h. При — верно.При По индукции число узлов в каждом из них больше или равно левое и правое поддеревья исходного дерева левосторонние, а от их корней больше либо равен . , тогда во все дереве узлов. |
Алгоритм
- Найдем узел , вырежем поддерево с корнем в этом узле.
- Пройдем от предка вырезанной вершины, при этом пересчитывая . Если левого сына вершины меньше правого, то меняем местами поддеревья.
- Уменьшаем ключ данного узла и сливаем два дерева: исходное и вырезанное.
Лемма (3): |
Нужно транспонировать не более поддеревьев. |
Доказательство: |
Длина пути от вершины до корня может быть и | , но нам не нужно подниматься до корня — достаточно подняться до вершины, у которой свойство левосторонней кучи уже выполнено. Транспонируем только если , но . На каждом шаге, если нужно транспонируем и увеличиваем , тогда увеличится до и обменов уже не надо будет делать.
Таким образом, мы восстановили левостороннесть кучи за
. Поэтому асимптотика операции — .
Построение кучи за O(n)
Храним список левосторонних куч. Пока их количество больше
, из начала списка достаем две кучи, сливаем их и кладем в конец списка.Преимущества левосторонней кучи
Нигде не делается уничтожающих присваиваний. Не создается новых узлов в
. Эта реализация слияния является функциональной — ее легко реализовать на функциональном языке программирования. Также данная реалзация является персистентной.