Кучи (15 октября 2024)
- [15 минут] V.E.B за loglogC
- Сформулируем задачу: heap для целых чисел от 0 до C-1. C = 22k (чисел битовой длины 2k).
- Интерфейс Min = O(1), Add и Del = O(k) = O(loglogC).
- Случаи: size = 0, size = 1, size ≥ 2,
struct Heapk: { Heapk-1 a; u_map<int,Heapk-1> b; int min, size; }
.
- Реализуем Add, пересчёт Min
- Реализуем ExtractMin
- [15 минут] MinMax heap [1986, Atkinson]. Inplace. Min, Max.
- У нас уже была Min-Max куча: хранить две бинарных и обратные ссылки.
- Новая идея: чётный уровень = max, нечётный уровень = min
- Операции Add, ExtractMin из обычной бинарной кучей выражаются через siftUp, siftDown, поэтому пропатчим siftUp, siftDown
- siftUp: сперва можем свопнуться в отцом, далее прыгаем на два вверх (1/2)
- siftDown: берём минимального из четырёх внуков, свпопаемся, решаем конфликт с отцом (5/2)
- [15 минут] Leftist heap [1971, Clark].
- merge → add, (для бинарной) extractMin.
- struct: l, r, x, d (ввели ссылочную структуру вместо массива)
- d(v) = (расстояние вниз от вершины до ближайшего ``отсутствия ребенка'') - 1 ≤ log n
- Левацкая (leftist):
d(l[v]) ≥ d(r[v]) ⇒ d(r[v]) ≤ d(v) - 1
Add(x): Merge with {x}
Merge: let x.key < y.key ⇒ x.right = merge(x.right, y)
if d[l[v]] < d[r[v]] then swap(l[v], r[v])
- [10 минут] skew heap [1986, Tarjan]
- Укорачиваем код merge из leftist
- Рассказ про лёгкие и тяжёлые рёбра, показываем, за сколько работает: k + logn
- Придумываем потенциал "число правых тяжёлых рёбер"
- (можно скипать) Альтернативный потенциал: число вершин, у которых d(r[v]) > d(l[v]).
− Перерыв −
- [25 минут] Списко-куча
- Мотивация: куча должна уметь Add, Min, Merge, DecreaseKey, ExtractMin. Обычная бинарная умеет всё кроме Merge за log, Merge за log2. Сегодня изучили Leftist м Skew, которые умеют всё за log. Наша конечная цель − куча Фибоначчи, которая всё кроме ExtractMin умеет за O(1), ExtractMin за O(logn).
- Куча, которая умеет Add за O(1), Min за O(1), Merge за O(1), DecreaseKey за O(1), ExtractMin за O(n)
- План, как хотим самортизировать ExtractMin
- Турнирные деревья
- Quake Куча
- [15 минут] heap: lower bound on build
- build: permutation → heap. Общий способ доказательства нижних оценок: если k сравнениями мы из n! перестановок выдаём одну из H(n), то нужно log(n!/H(n)) сравнений.
- Док-во: пусть всего есть H(n) куч, а процедура делает не более k сравнений, она разбивает n! перестановок на 2k классов, в одном хотя бы n!/2k,
после build() все они различны, все они корректные кучи ⇒ H(n) ≥ n!/2^k
- Сколько есть различных корректных куч? H(n) = n! / ∏ sizei = n! / n(n/2)2(n/4)4... Доказываем по индукции: цэшка, факторилы сократятся.
− Перерыв −
- Бонусная часть: pairing heap [1984, Fredman & Tarjan]
- Список → дерево. Хранение. Pairing для extractMin (null, onlyone, ret pair(pair(0, 1), pairing(2)))
- Время работы. Для всех операций кроме DecreasyKey O(logn).
- DecreasyKey: два варианта
- DecreasyKey, оценки: Omega(loglogn) [Fredman, 1999], O(22loglogn1/2) [Pettie, 2005], [Amr Elmasry (2009) уменьшил время до O(loglogn)]
Кучи (продолжение, которого не будет)
- [10 минут] Недофибоначчи. Разбор домашнего задания.
- Решение 1: Храним список из n1/2 кусков по n1/2 элементов. Add: добавить в последний кусок, нужно хранить размер. Merge: у нас есть два мелких куска, сольём в один, разрешим [n1/2..2n1/2]
- Решение 2: Храним список кусок размера ≤n1/2. Add и Merge = соединить списки. ExtractMin − совместить слишком мелкие списки.
- DecreaseKey: обратная ссылка на элемент + уменьшили его, уменьшили минимум куска (но не минимум всех кусков!)
- [20 минут] Биномиальная [1978, Vuillemin]
- Определяем деревья (Tn+1 = Tn + Tn), леммы про них (размер, степень, дети, глубина), определяем кучу (список деревьев различных рангов)
- Как хранить? {next, child, x, rank}. По ссылкам next односвязный список. Куча = список корней.
- Пишем add в список, join(Tn, Tn). 2D-картинка со списками.
- extractMin: нашли минимум, удалили из списка; вызвали merge для списка корней и списка детей.
- merge: используем
vector<Node*> roots[maxLog]
, складываем туда все корни. Время работы = Roots + maxLog.
- Add и Merge за O(1) (ленивое добавление, слияние)
- Пишем Build за O(n) = n * Add, важно показать, как он выглядит внутри.
− Перерыв −
- [40 минут] Фибоначчи [1984, Fredman & Tarjan]
- Описание кучи без decreaseKey: биномиальная куча с Add и Merge за O(1)
- decreaseKey: алгоритм (отрезаем себя и или помечаем отца, или рекурсивно идём в него)
- Доказательство: лемма про числа фибоначчи Fn = 1 + Fn-2 + Fn-3 + Fn-4..., фибоначчиевы деревья Sizen ≥ Fn.
- Доказательство: интуиция с числом единиц, ранг дерева, правильный инвариант