
- •Алгоритмы на графах
- •3.1. Представление графа в памяти компьютера
- •3.2. Поиск в графе
- •3.2.1. Поиск в глубину
- •3.2.2. Поиск в ширину
- •3.3. Деревья
- •3.3.1. Основные понятия. Стягивающие деревья
- •3.3.2. Порождение всех каркасов графа
- •3.3.3. Каркас минимального веса. Метод Краскала
- •3.3.4. Каркас минимального веса. Метод Прима
- •3.4. Связность
- •3.4.1. Достижимость
- •3.4.2. Определение связности
- •3.4.3. Двусвязность
- •3.5. Циклы
- •3.5.1. Эйлеровы циклы
- •3.5.2. Гамильтоновы циклы
- •3.5.3. Фундаментальное множество циклов
- •3.6. Кратчайшие пути
- •3.6.1. Постановка задачи. Вывод пути
- •3.6.2. Алгоритм Дейкстры
- •3.6.3. Пути в бесконтурном графе
- •3.6.4. Кратчайшие пути между всеми парами вершин. Алгоритм Флойда
- •3.7. Независимые и доминирующие множества
- •3.7.1. Независимые множества
- •3.7.2. Метод генерации всех максимальных независимых множеств графа
- •3.7.3. Доминирующие множества
- •3.7.4. Задача о наименьшем покрытии
- •3.7.5. Метод решения задачи о наименьшем разбиении
- •3.8 Раскраски
- •3.8.1 Правильные раскраски
- •3.8.2. Поиск минимальной раскраски вершин графа
- •3.8.3. Использование задачи о наименьшем покрытии при раскраске вершин графа
- •3.9. Потоки в сетях, паросочетания
- •3.9.1. Постановка задачи
- •3.9.2. Метод построения максимального потока в сети
- •3.9.3. Наибольшее паросочетание в двудольном графе
- •3.10. Методы приближенного решения задачи коммивояжера
- •3.10.1. Метод локальной оптимизации
- •3.10.2. Алгоритм Эйлера
- •2.10.3. Алгоритм Кристофидеса
- •3.11. Задачи
3.10.2. Алгоритм Эйлера
Этот алгоритм и следующий работоспособны в том случае, если выполняется неравенство треугольника. Его суть в том, что для любой тройки городов i, j, k (между которыми есть связь) выполняется неравенство di,j+dj,k>di,k. Рассмотрим идею алгоритма.
Шаг1. Строится каркас минимального веса (алгоритмы Прима или Краскала).
Примечание. Это не есть первое приближение, как в предыдущем алгоритме.
Шаг 2. Путем дублирования каждого ребра каркас преобразуется в эйлеров граф.
Шаг 3. Находим в построенном графе эйлеров цикл.
Шаг 4. Эйлеров цикл преобразуем в гамильтонов цикл (или маршрут коммивояжера). Метод преобразования: последовательность вершин эйлерова цикла сокращается так, чтобы каждая вершина графа в получившемся цикле встречалась ровно один раз.
Шаг 5. Закончить работу алгоритма. Получено приближенное решение задачи коммивояжера.
Покажем , что стоимость приближенного решение CostAp не превосходит удвоенной стоимости оптимального решения CostBet. Пусть стоимость каркаса - CostFr. Тогда CostFr<CostBet, так как при удалении из оптимального пути коммивояжера ребра получаем каркас с весом не большим, чем CostAp. Из правила построения эйлерова графа получаем, что вес построенного эйлерова цикла 2*СostFr. Неравенство треугольника обеспечивает результат: следующую оценку шага 4 - CostAp<2*CostFr, а значит, CostAp<2*CostBet.
Рассмотрим пример.
Структуры данных. Помимо названных ранее массивов А - матрица расстояний и массива Way - хранение искомого пути, требуется «что-то» для хранение описания каркаса. Пусть это будет массив B (B:array[1..Nmax,1..Nmax] of boolean). Элемент B[i,j], равный true, говорит о том, что ребро (i,j) графа принадлежит каркасу.
Общая логика.
Begin
init;{ввод описания - матрица расстояний; инициализация данных}
solve;{решатель}
out;{вывод результата}
end.
Процедуры init и out очевидны (должны писаться «на автомате»). Уточняем процедуру solve. Первый «набросок».
Procedure solve;
var ?
<процедуры и функции>;
begin
init_solve;{инициализация переменных процедуры solve}
find_tree;{построение каркаса}
eiler_way;{поиск эйлерова цикла}
komm_way;{поиск пути коммивояжера}
end;
Прежде чем продолжать уточнение, необходимо определить структуры данных этой части логики и взаимодействие ее составных частей. Во-первых, при построении каркаса необходимо иметь список ребер, отсортированный в порядке возрастания их весов (алгоритмы Краскала и Прима). Следовательно, на входе процедуры find_tree матрица расстояний A, на выходе - B, рабочие структуры - массив для хранения списка ребер. Внутренние процедуры: формирование списка ребер и сортировки. Продолжим рассмотрение. Эйлеров цикл необходимо где-то хранить, пусть это будет массив St (St:array[1..n*(n-1) div 2] of byte. Количество не нулевых элементов в St - значение переменной Count. Эти величины описываются в разделе переменных процедуры solve, их инициализация - в процедуре init_solve.Процедуру поиска эйлерова цикла сделаем рекурсивной, поэтому первый вызов изменится - eiler_way(1). Выбор начальной вершины при поиске эйлерова цикла не имеет значения.
Итак, классическая логика поиска эйлерова цикла. Приводится с целью показа работы процедуры komm_way, ибо последняя не есть поиск гамильтонова цикла в обычной трактовке.
procedure eiler_way(v:byte);
var j:integer;
begin
for j:=1 to N do
if b[v,j] then begin b[v,j]:=false;b[j,v]:=false;eiler_way(j);end;
inc(count);St[count]:=v;{заносим номер вершины в эйлеров цикл}
end;
procedure komm_way;
var s:set of 1..Nmax;
i,j:integer;
begin
i:=0;s:=[];
for j:=1 to Count do{исключаем повторяющиеся номера вершин из эйлерова цикла}
if Not(St[j] in s) then begin
inc(i);way[i]:=St[j];s:=s+[St[j]];
end;
end;