- •Лекция 1. Введение
- •Лекция 2. Графы и деревья
- •Лекция 3. Машинное представление графов
- •Лекция 4. Поиск в глубину в графе
- •Лекция 5. Поиск в ширину в графе
- •Лекция 6. Стягивающие деревья (кАркасы)
- •Лекция 7. Фундаментальное множество циклов графа
- •Лекция 8. Нахождение компонент двуcвязности (блоков) графа
- •Лекция 9. Эйлеровы пути
- •Лекция 10. Классификация задач по степени сложности
- •Лекция 11. Алгоритмы с возвратом
- •Лекция 12. Модификации алгоритма с возвратом. Задачи на максимум и минимум
- •Лекция 13. Кратчайшие пути
- •Лекция 14. Алгоритм форда-беллмана
- •Лекция 15. Алгоритм дейкстры
- •Лекция 16. Пути в бесконтурном графе
- •Лекция 17. Алгоритм флойда
- •Лекция 18. Кратчайшие пути с фиксированными платежами
- •Лекция 19. Первые k кратчайших путей
- •Приложение 1. Список np-полных задач
- •9. Множество представителей
- •10. Упорядочение внутри интервалов
- •11. Составление учебного расписания
- •Библиографический список
Лекция 11. Алгоритмы с возвратом
Чтобы определить, что яйцо тухлое, необязательно есть его до конца (народная мудрость).
Вернемся к задаче существования ГАМИЛЬТОНОВА ПУТИ — пути, проходящего через каждую вершину графа ровно один раз. В предыдущей главе было упомянуто, что для такой задачи не построен полиномиальный алгоритм. Попробуем произвести полный перебор всех возможных путей. n вершин графа можно расположить в n! различных цепочек. Чтобы проверить для каждой цепочки, реализована ли она как гамильтонов путь на графе, необходимо еще n шагов, итого сложность полного перебора n*n! шагов.
Такая величина растет гораздо быстрее экспоненты, поэтому полный перебор является неприменимым методом. Однако число шагов в алгоритмах переборного типа можно значительно уменьшить.
ОБЩАЯ СХЕМА АЛГОРИТМА С ВОЗВРАТОМ.
Пусть решение, которое мы ищем, имеет вид последовательности <X(1), …, X(n)>.
Будем строить его, начиная с пустой последовательности длины 0.
Пусть на каком-то этапе уже построено ЧАСТИЧНОЕ (неполное) решение длины i: < X(1), …, X(i) >.
Попытаемся продолжить это решение еще на один шаг. Для этого нужно отыскать допустимое X(i+1). X(i+1) считается допустимым, если или < X(1), …, X(i+1) > уже является решением, или относительно < X(1), …, X(i+1) > НЕЛЬЗЯ СРАЗУ сказать, что его невозможно расширить до полного решения.
Дальше существует две возможности:
если X(i+1) допустимо, то следующее допустимое X ищется для частичного решения <X(1), …, X(i+1)> (заметим, что допустимость X(i+1) вовсе не означает, что <X(1), …, X(i+1)> можно расширить до полного решения);
если допустимого X(i+1) не существует, то возвращаемся на шаг назад — к частичному решению < X(1), …, X(i-1) > и для него отыскиваем другое X'[i], не совпадающее с предыдущим X(i).
Более точно, пусть для каждого i>0 существует множество A(i), из которого будут выбираться X(i) — претенденты на i–тую координату частичного решения.
Очевидно, множества A(i) должны содержать все X(i), занимающие i-ю координату любого решения. Кроме того, A(i) всегда будут содержать какие-то лишние злементы, не содержащие в i–й координате ни одного решения.
Теперь общая схема алгоритма с возвратом имеет следующий вид:
1 begin
2 k:=1;
3 while k>0 do
4 if существует еще неиспользованый y A(k), являющийся допустимым then
5 begin
6 X(k):=y ; {y испсльзован}
7 if <X(1), …, X(k)> является решением then write (<X(1), …, X(k)>);
8 k:=k+1
9 end
10 else {возврат на предыдущее частичное решение, все элементы A(k) становятся неиспользоваными}
11 k:=k-1
12 end.
Если предположить, что все решения имеют длину, меньшую n+1, и все множества A(k) состоят из конечного числа элементов, то этот алгоритм находит ВСЕ решения.
Тот же самый алгоритм можно очень просто записать с помощью рекурсии.
{рекурсивный вариант алгоритма с возвратом}
1 procedure BC(k);
{генерируем все решения, являющиеся продолжением частичного решения < X(1), …, X(k-1) >, массив Х — глобальный}
2 begin
3 for y A(k) do
4 if y допустим then
5 begin
6 X(k):=y;
7 if <X(1), …, X(k)> — решение then
8 write (<X(1), …, X(k)>);
9 BC(k+1); {генерируем все решения, являющиеся продолжением частичного решения < X(1),…,X(k-1), y >}
10 y становится неиспользованным; {возврат на <X(1), …, X(k-1)>}
11 end {цикл 3 выберет для продолжения < X(1), …, X(k-1) > следующий y', не равный y}
12 end;
Генерировать все решения можно вызовом ВС(1). Описание алгоритма возврата мы начали с несколько более сложного нерекурсивного варианта, т.к. в рекурсивном варианте «возврат» является частью механизма рекурсии и не появляется в явном виде.
ЗАДАЧА ГАМИЛЬТОНА
Применим теперь алгоритм с возвратом для поиска всех гамильтоновых циклов в графе G=<V,E>. Каждый такой цикл — последовательность различных вершин графа < X(1), …, X(n+1) > и только X(1)=X(n+1)=k, где k — произвольная фиксированная вершина, соседние вершины X(i), X(i+1) соединены ребром.
Тогда A(i) = V (множество всех вершин) для любого i <= n+1 ;
y — допустима для продолжения <X(1), …, X(i-1)>, если
y ЗАПИСЬ[ X(i-1) ] (y соединен ребром с X(i-1)) и
y не использована (y не принадлежит <X(1), …, X(i-1)>)
АЛГОРИТМ. {Нахождение всех гамильтоновых циклов в графе}
Данные: Граф G=<V,E>, представленный списками ЗАПИСЬ[v], v V.
Результаты: Печать всех гамильтоновых циклов графа G.
Переменные ЗАПИСЬ, X, DOP — глобальные.
1 procedure GAMI(i);
{генерирование всех гамильтовых циклов, являющихся продолжением частичного решения <X[1], …, X[i-1]>}
2 begin
3 for y Є ЗАПИСЬ[X[i-1]] do
4 if (i = n+1) AND (y = k) then
5 write(X[1], …, X[n], k)
6 else if DOP[y] then
7 begin
7 X[i]:=y; DOP[y]:=ложь;
8 GAMI(i+1);
9 DOP[y]:=истина;
{все решения, продолжающие < X[1], …, X[i-1], y > уже сгенерированы, выбираем другое y', а y вновь становится неиспользованным}
10 end
11 end;{GAMI}
1 begin {основная программа}
2 for v Є V do DOP[V]:=истина;
3 X[1]:=k ; {k — произвольная}
4 DOP[k]:=ложь;
5 GAMI(2);
6 end.
Рис. 26
Сложность этого алгоритма в наихудшем случае растет по экспоненте с ростом n.
Это справедливо и для случая, когда ищется ровно одно решение (если задача не имеет решения, то эта модификация не влияет на ход выполнения алгоритма).
?Вопрос1. Докажите, представив соответствующий «плохой» граф, что число шагов в алгоритме «GAMI» растет экспоненциально с ростом n.
Ответы
Ответ 1. Граф типа «погремушка». Такой граф не содержит ни одного гамильтонова цикла, в то же время все вершины, кроме V0, соединены и представляют собой клику, т.е. граф, у которого все вершины соединены попарно. Всего различных путей будет n!, а для проверки каждой uj из них потребуется n шагов, итого nn!, что растет быстрее экспоненты.
Рис. 27
