
- •Лекция 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. Составление учебного расписания
- •Библиографический список
Лекция 4. Поиск в глубину в графе
Ягодка за ягодкой… (Русские народные сказки)
Существует много различных алгоритмов, в основе которых лежит систематический перебор вершин графа, при котором каждая вершина просматривается ровно один раз. Будем рассматривать такие методы обхода или поиска в графе, что:
а) алгоритм решения легко «погружается» в этот метод;
б) каждое ребро графа анализируется число раз, ограниченное константой.
Опишем классический метод поиска в неориентированном графе — метод ПОИСКА В ГЛУБИНУ (англ. depth first search).
Общая идея следующая.Начинаем поиск с некоторой фиксированной вершины k (корня). Затем выбираем одну из смежных (соединенных ребрами) с ней вершин. Назовем эту вершину u. Далее процесс поиска повторяется от u.
Пусть на каком-то этапе мы находимся в вершине v. Если среди ее соседей есть хотя бы одна НОВАЯ (еще непросмотренная) вершина w, то просматриваем ее. После этого w перестает быть новой и дальнейший поиск продолжаем с w.
Если же у вершины v НЕТ ни одной НОВОЙ «соседки», то говорим, что v ИСПОЛЬЗОВАНА, и возвращаемся на ШАГ НАЗАД — в вершину, из которой попали в v. Просмотренные, но еще не использованные вершины накапливаются в стеке. Использованные вершины удаляются из стека. Когда стек опустеет, то у связного графа будут просмотрены все вершины, у несвязного — компонента связности, содержащая вершину k.
Рис. 10
?Вопрос 1. Когда следует прервать работу алгоритма, если мы ищем путь от 1 до 5? Где находится путь?
Поиск в глубину можно записать с помощью рекурсивной процедуры GL.
Переменная НОВЫЙ — глобальная.
1 procedure GL(v) {поиск в глубину из вершины v}
2 begin рассмотреть v; НОВЫЙ[v]:=ложь;
3 for u ЗАПИСЬ[v] do {перебор всех соседей v}
4 if НОВЫЙ[u] then GL[u]
5 end {v использована}
Поиск в глубину в произвольном (необязательно связном) графе проводится согласно следующему алгоритму:
1 begin
2 for v V do НОВЫЙ[v]:=истина ; {инициализация}
3 for v V do {перебор всех вершин графа}
4 if НОВЫЙ[v] then GL(v)
5 end {поиск окончен, все вершины просмотрены}
Покажем теперь, что этот алгоритм просматривает каждую вершину в точности ОДИН РАЗ и СЛОЖНОСТЬ его порядка O(n+m).
Первый вызов GL(v) для некоторой вершины v влечет за собой просмотр всех вершин компоненты связности графа, содержащей v. Это обеспечивает цикл 3 процедуры GL: после просмотра v будет вызвана GL для всех ее новых соседей.
Каждая вершина просматривается ровно один раз — просматриваются только новые вершины, сразу после просмотра НОВЫЙ[v]:=ложь.
Будут просмотрены все вершины — цикл 3 основного алгоритма.
Оценим теперь вычислительную сложность алгоритма.
Циклы 2 и 3 основного алгоритма содержат n шагов, не считая вызовы процедуры GL. Строка 2 процедуры GL для ВСЕХ вызовов повлечет O(n) шагов. Полное число шагов цикла 3 процедуры GL для ВСЕХ рекурсивных вызовов будет порядка m — числа всех ребер, т.к. от вершины к ее соседям мы двигаемся по ребрам.
Суммарная сложность алгоритма О(n+m).
В связи с важностью поиска в глубину для проектирования алгоритмов на графах представим также НЕРЕКУРСИВНУЮ версию процедуры GL.
Рекурсия устраняется стандартным способом: каждая просмотренная вершина помещается в СТЕК и удаляется из стека после использования.
1 procedure GL1(v); {поиск в глубину,начиная с v}
2 begin
3 СТЕК:=NIL; СТЕК<=v;
4 рассмотреть v;
5 НОВЫЙ[v]:=ложь; {v помещается в СТЕК}
6 while СТЕК do
7 begin
8 verh:=top(СТЕК); {читаем верхний элемент СТЕКА}
{будем искать первую новую «соседку» элемента verh}
9 if НАЧАЛО [verh]=nil then b:=ложь
10 else b:=not НОВЫЙ [НАЧАЛО[verh]^.uzl];
{НАЧАЛО[verh]^.uzl-номер первой вершины в списке ЗАПИСЬ[verh]}
11 while b do
12 begin
13 НАЧАЛО[verh]:=НАЧАЛО[verh]^.next;
{переводим указатель НАЧАЛО на следующую запись в
списке ЗАПИСЬ[verh]}
14 if НАЧАЛО [verh]=nil then b:=ложь
15 else b:=not НОВЫЙ[НАЧАЛО[verh]^.uzl]
{проверяем, будет ли вершина следующей записи новой}
16 end;
17 if НАЧАЛО[verh]=nil then {найдена новая вершина!}
18 begin
19 verh:=НАЧАЛО[verh]^.uzl;
20 СТЕК<=verh; {новая вершина помещена в СТЕК}
21 рассмотреть verh; НОВЫЙ[verh]:=ложь
22 end
23 else {вершина verh использована}
24 verh<=СТЕК {удалить verh из СТЕКа}
25 end {while СТЕК <> NIL}
26 end; {procedure GL1}
Корректность процедуры GL1 доказывается аналогично GL.
Ответы.
Ответ 1. Когда 5 попадет в стек. В стеке.