
- •3.7. Древовидные структуры для задачи объединить–найти
- •Алгоритм 3.3. Алгоритм быстрого объединения непересекающихся множеств
- •VОтец [I]
- •3.8. Приложения и обобщения алгоритма объединить–найти
- •Приложение 2. Задача определения глубины
- •3.9. Схемы сбалансированных деревьев
- •3.10. Словари и очереди с приоритетами
- •Алгоритм 3.4. Вставка нового элемента в 2-3-дерево
- •3.11. Сливаемые деревья
- •3.12. Сцепляемые очереди
- •3.13. Разбиение
Пример 3.6. Пусть n = 8 и у нас есть набор из трех множеств {1, 3, 5, 7}, {2, 4, 8} и {6} с внешними именами 1, 2 и 3 соответственно. Структуры данных для этих трех множеств показаны на рис. 3.13, где 2,3 и 1 – внутренние имена для 1,2 и 3 соответственно.
Операция НАЙТИ (i) выполняется, как и раньше, обращением к R[i] для установления внутреннего имени множества, содержащего элемент i в данный момент. Затем ВНЕШ_ИМЯ[R[(i)]] дает настоящее имя множества, которому принадлежит i.
Операцию объединения вида ОБЪЕДИНИТЬ (I, J, К) выполняем следующим образом. (Номера строк относятся к рис. 3.14.)
1. Определяем внутренние имена для множеств I и J (строки 1,2).
2. Сравниваем относительные размеры множеств I и J, справляясь в массиве РАЗМЕР (строки 3, 4).
3. Проходим список элементов меньшего множества и изменяем соответствующие компоненты в массиве R на внутреннее имя большего множества (строки 5–9).
4. Вливаем меньшее множество в большее, добавляя список элементов меньшего множества к началу списка для большего множества (строки 10–12).
5. Присваиваем полученному множеству внешнее имя К (строки 13, 14).
Вливая меньшее множество в большее, мы делаем время выполнения операции ОБЪЕДИНИТЬ пропорциональным мощности меньшего множества. Все детали приведены в процедуре на рис. 3.14.
Пример 3.7. После выполнения операции ОБЪЕДИНИТЬ (1, 2, 4) структура данных рис. 3.13 превратится в структуру данных, изображенную на рис. 3.15.
Теорема 3.3. С помощью алгоритма рис. 3.14 можно выполнить п - 1 (максимально возможное число) операций ОБЪЕДИНИТЬ за O (п log n) шагов.
Доказательство. За каждое выполнение операции ОБЪЕДИНИТЬ будем налагать на перемещаемые элементы одинаковые штрафы, в сумме равные сложности этого выполнения. Так как сложность пропорциональна числу перемещаемых элементов, то величина штрафа, налагаемого на один элемент, будет всегда одна и та же. Основное здесь – это заметить, что всякий раз, когда элемент перемещается из списка, он оказывается в списке, по крайней мере в два раза длиннее прежнего. Поэтому никакой элемент нельзя переместить более чем log n раз и, значит, суммарный штраф, налагаемый на один элемент, составляет O (log n).
Общая сложность получается суммированием штрафов, наложенных на отдельные элементы. Таким образом, общая сложность есть O(п log n) .
Из теоремы 3.3 вытекает, что если выполняется т операций НАЙТИ и до п - 1 операций ОБЪЕДИНИТЬ, то тратится время O (МАХ (m, n log п)). Если т имеет порядок п log п или больше, то этот алгоритм действительно оптимален с точностью до постоянного множителя. Однако во многих ситуациях т есть O(п), а в таком случае, как мы увидим в следующем разделе, можно добиться лучшего времени, нежели O (МАХ (m, п log п)),
3.7. Древовидные структуры для задачи объединить–найти
В предыдущем разделе мы познакомились со структурой данных для задачи ОБЪЕДИНИТЬ – НАЙТИ, позволяющей выполнить п - 1 операций ОБЪЕДИНИТЬ и O(п log п) операций НАЙТИ за время O(n log n). В данном разделе будет описана структура данных, которая состоит из. деревьев, образующих лес, и предназначена для представления набора множеств. Эта структура данных позволит выполнить O(п) операций ОБЪЕДИНИТЬ и НАЙТИ за почти линейное время.
Допустим, что каждое множество А представлено корневым нeориентированным деревом ТA, узлам которого поставлены в соответствие элементы из А. Корню этого дерева приписано имя самого множества. Операцию ОБЪЕДИНИТЬ (A, B, C) можно выполнить, преобразуя корень дерева ТA в сына корня дерева ТB и заменяя имя в корне дерева TB на С. Операцию НАЙТИ (i) можно выполнить, определяя положение узла, представляющего элемент i в некотором дереве Т из леса, и проходя путь из этого узла в корень дерева T, где помещено имя множества, содержащего i.
В такой схеме сложность слияния двух деревьев равна постоянной. Однако сложность выполнения операции НАЙТИ (i) имеет порядок длины пути из узла i в корень. Эта длина может быть п–1. Поэтому сложность выполнения п–1 операций ОБЪЕДИНИТЬ, за которыми идут п операций НАЙТИ, может достигать O(п2). Например, вычислим сложность выполнения последовательности операций
ОБЪЕДИНИТЬ (1, 2, 2)
ОБЪЕДИНИТЬ (2, 3, 3)
.
.
.
ОБЪЕДИНИТЬ(n - 1, п, п)
НАЙТИ(1)
НАЙТИ(2)
.
.
.
НАЙТИ(n)
Выполнение п - 1 операций ОБЪЕДИНИТЬ приводит к дереву, изображенному на рис. 3.16. Сложность выполнения п операций НАЙТИ пропорциональна
Однако её можно уменьшить, если проводить балансировку деревьев. Для этого можно в каждом дереве считать число узлов и, сливая два множества, всегда присоединять меньшее дерево к корню большего. Этот способ аналогичен вливанию меньшего множества в большее, которое мы применяли в предыдущем разделе.
Лемма 3.1. Если при выполнении каждой операции ОБЪЕДИНИТЬ корень дерева с меньшим числом узлов (при равенстве узлов берется любое дерево) преобразуется в сына корня дерева с большим числом узлов, то высота дерева в лесу может достичь значения h, только если оно имеет не менее 2h узлов.
Доказательство.
Доказательство проведем индукцией по
h.
Для h
= 0 утверждение
верно, поскольку каждое дерево имеет
по крайней мере, один узел. Допустим,
что утверждение верно для всех значений
параметра, меньших
.
Пусть Т –
дерево высоты
h
с наи-
Рис. 3.16. Дерево после выполнения операций ОБЪЕДИНИТЬ.
меньшим числом узлов. Тогда оно должно было получиться в результате слияния деревьев T1 и T2, где T2 дерево высоты i - 1 и у него узлов не больше, чем у Т2. По предположению индукции T1 имеет не менее 2h-1 узлов, и, значит, Т2 имеет не менее 2h-1 узлов, откуда следует, что Т имеет не менее 2h узлов.
Оценим время выполнения (в худшем случае) последовательности из п операций ОБЪЕДИНИТЬ и НАЙТИ, если пользоваться структурой данных в виде леса, модифицированной так, что в операции ОБЪЕДИНИТЬ корень меньшего дерева становится сыном корня большего дерева. Никакое дерево не может иметь высоту, большую log п. Следовательно, сложность выполнения O(п) операций ОБЪЕДИНИТЬ и НАЙТИ не превосходит O(п log п). Эта граница точна в том смысле, что существуют последовательности из п операций, занимающие время, пропорциональное п log п.
Изложим
еще одну модификацию этого алгоритма,
называемую сжатием
путей.
Поскольку в общей сложности преобладает
сложность операций НАЙТИ, попробуем
уменьшить ее. Всякий раз, когда выполняется
операция НАЙТИ (i),
мы проходим путь от узла i
до корня r.
Пусть
– узлы на этом пути. Тогда каждый из
узлов
делается сыном корня. На рис. 3.17, б
отражено влияние операции НАЙТИ (i)
на дерево, приведенное на рис. 3.17, а
Вся процедура слияния деревьев для задачи ОБЪЕДИНИТЬ – НАЙТИ, включая сжатие путей, выражена в следующем алгоритме.