
- •3.7. Древовидные структуры для задачи объединить–найти
- •Алгоритм 3.3. Алгоритм быстрого объединения непересекающихся множеств
- •VОтец [I]
- •3.8. Приложения и обобщения алгоритма объединить–найти
- •Приложение 2. Задача определения глубины
- •3.9. Схемы сбалансированных деревьев
- •3.10. Словари и очереди с приоритетами
- •Алгоритм 3.4. Вставка нового элемента в 2-3-дерево
- •3.11. Сливаемые деревья
- •3.12. Сцепляемые очереди
- •3.13. Разбиение
3.8. Приложения и обобщения алгоритма объединить–найти
Мы уже видели, как в задаче построения остовного дерева, описанной в примере 3.1, естественно возникает последовательность основных операций ОБЪЕДИНИТЬ и НАЙТИ. В этом разделе мы познакомимся с несколькими другими задачами, которые приводят к последовательностям операций ОБЪЕДИНИТЬ и НАЙТИ. В нашей первой задаче надо осуществить вычисления в свободном режиме, т. е. прочесть всю последовательность операций перед выдачей каких бы то ни было ответов.
Приложение 1. MIN-задача в свободном режиме
Даны два типа операций: ВСТАВИТЬ(i) и ИЗВЛЕЧЬ_MIN. Начнем с множества S, вначале пустого. Каждый раз, когда встречается операция ВСТАВИТЬ(i), целое число i помещается в S. Каждый раз, когда выполняется операция ИЗВЛЕЧЬ_МIN, разыскивается и удаляется наименьший элемент в S.
Пусть
– такая
последовательность операций ВСТАВИТЬ
и ИЗВЛЕЧЬ_MIN,
что для каждого
операция ВСТАВИТЬ(i)
встречается не более одного раза. По
данной последовательности
надо найти последовательность целых
чисел, удаляемых операцией ИЗВЛЕЧЬ_MIN.
Задача решается в свободном режиме,
поскольку предполагается, что вся
последовательность
известна до
того, как надо вычислить даже первый
элемент выходной последовательности.
MIN-задачу
в свободном режиме можно решить следующим
методом. Пусть k–число
операций ИЗВЛЕЧЬ_МIN
в
.
Можно записать
в виде
,
где каждая подпоследовательность
for I 1 until n do
begin
j НАЙТИ(i);
if
jk
then
begin
print i "удаляется j-й операцией ИЗВЛЕЧЬ_MIN";
ОБЪЕДИНИТЬ(j, СЛЕД[j], СЛЕД[j]);
СЛЕД[ПРЕД[j]] <– СЛЕД[j];
ПРЕД[СЛЕД[j]] <– ПРЕД[j]
end
end
Рис. 3.23. Программа для решения MIN-задачи в свободном режиме.
,
состоит только из операций ВСТАВИТЬ, а
Е
обозначает операцию ИЗВЛЕЧЬ_МIN.
Промоделируем
с помощью алгоритма 3.3. Начальная
последовательность множеств для работы
алгоритма объединения строится так:
если в последовательности
;
встречается операция ВСТАВИТЬ(i),
то считаем, что множество с именем
,
содержит элемент i.
Для того чтобы для тех значений j,
для которых существует множество с
именем j,
образовать дважды связанный упорядоченный
список, пользуемся двумя массивами ПРЕД
и СЛЕД. Вначале ПРЕД[j]
= j
- 1 для 1
j
k
+ 1 и СЛЕД[j]
= j
+ 1 для 0
j
k.
Затем выполняется программа, приведенная
на рис. 3.23.
Легко видеть, что время выполнения этой программы ограничено временем работы алгоритма объединения множеств. Следовательно, MIN-задача в свободном режиме имеет временную сложность O(nG(n)).
Пример 3.8. Рассмотрим последовательность операций = 4 3 E 2 Е 1 E, где j означает ВСТАВИТЬ(j), а Е– ИЗВЛЕЧЬ_MIN. Тогда (1 = 4 3, 2 = 2, 3 = 1 и 4 – пустая последовательность. Начальная структура данных представляет собой последовательность множеств
1={3, 4}, 2={2}, 3={1}, 4 = .
При первом выполнении for-цикла выясняется, что НАЙТИ(1) = 3. Следовательно, ответом на третью операцию в ИЗВЛЕЧЬ_MIN будет 1. Последовательность множеств превращается в
1 = {3, 4}, 2 = {2}, 4={1}.
В этот момент СЛЕД[2] = 4 и ПРЕД[4] = 2, поскольку множества с именами 3 и 4 были слиты в одно множество с именем 4.
При следующем прохождении с i = 2 выясняется, что НАЙТИ(2) = 2. Таким образом, ответом на вторую операцию ИЗВЛЕЧЬ_МIN будет 2. Множество с именем 2 сливается со следующим множеством (с именем 4) и получается последовательность множеств
1 = {3, 4}, 4 = {1, 2}.
Два последних прохождения устанавливают, что ответ на первую операцию ИЗВЛЕЧЬ_МIN есть 3 и что 4 никогда не извлекается.
Рассмотрим другое приложение – задачу определения глубины. В частности, она возникает при "приравнивании" идентификаторов в программе на языке ассемблера. Во многих языках ассемблера есть операторы, описывающие, что два идентификатора представляют одну и ту же ячейку памяти. Как только ассемблер встречает оператор, приравнивающий два идентификатора и , он должен найти два множества S и S идентификаторов, эквивалентных соответственно и , и заменить эти два множества их объединением. Очевидно, задачу можно смоделировать последовательностью операций ОБЪЕДИНИТЬ И НАЙТИ.
Однако если внимательнее проанализировать эту задачу, можно по-другому применить структуры данных из предыдущего раздела. Каждому идентификатору соответствует графа таблицы символов, и, если несколько идентификаторов эквивалентны, удобно хранить данные о них только в одной графе таблицы символов. Это означает, что для каждого множества эквивалентных идентификаторов есть начало отсчета, т. е. место в таблице символов, где хранится информация об этом множестве, и каждый элемент этого множества имеет смещение от начала. Чтобы найти положение идентификатора в таблице символов, надо прибавить его смещение к началу отсчета множества, которому принадлежит данный идентификатор. Но когда два множества идентификаторов становятся эквивалентными, надо изменить смещение. Эта задача корректировки смещений в абстрактном виде решается в приложении 2.