Фибоначчиева куча — различия между версиями
AlexeyL (обсуждение | вклад) |
AlexeyL (обсуждение | вклад) |
||
Строка 1: | Строка 1: | ||
− | = Фибоначчиево дерево = | + | == Фибоначчиево дерево == |
{{Определение | {{Определение | ||
|definition= | |definition= | ||
Строка 63: | Строка 63: | ||
}} | }} | ||
− | = Фибоначчиева куча = | + | == Фибоначчиева куча == |
{{Определение | {{Определение | ||
Строка 118: | Строка 118: | ||
}} | }} | ||
− | == Структура == | + | === Структура === |
[[File:Fibonacci-heap.png|thumb|340px|Пример фибоначчиевой кучи]] | [[File:Fibonacci-heap.png|thumb|340px|Пример фибоначчиевой кучи]] | ||
* Каждый узел <tex>x</tex> в куче <tex>H</tex> содержит следующие указатели и поля: | * Каждый узел <tex>x</tex> в куче <tex>H</tex> содержит следующие указатели и поля: | ||
Строка 135: | Строка 135: | ||
Циклический двусвязный список обладает двумя преимуществами для использования в фибоначчиевых кучах. Во-первых, удаление элемента из такого списка выполняется за время <tex>O(1)</tex>. Во-вторых, если имеется два таких списка, их легко объединить в один за время <tex>O(1)</tex>. | Циклический двусвязный список обладает двумя преимуществами для использования в фибоначчиевых кучах. Во-первых, удаление элемента из такого списка выполняется за время <tex>O(1)</tex>. Во-вторых, если имеется два таких списка, их легко объединить в один за время <tex>O(1)</tex>. | ||
− | == Потенциал == | + | === Потенциал === |
Для анализа производительности операций введем потенциал для фибоначчиевой кучи <tex>H</tex> как <tex> \Phi(H) = t[H] + 2m[H] </tex>, где <tex> t[H] </tex> {{---}} количество элементов в корневом списке кучи, а <tex> m[H] </tex> {{---}} количество вершин, у которых удален один ребенок (то есть вершин с пометкой <tex> x.mark = true </tex>). Договоримся, что единицы потенциала достаточно для оплаты константного количества работы. | Для анализа производительности операций введем потенциал для фибоначчиевой кучи <tex>H</tex> как <tex> \Phi(H) = t[H] + 2m[H] </tex>, где <tex> t[H] </tex> {{---}} количество элементов в корневом списке кучи, а <tex> m[H] </tex> {{---}} количество вершин, у которых удален один ребенок (то есть вершин с пометкой <tex> x.mark = true </tex>). Договоримся, что единицы потенциала достаточно для оплаты константного количества работы. | ||
− | == Операции == | + | === Операции === |
Рассмотрим операции, которые поддерживают фибоначчиевы кучи. Амортизированное время их работы показано в таблице. | Рассмотрим операции, которые поддерживают фибоначчиевы кучи. Амортизированное время их работы показано в таблице. | ||
{| border="1" | {| border="1" | ||
Строка 166: | Строка 166: | ||
Стоит заметить, что структура фибоначчиевых куч, также как биномиальных и бинарных, не могут обеспечить эффективную реализацию поиска элемента с заданным ключом, поэтому операции <tex>\mathrm {decreaseKey}</tex> и <tex>\mathrm {delete}</tex> получают в качестве аргумента указатель на узел, а не значение его ключа. | Стоит заметить, что структура фибоначчиевых куч, также как биномиальных и бинарных, не могут обеспечить эффективную реализацию поиска элемента с заданным ключом, поэтому операции <tex>\mathrm {decreaseKey}</tex> и <tex>\mathrm {delete}</tex> получают в качестве аргумента указатель на узел, а не значение его ключа. | ||
− | === makeHeap === | + | ==== makeHeap ==== |
Создается новый пустой корневой список, в <tex> H.min </tex> устанавливается значение <tex> null </tex>. Реальное время работы {{---}} <tex> O(1) </tex>. | Создается новый пустой корневой список, в <tex> H.min </tex> устанавливается значение <tex> null </tex>. Реальное время работы {{---}} <tex> O(1) </tex>. | ||
− | === insert === | + | ==== insert ==== |
Вставка элемента в фибоначчиеву кучу также тривиальна: создается новая куча из одного элемента и сливается с текущей. Для оценки амортизированной стоимости операции рассмотрим исходную кучу <tex> H </tex> и получившуюся в результате вставки нового элемента кучу <tex> H' </tex>. <tex> t[H'] = t[H] + 1 </tex> и <tex> m[H'] = m[H] </tex>. Следовательно, увеличение потенциала составляет <tex> (t[H] + 1 + 2m[H]) - (t[H] + 2m[H]) = 1 </tex>. Так как реальное время работы составляет <tex> O(1) </tex>, то амортизированная стоимость данной операции также равна <tex> O(1) </tex>. | Вставка элемента в фибоначчиеву кучу также тривиальна: создается новая куча из одного элемента и сливается с текущей. Для оценки амортизированной стоимости операции рассмотрим исходную кучу <tex> H </tex> и получившуюся в результате вставки нового элемента кучу <tex> H' </tex>. <tex> t[H'] = t[H] + 1 </tex> и <tex> m[H'] = m[H] </tex>. Следовательно, увеличение потенциала составляет <tex> (t[H] + 1 + 2m[H]) - (t[H] + 2m[H]) = 1 </tex>. Так как реальное время работы составляет <tex> O(1) </tex>, то амортизированная стоимость данной операции также равна <tex> O(1) </tex>. | ||
− | === getMin === | + | ==== getMin ==== |
Возвращает указатель <tex>H.min</tex>. Реальное время работы {{---}} <tex> O(1) </tex>. | Возвращает указатель <tex>H.min</tex>. Реальное время работы {{---}} <tex> O(1) </tex>. | ||
− | === merge === | + | ==== merge ==== |
Слияние двух фибоначчиевых куч происходит просто: объединяем списки этих куч в один, релаксируем минимум. Реальное время работы {{---}} <tex> O(1) </tex>. Амортизированное время работы также <tex> O(1) </tex>, поскольку, при объединении двух куч в одну, потенциалы обеих куч суммируются, итоговая сумма потенциалов не изменяется, <tex> \Phi_{n + 1} - \Phi_n = 0 </tex>. | Слияние двух фибоначчиевых куч происходит просто: объединяем списки этих куч в один, релаксируем минимум. Реальное время работы {{---}} <tex> O(1) </tex>. Амортизированное время работы также <tex> O(1) </tex>, поскольку, при объединении двух куч в одну, потенциалы обеих куч суммируются, итоговая сумма потенциалов не изменяется, <tex> \Phi_{n + 1} - \Phi_n = 0 </tex>. | ||
− | === extractMin === | + | ==== extractMin ==== |
Первая рассматриваемая операция, в ходе которой меняется структура кучи. Здесь используется вспомогательная процедура <tex> \mathrm {consolidate} </tex>. Возьмем указатель на <tex> H.min </tex>, удалим эту вершину. Ее поддеревья (их не более, чем <tex> D(n) </tex>, где <tex> D(n) </tex> {{---}} максимальная степень вершины в куче) объединим с корневым списком. Теперь вызываем процедуру <tex> \mathrm {consolidate} </tex>. После этой операции в списке корней остается не более чем <tex> D(n) + 1</tex> узлов, среди которых нужно найти минимальный. Итоговая асимптотика операции <tex>\mathrm {extraxtMin}</tex>, учитывая и вспомогательную функцию <tex> \mathrm {consolidate} </tex>, время работы которой доказывается ниже, равно: <tex> O(1)+O(D(n))+O(D(n))=O(D(n)) </tex>. По доказанной выше [[#Лемма4|лемме]] <tex>O(D(n)) = O(\log(n))</tex>. | Первая рассматриваемая операция, в ходе которой меняется структура кучи. Здесь используется вспомогательная процедура <tex> \mathrm {consolidate} </tex>. Возьмем указатель на <tex> H.min </tex>, удалим эту вершину. Ее поддеревья (их не более, чем <tex> D(n) </tex>, где <tex> D(n) </tex> {{---}} максимальная степень вершины в куче) объединим с корневым списком. Теперь вызываем процедуру <tex> \mathrm {consolidate} </tex>. После этой операции в списке корней остается не более чем <tex> D(n) + 1</tex> узлов, среди которых нужно найти минимальный. Итоговая асимптотика операции <tex>\mathrm {extraxtMin}</tex>, учитывая и вспомогательную функцию <tex> \mathrm {consolidate} </tex>, время работы которой доказывается ниже, равно: <tex> O(1)+O(D(n))+O(D(n))=O(D(n)) </tex>. По доказанной выше [[#Лемма4|лемме]] <tex>O(D(n)) = O(\log(n))</tex>. | ||
− | ==== consolidate ==== | + | ===== consolidate ===== |
Данная процедура принимает кучу и преобразует ее таким образом, что в корневом списке остается не более <tex> D(n) + 1</tex> вершин. | Данная процедура принимает кучу и преобразует ее таким образом, что в корневом списке остается не более <tex> D(n) + 1</tex> вершин. | ||
Строка 201: | Строка 201: | ||
Поскольку мы договорились, что можем масштабировать единицу потенциала таким образом, чтобы покрывать константное количество работы, то итоговая амортизационная оценка {{---}} <tex> O(D(n)) </tex> | Поскольку мы договорились, что можем масштабировать единицу потенциала таким образом, чтобы покрывать константное количество работы, то итоговая амортизационная оценка {{---}} <tex> O(D(n)) </tex> | ||
− | === decreaseKey === | + | ==== decreaseKey ==== |
Основная идея: хотим, чтобы учетная стоимость данной операции была <tex> O(1) </tex>. Было бы хорошо, чтобы вершина не всплывала до корня, и тогда дерево не придется сильно перестраивать. Для этого при удобном случае будем вырезать поддерево полностью и перемещать его в корневой список. Итак, сам алгоритм: | Основная идея: хотим, чтобы учетная стоимость данной операции была <tex> O(1) </tex>. Было бы хорошо, чтобы вершина не всплывала до корня, и тогда дерево не придется сильно перестраивать. Для этого при удобном случае будем вырезать поддерево полностью и перемещать его в корневой список. Итак, сам алгоритм: | ||
Строка 208: | Строка 208: | ||
# Иначе, вырезаем дерево с текущей вершиной в корневой список, и производим каскадное вырезание родителя. | # Иначе, вырезаем дерево с текущей вершиной в корневой список, и производим каскадное вырезание родителя. | ||
− | ==== cut ==== | + | ===== cut ===== |
При вырезании вершины мы удаляем ее из списка детей своего родителя, уменьшаем степень ее родителя (<tex> x.p.degree </tex>) и снимаем пометку с текущей вершины (<tex> x.mark = false </tex>). | При вырезании вершины мы удаляем ее из списка детей своего родителя, уменьшаем степень ее родителя (<tex> x.p.degree </tex>) и снимаем пометку с текущей вершины (<tex> x.mark = false </tex>). | ||
− | ==== cascadingCut ==== | + | ===== cascadingCut ===== |
[[File:Каскадное вырезание.png|thumb|500px|Пример каскадного вырезания]] | [[File:Каскадное вырезание.png|thumb|500px|Пример каскадного вырезания]] | ||
Строка 226: | Строка 226: | ||
* У вершины с ключом <tex>7</tex> удален лишь один ребенок, поэтому операция <tex>\mathrm {cascadingCut}</tex> от нее не запускается. В итоге, получаем кучу, состоящую из <tex>5</tex> фибоначчиевых деревьев. | * У вершины с ключом <tex>7</tex> удален лишь один ребенок, поэтому операция <tex>\mathrm {cascadingCut}</tex> от нее не запускается. В итоге, получаем кучу, состоящую из <tex>5</tex> фибоначчиевых деревьев. | ||
− | ==== Время работы ==== | + | ===== Время работы ===== |
Докажем, что амортизированное время работы операции <tex> \mathrm {decreaseKey} </tex> есть <tex> O(1) </tex>. Поскольку в процедуре нет циклов, ее время работы определяется лишь количеством рекурсивных вызовов каскадного вырезания. | Докажем, что амортизированное время работы операции <tex> \mathrm {decreaseKey} </tex> есть <tex> O(1) </tex>. Поскольку в процедуре нет циклов, ее время работы определяется лишь количеством рекурсивных вызовов каскадного вырезания. | ||
Строка 236: | Строка 236: | ||
В итоге, изменение потенциала составляет: <tex> \Phi_i - \Phi_{i - 1} = ((t[H] + k) + 2(m[H] + k - 2)) - (t[H] + 2m[H]) = 4 - k </tex>. Следовательно, амортизированная стоимость не превышает <tex> O(k) + 4 - k </tex>. Но поскольку мы можем соответствующим образом масштабировать единицы потенциала, то амортизированная стоимость операции <tex> \mathrm {decreaseKey} </tex> равна <tex> O(1) </tex>. | В итоге, изменение потенциала составляет: <tex> \Phi_i - \Phi_{i - 1} = ((t[H] + k) + 2(m[H] + k - 2)) - (t[H] + 2m[H]) = 4 - k </tex>. Следовательно, амортизированная стоимость не превышает <tex> O(k) + 4 - k </tex>. Но поскольку мы можем соответствующим образом масштабировать единицы потенциала, то амортизированная стоимость операции <tex> \mathrm {decreaseKey} </tex> равна <tex> O(1) </tex>. | ||
− | === delete === | + | ==== delete ==== |
Удаление вершины реализуется через уменьшение ее ключа до <tex> -\infty </tex> и последующим извлечением минимума. Амортизированное время работы: <tex> O(1) + O(D(n)) = O(D(n)) </tex>. | Удаление вершины реализуется через уменьшение ее ключа до <tex> -\infty </tex> и последующим извлечением минимума. Амортизированное время работы: <tex> O(1) + O(D(n)) = O(D(n)) </tex>. | ||
Строка 242: | Строка 242: | ||
Поскольку ранее мы показали, что <tex> D(n) = O(\log n ) </tex>, то соответствующие оценки доказаны. | Поскольку ранее мы показали, что <tex> D(n) = O(\log n ) </tex>, то соответствующие оценки доказаны. | ||
− | = Источники информации = | + | == Источники информации == |
* Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн — Алгоритмы: построение и анализ. — М.: Издательский дом «Вильямс», 2005. — С. 1296. — ISBN 5-8459-0857-4 | * Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн — Алгоритмы: построение и анализ. — М.: Издательский дом «Вильямс», 2005. — С. 1296. — ISBN 5-8459-0857-4 |
Версия 18:48, 12 июня 2014
Содержание
Фибоначчиево дерево
Определение: |
Фибоначчиево дерево — биномиальное дерево, где у каждой вершины удалено не более одного ребенка. |
Определение: |
Порядок фибоначчиева дерева — порядок соответствующего биномиального дерева, из которого оно получено. |
Определение: |
Степень вершины — количество дочерних узлов данной вершины. |
Лемма: |
Для всех целых
, где — -ое число Фибоначчи, определяемое формулой: |
Доказательство: |
Докажем лемму по индукции: при , что действительно верно. По индукции предполагаем, что . Тогда |
Лемма: |
Фибоначчиево дерево порядка содержит не менее вершин. |
Доказательство: |
Докажем это утверждение по индукции. Пусть — минимальный размер фибоначчиева дерева порядка .При . При . Предположим по индукции, что для всех . Пусть в нашем дереве удалено поддерево порядка . Тогда
Но по предыдущей лемме : . Следовательно, |
Фибоначчиева куча
Определение: |
Фибоначчиева куча — набор фибоначчиевых деревьев, корни которых объединены в неупорядоченный циклический двусвязный список. В отличие от биномиальной кучи, степени корней не обязаны быть попарно различными. |
Фибоначчиевы кучи поддерживают тот же набор операций, что и биномиальные кучи, но имеют то преимущество, что операции, в которых не требуется удаление, имеют амортизированное время работы, равное .
С теоретической точки зрения фибоначчиевы кучи особенно полезны в случае, когда количество операций бинарные кучи.
и относительно мало по сравнению с количеством других операций. Однако с практической точки зрения программная сложность и высокие значения постоянных множителей в формулах времени работы существенно снижают эффективность применения фибоначчиевых куч, делая их в большинстве случаев менее привлекательными, чем обычныеЛемма: |
, где |
Доказательство: |
Для начала докажем, что Используем для этого математическую индукцию. При , что верно. При , что также верно. По индукции предполагаем, что и . Тогда
Подставив вместо Поскольку его значение, нетрудно убедится, что , то выполняются неравенства . Таким образом, -ое число Фибоначчи равно , округленному до ближайшего целого числа. Следовательно, . |
Лемма: |
Максимальная степень произвольной вершины в фибоначчиевой куче с вершинами равна |
Доказательство: |
Пусть доказанному выше в дереве, корень которого , содержится не менее вершин, что в свою очередь по лемме равно . То есть — произвольная вершина в фибоначчиевой куче с вершинами, и пусть — степень вершины . Тогда по
Логарифмируя по основанию , получаемТаким образом, максимальная степень произвольной вершины равна . |
Структура
- Каждый узел
- — поле, в котором хранится ключ;
- — указатель на родительский узел;
- — указатель на один из дочерних узлов;
- — указатель на левый сестринский узел;
- — указатель на правый сестринский узел;
- — поле, в котором хранится количество дочерних узлов;
- — логическое значение, которое показывает, удаляли ли мы дочерние узлы данной вершины.
в куче содержит следующие указатели и поля:
- Дочерние узлы объединены при помощи указателей и в циклический двусвязный список.
- Корни всех деревьев в связаны при помощи указателей и в циклический двусвязный список корней.
- Обращение к выполняется посредством указателя на корень дерева с минимальным ключом. Этот узел называется минимальным узлом .
- Текущее количество узлов в хранится в .
Циклический двусвязный список обладает двумя преимуществами для использования в фибоначчиевых кучах. Во-первых, удаление элемента из такого списка выполняется за время
. Во-вторых, если имеется два таких списка, их легко объединить в один за время .Потенциал
Для анализа производительности операций введем потенциал для фибоначчиевой кучи
как , где — количество элементов в корневом списке кучи, а — количество вершин, у которых удален один ребенок (то есть вершин с пометкой ). Договоримся, что единицы потенциала достаточно для оплаты константного количества работы.Операции
Рассмотрим операции, которые поддерживают фибоначчиевы кучи. Амортизированное время их работы показано в таблице.
Стоит заметить, что структура фибоначчиевых куч, также как биномиальных и бинарных, не могут обеспечить эффективную реализацию поиска элемента с заданным ключом, поэтому операции
и получают в качестве аргумента указатель на узел, а не значение его ключа.makeHeap
Создается новый пустой корневой список, в
устанавливается значение . Реальное время работы — .insert
Вставка элемента в фибоначчиеву кучу также тривиальна: создается новая куча из одного элемента и сливается с текущей. Для оценки амортизированной стоимости операции рассмотрим исходную кучу
и получившуюся в результате вставки нового элемента кучу . и . Следовательно, увеличение потенциала составляет . Так как реальное время работы составляет , то амортизированная стоимость данной операции также равна .getMin
Возвращает указатель
. Реальное время работы — .merge
Слияние двух фибоначчиевых куч происходит просто: объединяем списки этих куч в один, релаксируем минимум. Реальное время работы —
. Амортизированное время работы также , поскольку, при объединении двух куч в одну, потенциалы обеих куч суммируются, итоговая сумма потенциалов не изменяется, .extractMin
Первая рассматриваемая операция, в ходе которой меняется структура кучи. Здесь используется вспомогательная процедура лемме .
. Возьмем указатель на , удалим эту вершину. Ее поддеревья (их не более, чем , где — максимальная степень вершины в куче) объединим с корневым списком. Теперь вызываем процедуру . После этой операции в списке корней остается не более чем узлов, среди которых нужно найти минимальный. Итоговая асимптотика операции , учитывая и вспомогательную функцию , время работы которой доказывается ниже, равно: . По доказанной вышеconsolidate
Данная процедура принимает кучу и преобразует ее таким образом, что в корневом списке остается не более
вершин.Для этого возьмем массив списков указателей на корни деревьев
, где — максимальная степень вершины в текущем корневом списке.Затем происходит процесс, аналогичный слиянию биномиальных куч: добавляем поочередно каждый корень, смотря на его степень. Пусть она равна . Если в соответствующей ячейке еще нету вершины, записываем текущую вершину туда. Иначе подвешиваем одно дерево к другому, и пытаемся также добавить дерево, степень корня которого уже равна . Продолжаем, пока не найдем свободную ячейку.
Учетная стоимость
равна . Докажем это:Изначально в корневом списке было не более
вершин, поскольку он состоит из исходного списка корней с узлами, минус извлеченный узел и плюс дочерние узлы, количество которых не превышает . В ходе операции мы сделали слияний деревьев. Потенциал перед извлечением минимума равен , а после не превышает , поскольку в корневом списке остается не более узлов, а количество помеченных узлов не изменяется. Таким образом, амортизированная стоимость не превосходит
Поскольку мы договорились, что можем масштабировать единицу потенциала таким образом, чтобы покрывать константное количество работы, то итоговая амортизационная оценка —
decreaseKey
Основная идея: хотим, чтобы учетная стоимость данной операции была
. Было бы хорошо, чтобы вершина не всплывала до корня, и тогда дерево не придется сильно перестраивать. Для этого при удобном случае будем вырезать поддерево полностью и перемещать его в корневой список. Итак, сам алгоритм:- Проверяем, если новое значение ключа все же не меньше значения ключа родителя, то все хорошо, и мы выходим.
- Иначе, вырезаем дерево с текущей вершиной в корневой список, и производим каскадное вырезание родителя.
cut
При вырезании вершины мы удаляем ее из списка детей своего родителя, уменьшаем степень ее родителя (
) и снимаем пометку с текущей вершины ( ).cascadingCut
Перед вызовом каскадного вырезания нам известно, удаляли ли ребенка у этой вершины. Если у вершины до этого не удаляли дочерний узел (
), то мы помечаем эту вершину ( ) и прекращаем выполнение операции. В противном случае применяем операцию для текущей вершины и запускаем каскадное вырезание от родителя.Пример
Рисунок иллюстрирует пример каскадного вырезания:
- Изначально, куча состояла из фибоначчиевых деревьев. У вершины с ключом отсутствует ребенок.
- Уменьшаем ключ до и делаем операцию этого дерева. Получаем кучу с деревьями и новым минимумом. Но у вершины с ключом был удален второй ребенок, поэтому запускам операцию для этой вершины: вырезаем ее, помещаем в корневой список и помечаем ее родителя.
- У вершины с ключом удален лишь один ребенок, поэтому операция от нее не запускается. В итоге, получаем кучу, состоящую из фибоначчиевых деревьев.
Время работы
Докажем, что амортизированное время работы операции
есть . Поскольку в процедуре нет циклов, ее время работы определяется лишь количеством рекурсивных вызовов каскадного вырезания.Пусть мы вызвали процедуру каскадного вырезания
раз. Так как реальное время работы операции без учета рекурсии составляет , то реальное время работы операции — .Рассмотрим, как изменится потенциал в результате выполнения данной операции. Пусть
— фибоначчиева куча до вызова . Тогда после рекурсивных вызовов операции вершин с пометкой стало как минимум на меньше, потому что каждый вызов каскадного вырезания, за исключением последнего, уменьшает количество помеченных вершин на одну, и в результате последнего вызова одну вершину мы можем пометить. В корневом списке прибавилось новых деревьев ( дерево за счет каскадного вырезания и еще одно из-за самого первого вызова операции ).В итоге, изменение потенциала составляет:
. Следовательно, амортизированная стоимость не превышает . Но поскольку мы можем соответствующим образом масштабировать единицы потенциала, то амортизированная стоимость операции равна .delete
Удаление вершины реализуется через уменьшение ее ключа до
и последующим извлечением минимума. Амортизированное время работы: .Поскольку ранее мы показали, что
, то соответствующие оценки доказаны.Источники информации
- Томас Кормен, Чарльз Лейзерсон, Рональд Ривест, Клиффорд Штайн — Алгоритмы: построение и анализ. — М.: Издательский дом «Вильямс», 2005. — С. 1296. — ISBN 5-8459-0857-4
- Числа Фибоначчи — Википедия
- Фибоначчиева куча — Википедия
- Фибоначчиевы кучи — INTUIT.ru
- Визуализаторы
- Fibonacci Heaps