Фібоначчієва купа
Купа Фібоначчі — структура даних, ефективна реалізація черги з пріоритетом.
Операція | Амортизаційний час |
---|---|
Make-Heap | |
Insert | |
Minimum | |
Extract-Min | |
Union | |
Decrease-Key | |
Delete |
З теоретичної точки зору купи Фібоначчі особливо варто використовувати, коли кількість Extract-Min і Delete операцій мала порівняно з кількістю інших операцій. Наприклад, деякі алгоритми на графах можуть викликати Decrease-Key на кожному ребрі. Для щільного графу сталий амортизаційний час кожного виклику Decrease-Key складається у велику перевагу в порівнянні з логарифмічним часом у найгіршому випадку в бінарній купі. Це можна побачити на прикладі алгоритмів про найкоротші шляхи з одного входу і мінімального кістякового дерева. З практичної точки зору сталий множник прихований у складності алгоритму і складність у програмуванні купи Фібоначчі роблять її менш бажаною, ніж звичайну бінарну або d-арну купу для більшості застосувань.
Історія
Фібоначчієву купу запропонували Фредман і Тар'ян у 1984 році. Головним гаслом цієї конструкції було «ми виконуємо роботу лише коли мусимо, і тоді ми використовуємо це для спрощення структури настільки наскільки можливо, таким чином, щоб майбутня робота була легкою».
Структура купи
Купа Фібоначчі являє собою набір дерев, кожне з яких є мінкупою змінної арності з такими властивостями:
- Вузли дерев відповідають елементам, що зберігаються в черзі.
- Корені куповпорядкованих дерев поєднані у двобічно зв'язаний список.
- Зберігається вказівник на корінь дерева, який відповідає елементу з найменшим ключем (варто зауважити, що те, що кожне дерево є мінкупою, гарантує, що цей елемент буде коренем одного з дерев).
- Для кожного вузла зберігається його ранг (степінь), тобто кількість його дітей, а також чи він позначений (мету позначення ми визначимо пізніше).
- Вимога розміру: якщо вузол u має степінь k, то піддерево з коренем u має щонайменше вузлів, де — це i-те число Фібоначчі, тобто і для
Кожен вузол x містить вказівник x.p на свого предка і вказівник x.child на один із його нащадків. Нащадки зв'язані в циклічний двобічно зв'язаний список. Кожен нащадок y має вказівники y.left та y.right. Якщо вузол є єдиним нащадком, тоді y = y.left = y.right. Нащадки елемента можуть перебувати у будь-якому порядку. Використання двобічно зв'язаних списків уможливлює вставляння і видалення елементів за час O(1), а також зв'язування двох списків за O(1). Також кожен вузол має атрибут x.degree, що дорівнює кількості нащадків, і атрибут x.mark, що показує, чи втратив вузол нащадка з моменту, як він став нащадком свого поточного предка.
Доступ до купи відбувається через вказівник H.min, який вказує на корінь дерева з найменшим ключем. Якщо дерево порожнє, то H.min дорівнює NIL.
Для оцінки швидкості використовують метод потенціалів із потенціальною функцією Де дає кількість дерев у купі, а — кількість позначених вузів.
- Максимальний степінь
Надалі вважатимемо, що нам відома верхня межа D(n) максимального степеня вузла у Фібоначчієвій купі з n вузлами. Якщо купа підтримує лише операції поєднуваної купи, тоді Хоча, навіть якщо Фібоначчієва купа підтримує Decrease-Key та Delete, то
Операції
INSERT
Вставка дужа проста: додаємо новий елемент s як нову мінкупу в колекцію і перевіряємо, чи k(s) менше за поточне найменше значення. Якщо так, то відповідно змінюємо вказівник H.min.
Insert(H, x) 1 x.degree = 0 2 x.p = NIL 3 x.child = NIL 4 x.mark = FALSE 5 якщо H.min == NIL 6 створити список коренів для H, що містить лише x 7 H.min = x 8 інакше вставити x у список коренів H 9 якщо x.key < H.min.key 10 H.min = x 11 H.n = H.n + 1
Щоб визначити амортизаційну вартість вставляння елемента, нехай H буде початковою Фібоначчієвою купою і H' буде результовною. Тоді і отже збільшення потенціалу становить
Оскільки амортизаційна вартість дорівнює сумі дійсної вартості і різниці потенціалів, то вона дорівнює
DECREASE-KEY
Коли ми зменшуємо ключ елемента s, якщо умови мінкупи все ще задовільняються, то нам більше не потрібно робити нічого. Інакше, ми просто відтинаємо піддерево з коренем в s і вставляємо його як нове піддерево у колекцію. Ми порівнюємо новий ключ s із попереднім мінімальним елементом і змінюємо вказівник H.min відповідно.
Таким чином, ми отримуємо щось, що виглядає подібно до бажаної Фібоначчієвої купи. Однак, проблема із простим відтинанням кожного такого s полягає в тому, що ми можемо врешті решт порушити вимогу розміру. Отже, щоб полегшити ситуацію, ми впроваджуємо додаткове правило, що коли ми відтинаємо s ми перевіряємо, чи не помічений його батьківський елемент. Якщо так, то ми також відтинаємо батьківській елемент і знімаємо позначку з нього. Інакше, ми просто позначаємо батьківський елемент. Перевірка батьківського елемента виконується рекурсивно, тобто, якщо батько батька позначений, ми відтинаємо і його також. Очевидно, що якщо ми відтинаємо корінь, то ми не робимо нічого, тому немає сенсу позначати корінь. Тому, ця потенційно каскадна процедура, завжди завершується.
Decrease-Key(H,x,k)
1 якщо k > x.key
2 помилка "новий ключ більший ніж попередній"
3 x.key = k
4 y = x.p
5 якщо y != NIL і x.key < y.key
6 Cut(H,x,y)
7 Cascading-Cut(H,y)
8 якщо x.key < H.min.key
9 H.min = x
Cut(H,x,y) 1 видалити x зі списку дітей y, зменшити y.degree 2 додати до списку коренів H 3 x.p = NIL 4 x.mark = FALSE
Cascading-Cut(H,y)
1 z = y.p
2 якщо z != NIL
3 якщо y.mark == FALSE
4 y.mark = TRUE
5 інакше Cut(H,y,z)
6 Cascading-Cut(H,z)
З'ясуємо дійсну вартість зменшення ключа. Decrease-Key потребує O(1), не враховуючи каскадного видалення. Припустимо, що відбулось c викликів Cascading-Cut. Виконання кожного Cascading-Cut, не враховуючи рекурсивних викликів, потребує O(1). Отже дійсна вартість Decrease-Key становить O(c).
Тепер обчислимо різницю потенціалів. У висліді зменшення ключа утворюється c-1 нових дерев через каскадні відтинання і дерево з коренем x. c-1 вузів припиняють бути позначеними. І, можливо, позначається один вузол.
З цього випливає, що амортизаційна вартість буде не більше ніж
EXTRACT-MIN
Насамкінець, ми можемо описати видобування мінімального елемента s*. Починаємо з видалення s* (на нього вказує H.min) і додавання усіх його дітей як дерев у колекцію. Тепер, ми продивляємось усю колекцію, знаходимо найменший елемент і оновлюємо H.min відповідно.
На цьому можна було б і завершити, оскільки ми отримали правильну Фібоначчієву купу. Однак, не складно побачити, що досі описані операції над купою, роблять список коренів довшим і довшим. Отже, проходження через увесь список коренів ставатиме обчислювально дорожчим. Отже, якщо ми вже маємо зробити якусь роботу у будь-якому випадку, то ми виконаємо очищення зараз, щоб уникнути більшої роботи у наступному. Тому, доти доки наявні два дерева з однаковим рангом, скажімо k, ми зливаємо ці дерева, щоб отримати дерево рангу k+1. Злиття полягає у порівнянні коренів і додавання дерева з більшим коренем як дочірнього до другого кореня. Зауважимо, що оскільки злиття може створити друге дерево рангу k+1 у колекції, один корінь може взяти участь у кількох злиттях.
Extract-Min(H)
1 z = H.min
2 якщо z != NIL
3 для кожної дитини x z
4 додати x до списку коренів H
5 x.p = NIL
6 видалити z зі списку коренів H
7 якщо z == z.right // Вважаємо, що видалення z із двобічного списку не змінює її right/left вказівників
8 H.min = NIL
9 інакше H.min = z.right
10 Consolidate(H)
11 H.n = H.n - 1
12 повернути z
Після видалення z ми консолідуємо список коренів H. Консолідація відбувається виконуючи наступні кроки допоки у списку коренів присутні корені з однаковим степенем.
- Знайти два корені x, y з однаковим степенем. Припустимо, без втрати загальності, що x.key =< y.key.
- Приєднуємо y до x. Тут ми збільшуємо x.degree і очищаємо позначку на y.
Consolidate(H) 1 нехай A[0..D(H.n)] буде новим масивом 2 для i = 0 до D(H.n) 3 A[i] = NIL 4 для кожного вузла w у списку коренів H 5 x = w 6 d = x.degree 7 поки A[d] != NIL 8 y = A[d] // Інший корінь з тим самим степенем як і x 9 якщо x.key > y.key 10 обміняти x і y 11 Heap-Link(H,y,x) 12 A[j] = NIL 13 d = d + 1 14 A[d] = x 15 H.min = NIL 16 для i = 0 до D(H.n) 17 якщо A[i] != NIL 18 якщо H.min == NIL 19 створити список коренів для H, що містить лише A[i] 20 H.min = A[i] 21 інакше вставити A[i] у список коренів H 22 якщо A[i].key < H.min.key 23 H.min = A[i]
Heap-Link(H,y,x) 1 видалити y зі списку коренів H 2 зробити y дочірнім для x, збільшити x.degree 3 y.mark = FALSE
Обчислимо дійсну вартість видобування найменшого елемента. O(D(n)) маємо завдяки Extract-Min, через необхідність обробити усі дочірні вузли, і завдяки циклам 2-3 і 16-23 у Consolidate. Розглянемо внесок циклу 4-14 Consolidate, для цього використаємо груповий аналіз. Розмір списку коренів перед викликом Consolidate не більше ніж D(n) + t(H) + 1. Ми знаємо, що кожен раз у тілі циклу while один з коренів приєднується до іншого, отже, загальна кількість ітерацій циклу while у всіх ітераціях зовнішнього циклу for не може перевищити розмір списку коренів. Тому загальна кількість роботи у циклі 4-14 щонайбільше пропорційна до D(n) + t(H). Отже, всього нам треба O(D(n) + t(H)).
Потенціал перед видобуванням мінімального вузла є t(H) + 2m(H), а після — не більше ніж D(n) + 1. З цього і дійсної вартості маємо амортизаційну вартість
Обмеження на найбільший степінь
Лема. Нехай це вузол, і нехай позначають його дітей у порядку в якому вони були додані до Тоді: Доведення: Очевидно, що y1.degree ≥ 0. Для i ≥ 2, коли ми yi додавали до x, там вже було щонайменше i-1 дочірніх елементів, значить на момент додавання степінь yi мав дорівнювати степеню x. З того часу один з дочірніх елементів y міг бути відрізаним. Отже, степінь yi міг зменшитись на 1 і стати i-2.∎
Факти про числа Фібоначчі: 1. 2. де
Лема. Нехай x буде вузлом у купі Фібоначчі і нехай k = x.degree. Тоді size(x) ≥ Fk+2 ≥ φk.
Наслідок: Найбільший степінь D(n) будь-якого вузла в n-вузловій Фібоначчієвій купі є O(lg n). Доведення: Нехай x буде довільним вузлом і k=x.degree. Маємо, що n ≥ size(x) ≥ φk. Логарифмуючи з основою φ отримуємо k ≤ logφ n. Оскільки k ціле, то ∎
Джерела
- Томас Кормен; Чарльз Лейзерсон, Рональд Рівест, Кліфорд Стайн (2009) [1990]. 19 Fibonacci Heaps. Вступ до алгоритмів (вид. 3rd). MIT Press і McGraw-Hill. с. 505–530. ISBN 0-262-03384-4.
- Fibonacci Heaps на www.cs.princeton.edu