
- •3.7. Древовидные структуры для задачи объединить–найти
- •Алгоритм 3.3. Алгоритм быстрого объединения непересекающихся множеств
- •VОтец [I]
- •3.8. Приложения и обобщения алгоритма объединить–найти
- •Приложение 2. Задача определения глубины
- •3.9. Схемы сбалансированных деревьев
- •3.10. Словари и очереди с приоритетами
- •Алгоритм 3.4. Вставка нового элемента в 2-3-дерево
- •3.11. Сливаемые деревья
- •3.12. Сцепляемые очереди
- •3.13. Разбиение
3.11. Сливаемые деревья
В данном разделе мы познакомимся со структурой данных, с помощью которой можно выполнить последовательность из операций ВСТАВИТЬ, УДАЛИТЬ, ОБЪЕДИНИТЬ и MIN за время O(п log п). В этой структуре, которую можно воспринимать как обобщение сортирующего дерева, рассмотренного в разд. 3.4, множество элементов S представляется 2-3-деревом Т. Каждый элемент из S появляется в виде метки листа дерева Т, но множество листьев не упорядочено, как это было в двух предыдущих разделах. Каждый внутренний узел дерева Т пометим значением НАИМЕНЬШИЙ[v], т. е. значением наименьшего элемента, хранящегося в поддереве с корнем v. Для этого применения 2-3-деревьев L[v] и M[v] не нужны.
Наименьший элемент множества S можно найти, если следующим образом двигаться вниз по дереву Т, начиная от его корня. Находясь во внутреннем узле v, переходим к сыну узла v помеченному наименьшим значением функции НАИМЕНЬШИЙ. Следовательно, если Т содержит п листьев, то операция MIN занимает O(log п) шагов.
Во многих приложениях всякий раз, когда надо удалить из S какой-то элемент, он всегда наименьший. Но если мы хотим удалить из S произвольный элемент, мы должны уметь найти лист, содержащий его. В тех приложениях, где элементы можно представить целыми числами 1, 2, .... п, можно пронумеровать сами листья. Если же элементы произвольны, то можно воспользоваться вспомогательным 2-3-словарем, листья которого содержат указатели на листья дерева Т. С помощью этого словаря можно достичь произвольного листа за O(log n) шагов. Словарь надо корректировать
procedure ИМПЛАНТАЦИЯ(Т1, Т2):
if ВЫСОТА(Т1) = ВЫСОТА(Т2) then
begin
образовать новый корень r;
сделать KOPEHЬ [T1] и КОРЕНЬ[Т2] соответственно левым и правым сыновьями узла r;
end
else
wlg положим ВЫСОТА(Т1) > ВЫСОТА(Т2) otherwise переставить Т1 с Т2 и "левый" с "правым" in
begin
пусть v–такой узел на самом правом пути в T1, что глубина(v) = ВЫСОТА(T1) -ВЫСОТА(T2);
пусть f - отец узла v;
сделать КОРЕНЬ[T2] сыном узла f, расположенным непосредственно справа от v;
if у f сейчас четыре сына then ДОБАВСЫНА(f)1
end
Рис. 3.32. Процедура ИМПЛАНТАЦИЯ.
всякий раз, когда выполняется операция ВСТАВИТЬ, но это требует не более O(logn) шагов.
Коль скоро лист l удален из T, надо для каждого его подлинного предка v пересчитать значения функции НАИМЕНЬШИЙ. Новым значением для НАИМЕНЬШИЙ[v] будет наименьшее из значений НАИМЕНЬШИЙЫ для двух или трех сыновей s узла v. Если всегда пересчитывать снизу вверх, то индукцией по числу пересчетов можно показать, что каждое вычисление дает для функции НАИМЕНЬШИЙ правильный ответ. Так как эта функция меняется только в предках удаленного листа, то операцию УДАЛИТЬ можно выполнить за O(log n) шагов.
Изучим операцию ОБЪЕДИНИТЬ. Каждое множество представлено отдельным 2-3-деревом. Чтобы слить два множества S1 и S2, вызываем процедуру ИМПЛАНТАЦИЯ (T1, Т2), приведенную на рис. 3.32, где T1 и Т2 – это 2-3-деревья, представляющие S1 и S21.
Пусть высота h1 дерева T1 не меньше высоты h2 дерева T2. ИМПЛАНТАЦИЯ находит на самом правом пути в T1 узел v с высотой h2 и делает корень дерева T2 его самым правым братом. Если у его отца f окажется четыре сына, ИМПЛАНТАЦИЯ вызовет процедуру ДОБАВСЫНА(f). Значения функции НАИМЕНЬШИЙ на узлах, потомки которых изменяются в процессе выполнения процедуры ИМПЛАНТАЦИЯ, можно скорректировать тем же способом, что и в операции УДАЛИТЬ.
В качестве упражнения предлагаем показать, что процедура ИМПЛАНТАЦИЯ соединяет T1 и Т2 в одно 2-3-дерево за время O(h1 – h2) (при h1 h2). Если учитывать время на корректировку значений L и М, то процедура ИМПЛАНТАЦИЯ может занять O(MAX(log||S1||, log||S2||) времени.
Рассмотрим теперь приложение, в котором естественно возникают операции ОБЪЕДИНИТЬ, MIN и УДАЛИТЬ.
Пример 3.12. В примере 3.1 мы изложили алгоритм для нахождения остовных деревьев наименьшей стоимости. Он формировал из узлов все большие и большие множества, такие, что элементы каждого из них соединялись ребрами, выбранными для остовного дерева наименьшей стоимости. Стратегия нахождения новых ребер для этого остовного дерева состояла в том, чтобы перебирать ребра (сначала наименьшей стоимости) и проверять, соединяют ли они какие-нибудь еще не соединенные узлы.
Рассмотрим другую стратегию. Для каждого множества узлов Vi будем хранить множество Ei всех нерассмотренных ребер, инцидентных каким-то узлам в Vi. Если выбрать не рассмотренное ранее ребро е, инцидентное узлу из относительно малого множества Vi то другой конец ребра е с большой вероятностью не будет лежать в Vi, и можно будет добавить е к остовному дереву. Если это нерассмотренное ребро е обладает наименьшей стоимостью среди всех ребер, инцидентных узлам из Vi, тo можно показать, что включение его в остовное дерево приведет к остовному дереву наименьшей стоимости.
Для реализации этого алгоритма надо сначала образовать для каждого узла множество инцидентных ему ребер. Чтобы среди ребер, инцидентных узлам из Vi, найти нерассмотренное ребро наименьшей стоимости, применим MIN-оператор к множеству Ei нерассмотренных ребер для Vi. Затем удалим из Ei найденное так ребро е. Если окажется, что е имеет в Vi только один конец, а другой лежит в множестве Vi, отличном от Vi, то выполним операцию ОБЪЕДИНИТЬ для Vi и Vi (например, используя структуру данных алгоритма 3.3), а также для Ei и Еi.
В качестве структуры данных для представления каждого множества ребер Ei можно взять 2-3-дерево, каждый лист которого помечен ребром и его стоимостью. На множестве ребер нет никакого специального порядка. Каждому нелисту приписана наименьшая стоимость его потомков-листьев, обозначаемая НАИМЕНЬШИМ.
Вначале для каждого узла образуем 2-3-дерево, содержащее все инцидентные ему ребра. Чтобы построить такое дерево, начнем с листьев. Затем добавим узлы высоты 1, так объединяя листья в группы по два или три, чтобы групп из двух листьев было не больше двух. Сделав это, вычислим для каждого узла высоты 1 наименьшую стоимость листа-потомка. Затем соберем узлы высоты 1 в группы по два или три и будем продолжать процесс до тех пор, пока на некотором уровне не образуется один узел, а именно корень. Время, затрачиваемое на такое построение дерева, пропорционально числу листьев. Реализация остальной части алгоритма теперь должна быть очевидна. Общее время работы составляет O(e log e), где е – число всех ребер.