Загрузил rodzher.zet

Практика по алгоритмам BST, AVL деревья

Реклама
Первый курс, весенний семестр 2020
Практика по алгоритмам #8
BST, AVL
11 апреля
Собрано 15 апреля 2020 г. в 00:58
Содержание
1. BST, AVL
1
2. Разбор задач практики
2
3. Домашнее задание
3.1. Обязательная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3.2. Дополнительная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
7
8
4. Разбор домашнего задания
4.1. Обязательная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2. Дополнительная часть . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
9
11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
BST, AVL
1. Быстрая вставка в BST
Вставка за 𝒪(lower_bound) + 𝒪(1).
2. Минимум на отрезке
Улучшите BST, хранящее пары ⟨𝑥𝑖 , 𝑦𝑖 ⟩: научите его находить min 𝑦𝑖 : 𝐿 6 𝑥𝑖 6 𝑅 за 𝒪(ℎ).
3. Прямой обход
Дан прямой обход BST: рекурсивная запись xLR. За 𝒪(𝑛) восстановить BST.
4. k-min
Доказать, что в AVL-дереве можно посмотреть 𝑘 минимальных за 𝒪(𝑘).
Будет ли это верно для не AVL дерева?
5. Операции над AVL
a) Удаление за 𝒪(log 𝑛).
b) Пусть root.l.h = root.r.h + 3. Как перебалансировать дерево за 𝒪(1)?
c) Пусть root.l.h = root.r.h + k. Как перебалансировать дерево за 𝒪(𝑘)?
d) Merge за 𝒪(log 𝑛) (два способа).
e) Split за 𝒪(log2 𝑛). (*) за 𝒪(log 𝑛).
f) (*) Уменьшите количество дополнительной информации до двух бит на вершину.
6. Простые запросы
a) Запросы: добавить/удалить пару ⟨𝑥, 𝑦⟩; посчитать сумму 𝑦 по всем ⟨𝑥, 𝑦⟩ : 𝑙 6 𝑥 6 𝑟.
b) То же самое и посчитать сумму 𝑥 по всем ⟨𝑥, 𝑦⟩ : 𝑙 6 𝑦 6 𝑟.
c) Запросы: добавить 𝑥; посчитать сумму 𝑥 : 𝑙 6 𝑥 6 𝑟; посчитать сумму 𝑥, добавленных в
моменты времени с 𝑙 по 𝑟.
d) Предыдущая задача плюс запрос удаления 𝑥.
7. Точка в стакане
Дано множество точек на плоскости. Нужно быстро обрабатывать запросы:
∙ добавить/удалить точку,
∙ вывести любую точку внутри области 𝑑𝑖 6 𝑦 6 𝑢𝑖 , 𝑥 6 𝑟𝑖 .
8. Модификации отрезка
a) Запросы: insert(i, x), del(i), get_sum(l, r), set(l, r, value).
b) Добавим запросы reverse(l, r) и rotate(k).
c) (*) То же самое, но add(l, r, value) по модулю 5, а get_sum по-прежнему без модуля.
9. (*) Монотонные пути
Дан взвешенный орграф.
Найти самый длинный путь, на котором рёбра строго возрастают и по номеру, и по весу.
1/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
Разбор задач практики
1. Быстрая вставка в BST
Вставляем 𝑥. Нашли 𝑣 – lower_bound. Если нет левого сына, вешаем туда 𝑥.
Если есть, делаем 𝑥 правым ребенком 𝑢 = prev(𝑣) за 𝒪(1).
Раз 𝑣 – lower_bound, всё его левое поддерево < 𝑥, 𝑥 занял там максимальную позицию. В
поддереве 𝑣 всё ок.
У предков 𝑣, у которых 𝑣 слева, 𝑣 меньше их, 𝑥 6 𝑣.
Предки 𝑣, у которых 𝑣 справа, < 𝑥, поскольку 𝑣 – lower_bound.
Ещё можно вставляться между 𝑣 и 𝑣->left.
2. Минимум на отрезке
Храним в каждой вершине min 𝑦 в поддереве. Также храним диапазон ключей, т.е. min и
max 𝑥 в поддереве.
int get(node* t, int L, int R) {
if (!t) return INF;
if (R < t->min_key || t->max_key < L) // в поддереве t нет x из [L, R]
return INF;
if (L <= t->min_key && t->max_key <= R) // в поддереве t все x из [L, R]
return t->min_y;
return min(get(t->l, L, R), get(t->r, L, R), (L <= t->x <= R ? t->y : INF));
}
На каждом уровне ветвимся только в двух вершинах, которые пересекают [𝐿, 𝑅].
⇒ на каждом уровне смотрим только 4 вершины ⇒ 𝒪(ℎ).
Более хитрый подход: не хранить [min, max] явно, а вычислять границы ключей в вершине в
процессе рекурсии. Если для вершины с ключом 𝑥 знали границы [𝐴, 𝐵] и перешли направо,
то теперь границы станут [𝑥 + 1, 𝐵]. В корне границы равны [−𝑖𝑛𝑓, +𝑖𝑛𝑓 ].
3. Прямой обход
Алгоритм. Идем по массиву. Держим стек.
Видим 𝑥, снимаем со стека все < 𝑥.
Делаем 𝑥 правым ребенком последней снятой. Если ничего не сняли, делаем 𝑥 левым ребенком вершины стека.
Кладем 𝑥 в стек.
Время 𝒪(𝑛), каждое значение один раз положили и один раз сняли.
Смысл. В стеке лежат все вершины, у которых еще не началось правое поддерево (но может
начаться).
Когда встречаем 𝑥, то у всех > 𝑥 еще не кончилось левое поддерево, а у всех < 𝑥 кончилось.
4. k-min
Понятно, что это можно сделать прошивкой, но и наивный алгоритм перехода к следующему
𝑘 раз работает за 𝒪(𝑘).
Правда, все равно нужно хранить указатель на min вершину дерева.
Рассмотрим самую высокую 𝑣 в пути. Мы обошли всё ее левое поддерево и часть правого.
Пусть в левом поддереве 𝑘1 элементов. Левое поддерево обошли за 𝒪(𝑘1 ): по каждому ребру
2/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
прошли один раз вниз и один раз вверх.
В правом поддереве спустились вниз за ℎ𝑣𝑟 6 ℎ𝑣𝑙 + 1 6 𝒪(log 𝑘1 ). Пока потратили 𝒪(𝑘1 ) +
𝒪(log 𝑘1 ) = 𝒪(𝑘1 ).
Затем то же самое в дереве с корнем 𝑣𝑟 , по индукции за 𝒪(𝑘 − 𝑘1 ). В сумме 𝒪(𝑘).
5. Операции над AVL
a) Удаление. Как в обычном BST. Если нет правого сына, вешаем левого на место 𝑣. Иначе
swap(𝑣, next(𝑣)), удалить лист, куда попала 𝑣.
Откатимся по предкам удаленной вершины и перебалансируем их.
Если у вершины уменьшилась глубина, после перебалансировки либо высота предка не
изменится, тогда конец, либо уменьшится на один, тогда нужна перебалансировка выше.
Значит, всё получится перебалансировать. Перебалансировок может понадобиться
Ω(log 𝑛).
c) Перебалансировка при дисбалансе 𝑘
Индукция по 𝑘. Также будем следить, чтобы высота вершины после перебалансировки
либо не менялась, либо уменьшалась на 1.
Пусть у нашей вершины 𝑑 высота ℎ + 1, у левого ребенка 𝑏 высота ℎ, у правого 𝑒 – ℎ − 𝑘.
Дети левого ребенка – 𝑎 и 𝑐.
Делаем поворот вправо. Наша вершина теперь 𝑏, дети – 𝑎 высоты ℎ − 1 или ℎ − 2 и 𝑑.
Дети 𝑑 – 𝑐 высоты ℎ − 1 или ℎ − 2 и 𝑒 высоты ℎ − 𝑘. Тогда высота 𝑑 равна ℎ или ℎ − 1, а
наша – ℎ + 1 или ℎ.
Дисбаланс в 𝑑 не более 𝑘 − 1, исправляем по индукции. В конце у нас (в 𝑏) мог стать
дисбаланс 2, исправляем.
Дисбаланс 2 в конце будет, только если у одного ребенка высота ℎ − 2, а у другого ℎ.
Тогда наша высота перед исправлением дисбаланса 2 равна ℎ + 1, значит, не могла стать
меньше ℎ.
d) Merge за 𝒪(log 𝑛)
Способ #1: удалим из левого дерева крайний правый элемент за 𝒪(log 𝑛). Сделаем его
корнем, подвесим к нему наши два дерева, перебалансируем за 𝒪(log 𝑛), используя прошлый пункт.
Способ #2:
node* Merge(node* l, node* r) {
if (!l || !r) return l ? l : r;
if (l->h <= r->h) {
r->l = Merge(l, r->l), Rebalance(r);
return r;
} else {
l->r = Merge(l->r, r), Rebalance(l);
3/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
return l;
}
}
Можно показать по индукции, что у Merge(l, r) высота 6 max(l.h, r.h) + 1.
⇒ на каждом уровне рекурсии дисбаланс 6 2 и Rebalance работает за 𝒪(1).
⇒ время равно глубине рекурсии, 𝒪(log 𝑛).
На самом деле, при равных высотах не так просто доказать высоту 6 max(l.h, r.h) + 1,
но сложность только техническая, доказывается большим разбором случаев.
e) Split за 𝒪(log2 𝑛)
TreePair Split(node* t, int x) {
if (!t) return {0, 0};
if (t->x < x) {
TreePair p = Split(t->r, x);
return {Merge(t->l, node(t->x, 0, 0), p.l), p.r}
} else {
TreePair p = Split(t->l, x);
return {p.l, Merge(p.r, node(t->x, 0, 0), t->r)}
}
}
Отделение корневого узла нужно, чтобы все сливаемые деревья были корректными
AVL-деревьями. Это универсальный способ сделать Split, когда есть Merge.
Вместо
Merge(t->l, node(t->x, 0, 0), p.l)
можно
делать
t->r = p.l,
Rebalance(t);
𝒪(log 𝑛) уровней рекурсии, на каждом Merge/Rebalance за 𝒪(log 𝑛), итого 𝒪(log2 𝑛).
На самом деле, это работает даже за 𝒪(log 𝑛).
Идея вкратце. На уровнях, где не происходит разрезание ребра, дисбаланс будет 𝒪(1) (из
того, что перебалансировка поворотами убавляет высоту максимум на один).
А там, где ребра разрезаются, происходит перебалансировка за разницу высот деревьев.
При сложении всех сложностей все высоты, кроме верхней, сократятся.
f) (*) Два бита на вершину
В вершине вместо высоты поддерева храним разницу 𝑙.ℎ − 𝑟.ℎ. Это число из {−1, 0, 1}.
Надо узнавать, как 𝑙.ℎ − 𝑟.ℎ изменилась после рекурсивной обработки ребенка (и таким
образом узнавать, есть ли дисбаланс). Для этого из рекурсивных функций, меняющих
дерево к корнем 𝑡, дополнительно возвращаем, насколько изменилась высота 𝑡 (опять же
{−1, 0, 1}).
Нужно пересчитывать разности при поворотах. Для этого можно явно вычислять относительную высоту всех узлов, участвующих в повороте, относительно корня поддерева.
После этого несложно понять, по каким формулам всё пересчитается.
6. Простые запросы
∑︀
a) Добавить/удалить ⟨𝑥, 𝑦⟩;
𝑦 : ⟨𝑥, 𝑦⟩ : 𝑙 6 𝑥 6 𝑟.
Дерево по ключу 𝑥 с дополнительным полем 𝑦. В каждом узле храним сумму 𝑦, min и
max 𝑥 в поддереве, при изменении детей эти величины надо пересчитывать.
int count(node* t, int l, int r) {
if (!t) return 0;
if (r < t->min_key || t->max_key < l) // в поддереве t нет x из [L, R]
return 0;
4/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
if (l <= t->min_key && t->max_key <= r) // в поддереве t все x из [L, R]
return t->sum;
return count(t->l, l, r) + count(t->r, l, r) + (l <= x && x <= r ? t->y : 0);
}
∑︀
∑︀
b) Добавить ⟨𝑥, 𝑦⟩;
𝑦 : ⟨𝑥, 𝑦⟩ : 𝑙 6 𝑥 6 𝑟;
𝑥 : ⟨𝑥, 𝑦⟩ : 𝑙 6 𝑦 6 𝑟.
Два дерева: по ключу 𝑥 с дополнительным полем 𝑦, и по ключу 𝑦 с дополнительным
полем 𝑥.
∑︀
∑︀
cd) Добавить/удалить 𝑥;
𝑥 : 𝑙 6 𝑥 6 𝑟;
𝑥, добавленных с 𝑙 по 𝑟 момент времени.
Можно завести два дерева, как в (b), 𝑦 = времени добавления.
Если нет удаления, вместо второго дерева можно использовать просто массив префиксных
сумм.
7. Точка в стакане
Хранить в каждой вершине BST точку с минимальным 𝑥 в поддереве, тогда можно находить
точку с min 𝑥 на отрезке 𝑑𝑖 6 𝑦 6 𝑢𝑖 и проверять, подошла ли она. Если не подошла, то
такой просто нет.
8. Модификации отрезка
a) add(i, x), del(i), get_sum(l, r), set(l, r, value).
Дерево по неявному ключу на нашем массиве. В каждой вершине храним:
• size – размер поддерева (для неявного ключа),
• sum – сумма в поддереве,
• pending – число, которое мы лениво хотим присвоить в поддерево.
• ts – отметка времени, когда присвоили pending.
Каждый раз, когда заходим в вершину, делаем push – проталкиваем pending вниз, ставя
вершине корректный sum.
Каждый раз, когда меняем вершину, делаем update – пересчитываем size и sum.
Код приводится для случая, когда определен пустой node::null.
void push(node* t) {
if (t == null) return;
for (node* ch : {t->l, t->r})
if (ch->ts < t->ts)
ch->pending += t->pending, ch->ts = t->ts;
t->sum = t->size * t->pending;
}
void update(node* t) {
if (t == null) return;
push(t->l), push(t->r);
t->sum = t->x + t->l->sum + t->r->sum;
t->size = 1 + t->l->size + t->r->size;
}
Тогда:
• insert и del делаются обычным образом, но с добавлением вызовов push и update.
• get_sum(l, r) как в прошлой задаче.
Либо, если есть Split и Merge, вырезанием нужного куска, взятием sum корня и
обратным слиянием.
5/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
• set(l, r, value) аналогично get_sum, но вместо возвращения sum происходит
pending = value, ts = timer++
b) reverse(l, r), rotate(k)
Храним флаг rev – нужно ли разворачивать поддерево. Это ленивый флаг, как pending
в прошлом пункте.
void push(node* t) {
...
if (t->rev) {
t->l->rev ^= 1;
t->r->rev ^= 1;
swap(t->l, t->r);
t->rev = 0;
}
}
rotate(k) = reverse(0, n - k) + reverse(n - k, n) + reverse(0, n)
c) (*) Прибавление по модулю 5, сумма без модуля
Как раньше, но вместо sum в вершине храним массив count[5], в count[i] записано,
сколько раз в поддереве встречается 𝑖. В push теперь будем циклически сдвигать count
и пересчитывать sum через него.
9. (*) Монотонные пути
Раз монотонные, не будет циклов ⇒ длина ответа < 𝑛.
Давайте в каждой вершине 𝑣 хранить в BST пары ⟨𝑑, 𝑤⟩ – в 𝑣 можно закончить путь длины
𝑑 на ребро веса 𝑤, причем 𝑤 минимально.
Заметим, что если есть пары ⟨𝑑1 , 𝑤1 ⟩, ⟨𝑑2 , 𝑤2 ⟩ : 𝑑1 < 𝑑2 , 𝑤1 > 𝑤2 , то первая пара бесполезна,
не будем ее хранить. То есть пары возрастают по обоим параметрам.
Добавляем ребра в граф по увеличению номера. Новое ребро 𝑒 = (𝑢, 𝑣) веса 𝑠 можно добавить
только в конец пути, кончавшегося в 𝑢.
Находим для 𝑢 максимальную пару ⟨𝑑, 𝑤⟩ : 𝑤 < 𝑠. Присоединяем к ней 𝑒. Вставляем в 𝑣 пару
⟨𝑑 + 1, 𝑠⟩.
Выкинем из 𝑣 все пары с меньшим расстоянием, но большим весом.
Пар всего 𝑚, каждая один раз пришла и один раз ушла. Обработка ребра за 𝒪(log 𝑛). Итого
время 𝒪(𝑚 log 𝑛).
6/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
Домашнее задание
3.1. Обязательная часть
1. (1) Построить AVL
Дан отсортированный массив. Построить AVL дерево за 𝒪(𝑛).
2. (3) Глубины вершин в несбалансированном дереве
Рассмотрим процесс добавления ключей в обычное несбалансированное BST.
Одна операция добавления может работать за Ω(𝑛).
В процессе мы поддерживаем struct Node { Node *l, *r, *p; int depth, key; }.
Научитесь моделировать этот процесс за 𝒪(log 𝑛) на запрос добавления.
3. (2) Площадь покрывающего прямоугольника
Придумать структуру данных, которая поддерживает множество точек 𝑆 ⊆ N2 со следующими операциями, каждая за 𝒪(log 𝑛), где |𝑆| = 𝑛:
• добавить/удалить точку (𝑥, 𝑦);
• найти min площадь прямоугольника со сторонами, параллельными осям координат, покрывающего все такие точки из 𝑆, что 𝑥 ∈ [𝑙, 𝑟].
4. (3) Код lower_bound
Напишите Node* lower_bound(Node *v, int x), работающую для произвольного BST.
5. (3) 𝑘 минимальных на отрезке
Пусть дано произвольное сбалансированное BST по ключу 𝑥, хранящее пары ⟨𝑥, 𝑦⟩.
Можно сохранить в вершинах дерева дополнительную информацию, если её можно пересчитывать через детей. Находить 𝑘 минимальных 𝑦 на отрезке 𝑙 6 𝑥 6 𝑟 за 𝒪(𝑘 log 𝑛).
a) (1) В вершине можно хранить сколько угодно информации.
b) (2) В вершине можно хранить 𝒪(1) информации.
6. (2) Все точки в стакане
Необходимо отвечать на запросы для точек на плоскости:
• добавить/удалить точку;
• перечислить все точки в области 𝑑𝑖 6 𝑦 6 𝑢𝑖 , 𝑥 6 𝑟𝑖 за 𝒪(𝑘 log 𝑛),
где 𝑘 – число точек в области.
(+1) балл за 𝒪(𝑘 + log 𝑛) на перечисление.
7/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
3.2. Дополнительная часть
1. (3) Изменений в AVL мало
Начинаем с пустого AVL дерева, делаем 𝑛 операций add. Докажите, что высота вершин
будет меняться суммарное всего лишь 𝒪(𝑛) раз. Иными словами, одна операция add делает
амортизированное 𝒪(1) операций записи в память.
2. (2) 𝑘 минимальных на отрезке нельзя извлекать быстро
Пусть дано произвольное Balanced Search Tree по ключу 𝑥, хранящее пары ⟨𝑥, 𝑦⟩.
Пусть так же это дерево уже построено за время 𝑜(𝑛 log 𝑛) (например, пары были исходно
упорядочены по 𝑥). Можно сохранить в вершинах дерева дополнительную информацию, если
её можно пересчитывать через детей. Докажите, что находить на отрезке 𝑙 6 𝑥 6 𝑟
𝑘 минимальных 𝑦 и удалять их нельзя за 𝒪(𝑘 + log 𝑛).
3. (5) Недо-AVL-дерево
Постройте тест, на котором недо-AVL-дерево, которое делает только малые вращения (см.
код), делает 𝜔(𝑛 log 𝑛) операций после 𝑛 запросов add. Либо докажите, что такого теста нет.
Формально: изначально дерево пусто, нужно 𝑛 раз вызвать add(root, 𝑥𝑖 ) для некоторой последовательности 𝑥𝑖 , что суммарное время работы 𝜔(𝑛 log 𝑛).
void add( node* &t, int x ) {
if (t == node::null)
t = new node(x);
else if (t->x == x)
return;
else if (x < t->x) {
add(t->l, x);
if (t->l->h > t->r->h + 1)
t = rot_left(t);
} else {
add(t->r, x);
if (t->r->h > t->l->h + 1)
t = rot_right(t);
}
t->calc();
}
8/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
Разбор домашнего задания
4.1. Обязательная часть
1. Построить AVL
Корнем будет средняя вершина массива. Рекурсивно строим дерево для правого и левого
подмассива, делаем результаты детьми корня.
𝑇 (𝑛) 6 2𝑇 ( 𝑛2 ) + 𝒪(1) = 𝒪(𝑛).
Высота дерева из 𝑛 вершин равна ⌊log 𝑛⌋ + 1. Индукция. 𝑛 ∈ [2𝑘 , 2𝑘+1 ) ⇒ размер большего
ребенка в [2𝑘−1 , 2𝑘 ), его высота по индукции 𝑘 ⇒ наша высота 𝑘 + 1.
Значит, раз размеры поддеревьев отличаются на 6 1, то же верно для высот, так что действительно AVL.
Другой способ: сделаем полное бинарное дерево нужного размера (как кучу), обойдем его
элементы в 𝐿𝑥𝑅 порядке и последовательно уложим в него элементы массива.
2. Глубины вершин в несбалансированном дереве
Мы знаем, что 𝑥 станет левым ребенком 𝑣 = lower_bound(𝑥), если у 𝑣 еще нет левого ребенка, иначе правым ребенком 𝑢 = prev(𝑣).
Давайте хранить кроме нашего дерева еще сбалансированное, в нем за 𝒪(log 𝑛) можем находить нужные нам 𝑣 и 𝑢.
3. Площадь покрывающего прямоугольника
Храним в BST по 𝑥 пары ⟨𝑥, 𝑦⟩. В каждой вершине также храним min и max 𝑥 и 𝑦 в поддереве.
Каждое из этих значений пересчитывается через детей при каждом изменении дерева.
Ответ на запрос: (max_x − min_x)(max_y − min_y) на отрезке. Находить функцию на отрезке
умеем с практики.
4. Код lower_bound
Node* lower_bound(Node* v, int x) {
Node* ans = 0;
while (v != 0)
if (v->x < x) v = v->r;
else ans = v, v = v->l;
return ans;
}
5. 𝑘 минимальных на отрезке
Способ #1. Хранить 𝑘 минимумов в поддереве, которые при ∀ операции пересчитываем
слиянием ответов в детях. 𝒪(𝑘 log 𝑛) на каждую модификацию дерева и на поиск, 𝒪(𝑘𝑛)
памяти.
Способ #2. Хранить один минимум в поддереве. Удаление и вставка 𝒪(log 𝑛), память 𝒪(𝑛).
Способ #2.1. 𝑘 раз удаляем минимум, в конце надо будет их все вернуть. 𝒪(𝑘 log 𝑛) на
запрос.
Способ #2.2. На каждом из 𝑘 шагов поддерживаем кучу из 𝑘 вершин-кандидатов на очередной минимум.
Сначала кладем в кучу корни поддеревьев, на которые разбивается исходный отрезок (если
9/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
есть split, такой корень один – результат split).
Теперь, пока не вытащим 𝑘 минимумов, берём из кучи поддерево с min элементом.
Если оно состоит из ровно одного элемента, мы нашли очередной min. Иначе разделяем его
на три части – корень, левое поддерево, правое поддерево, и все три части кладём в кучу.
Поддерживаем размер кучи 6 𝑘. Каждый ее элемент проделает путь в худшем случае от
корня до листа ⇒ время 𝒪(𝑘 log 𝑛 log 𝑘).
6. Все точка в стакане
На практике мы уже научились искать одну точку в стакане.
Чтобы найти все, будем их по одной искать и удалять. Потом добавим обратно.
Получили решение за 𝒪(𝑘 log 𝑛), где 𝑘 – размер ответа.
Способ при маленьких или заранее известных 𝑦 – по 𝑦 построить дерево отрезков, а в каждой
вершине дерево поиска по 𝑥.
Добавление и удаление точки за 𝒪(log2 𝑛).
Выдача всех точек в стакане за 𝒪(𝑘 +log 𝑛) (выдать префикс размера 𝑠 дерева поиска можно
за время 𝒪(𝑠)).
10/11
Алгоритмы, весна 2020
Практика #8. BST, AVL.
4.2. Дополнительная часть
1. Изменений в AVL мало
Рассмотрим вершину на высоте ℎ в итоговом дереве. Ее высота ℎ раз увеличивалась с нуля
за счет изменения высот при добавлении без учета поворотов.
Кроме этого, высоты некоторых вершин уменьшались при поворотах.
При каждом добавлении не более одного поворота. При каждом повороте сумма высот падает
на 6 2.
Итого уменьшений 6 2𝑛, «компенсирующих» их увеличений 6 2𝑛.
Число изменений высот без учета поворотов аналогично времени построения бинарной кучи.
Итоговая высота 𝐻 6 log2 𝑛. На высоте 𝐻 − 𝑖 находится 6 2𝑖 вершин. Суммируем 2𝑖 (𝐻 − 𝑖),
получаем 2𝐻+1 6 2𝑛.
2. 𝑘 минимальных на отрезке нельзя извлекать быстро
Пусть можно ⇒ научимся сортировать за 𝒪(𝑛 log log 𝑛), что невозможно ⇒ противоречие.
Строим за 𝒪(𝑛) по массиву 𝑎 дерево на парах ⟨𝑖, 𝑎[𝑖]⟩.
Повторим 𝑛/ log 𝑛 раз: удалим log 𝑛 минимумов по 𝑦 из всего дерева (т.е. на отрезке
−∞ 6 𝑥 6 ∞).
По предположению удаление работает за 𝒪(log 𝑛). Отсортируем удалённые элементы за
𝒪(log 𝑛 log log 𝑛).
Получили алгоритм, сортирующий массив за (𝑛/ log 𝑛)(log 𝑛 + log 𝑛 log log 𝑛) = 𝒪(𝑛 log log 𝑛).
3. Недо-AVL-дерево
Мы полагаем, что контр-теста не существует. Доказывать данную гипотезу не умеем.
11/11
Скачать