
- •Лекция 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. Составление учебного расписания
- •Библиографический список
Лекция 5. Поиск в ширину в графе
Бросая в воду камни, следи за кругами, ими образуемыми; иначе такое занятие будет пустою забавою. Козьма Прутков «Мысли и афоризмы»
При ПОИСКЕ В ГЛУБИНУ просмотренные, но еще не использованные вершины скапливались в стеке. Чем позднее была просмотрена вершина, тем раньше ее удаляли из стека. Заменим стек ОЧЕРЕДЬЮ.
Рис. 11
Механизм подключения элемента к началу очереди полностью совпадает с вталкиванием в стек (процедура push). Механизм ВЫТАЛКИВАНИИЯ ИЗ ОЧЕРЕДИ (функции poptail) — первым из очереди удаляется первый попавший туда элемент.
Основная идея ПОИСКА В ШИРИНУ — у вершины v просматриваются и помещаются в очередь СРАЗУ ВСЕ ее новые «соседи», после чего v становится использованной и удаляется из очереди.
Рис. 12
Поиск в ширину можно использовать для нахождения КРАТЧАЙШЕГО пути между парой фиксированных вершин s и t. Так как мы находим сразу всех соседей, поиск, как волна от источника, равномерно распространяется во все стороны. Поэтому в очереди сначала находятся все вершины, удаленные от s на расстояние 1, через некоторое время — все вершины, удаленные на расстояние 2 и т.д. Понятно, что путь, найденный таким образом, будет кратчайшим.
Рис. 13
Процедура поиска в ширину SH:
1 procedure SH(v);
{поиск в ширину с началом в вершине v}
2 begin
3 ОЧЕРЕДЬ:=NIL; ОЧЕРЕДЬ<= v; НОВЫЙ[v]:=ложь;
{v помещается в очередь}
4 while ОЧЕРЕДЬ<>NIL do
5 begin
6 tail <=ОЧЕРЕДЬ;
{выталкивание из очереди нижнего элемента}
7 for u ЗАПИСЬ[tail] do
{ищем ВСЕХ новых соседей узла tail}
8 if НОВЫЙ[u] then
9 begin
10 ОЧЕРЕДЬ <= u; НОВЫЙ[u]:=ложь
11 end
12 end {while ОЧЕРЕДЬ <>NIL}
13 end;
Как и для поиска в глубину, легко показать, что каждая вершина просматривается ровно один раз и вычислительная сложность равна O(n+m).
Чтобы найти кратчайший путь, нужно немного модифицировать процедуру SH. Во-первых, добавить одномерный массив ПРЕД по числу вершин графа. Пусть во время работы процедуры в вершину u2 мы попали из u1: u1 была предыдущей для u2, поэтому ПРЕД[u2]:=u1. Во-вторых, чтобы массив ПРЕД заполнялся именно так, изменим строку 10 процедуры SH:
9 begin
10 ОЧЕРЕДЬ<=u; НОВЫЙ[u]:=ложь;
ПРЕД[u]:=tail;
Итак, чтобы найти кратчайший путь из s в t, вызовем модифицированную SH(s).
Как только конец пути t попадет в очередь, поиск следует прекратить. Каким образом извлечь путь от s до t из массива ПРЕД?
Рассмотрим последовательность вершин, где u1=ПРЕД[u2]. В t мы попали из u, в u из v и т.д., пока не встретим s. Эта последовательность — путь из s в t. Первоначальная инициализация массива ПРЕД: ПРЕД[u]:=u.
?Вопрос 1. Как будет заполнен массив ПРЕД при поиске в ширину на рис. 12 в момент помещения в очередь вершины 5?
Метод поиска в ширину применим для определения связных компонент графа. Вызвав SH(s), просмотрим все вершины u, для которых существует путь из s в u.
Ответы.
Ответ 1. ПРЕД[1]=1; ПРЕД[2]=ПРЕД[3]=1; ПРЕД[4]=3; ПРЕД[5]=2.
Лекция 6. Стягивающие деревья (кАркасы)
У моря есть морская рыбка, Скрипичный ключ имеет скрипка… У. Дж. Смит «У моря есть морская рыбка…»
Каждый связный граф имеет каркас. Более того, у одного графа может быть несколько разных каркасов. Дадим теперь определение каркаса.
Дерево — произвольный неориентированный связный граф без циклов.
Для произвольного связного графа G=<V, E> любое дерево D=<V, T>, где T Í Е будет его КАРКАСОМ.
Другими словами, каркас D соединяет ВСЕ вершины графа G так, чтобы не было циклов.
Рис. 14
Ребра КАРКАСА будут называться ВЕТВЯМИ, ребра, не вошедшие в каркас, ХОРДАМИ.
?Вопрос 1. Перечислите хорды графа G относительно каркаса D на рис. 14.
Каркасы можно строить как поиском в глубину, так и поиском в ширину. В обоих случаях достижение новой вершины u из старой v означает включение в каркас ветви (v-u).
АЛГОРИТМ {Нахождение каркаса связного графа методом поиска в глубину}
Данные: Связный граф G <V, E>, заданный списками инцидентности ЗАПИСЬ[v], v V.
Результаты: Каркас D = <V, T> графа.
Переменные НОВЫЙ, ЗАПИСЬ, Т — глобальные
1 procedure GLD(v);
2 begin
3 НОВЫЙ[v]=ложь;
4 for u ЗАПИСЬ[v] do
5 if НОВЫЙ[u] then {нашли новую ветвь (v — u)}
6 begin
7 T:= T (v-u); {и присоединили ее к множеству 8 ветвей каркаса}
9 GLD(u)
10 end
11 end; {конец процедуры GLD(v)}
1 begin {основная программа}
2 for u V do НОВЫЙ[u]:=истина; {инициализация}
3 T:=0; {T-множество найденных ветвей}
4 GLD(k) {корень k — произвольная вершина}
5 end.
Докажем корректность этого алгоритма.
Алгоритм строит СВЯЗНЫЙ граф, т.к. каждое новое ребро (v-u) продолжает уже существующий путь от k к v.
Построенный граф не содержит ЦИКЛОВ. Каждая новая ветвь (v-u) соединяет уже рассмотренную v с НОВОЙ u. Чтобы замкнуть цикл, требуется ребро, соединяющее две уже РАССМОТРЕННЫЕ вершины.
Построенный граф содержит ВСЕ вершины графа G — это свойство поиска в глубину. По этой же причине вычислительная сложность алгоритма О(n+m).
Любой каркас обладает важным свойством — от корня k до произвольной вершины v существует ровно ОДИН путь, состоящий из ветвей каркаса. Если бы их было два, получился бы цикл, если бы ни одного, каркас не был бы связным.
Кроме того, если каркас D построен поиском в глубину, то для двух вершин u и v, СОЕДИНЕННЫХ РЕБРОМ e E, всегда можно сказать: или v — потомок u, или u — потомок v (относительно каркаса D).
Первое означает, что u лежит на пути из корня k в вершину v, второе — v на пути из k в u. Это легко доказать.
Пусть одна из вершин, например,v просмотрена раньше u. Построен путь от корня k до v. Процесс поиска в глубину начинается с вершины v. Так как u и v соединены ребром, то, рано или поздно, будет рассмотрена вершина u и построен путь от v до u. Получился путь k—v—u.
Если v и u соединены ВЕТВЬЮ каркаса, то одна из них — СЫН, другая — ОТЕЦ.
АЛГОРИТМ {Каркас связного графа ПОИСКОМ В ШИРИНУ}
Данные: связный граф G=<V, E>, представленный списками ЗАПИСЬ[v], v V.
Результаты: каркас D = <V, T> графа G.
1 begin
2 for u V do НОВЫЙ[u]:=истина; {инициализация}
3 T:=0 {T — множество найденных ветвей}
4 ОЧЕРЕДЬ=NIL; ОЧЕРЕДЬ <= k;
5 НОВЫЙ[k]:=ложь; {k — корень}
6 while ОЧЕРЕДЬ <> NIL do
7 begin v<= ОЧЕРЕДЬ; {вытолкнули из очереди нижний элемент}
8 for u ЗАПИСЬ[v] do
9 if НОВЫЙ[u] then {нашли новую ветвь v—u}
10 begin
11 ОЧЕРЕДЬ <=u; НОВЫЙ[u]:=ложь;
12
T:=T
(v-u) {и
присоединили ее к каркасу}
13 end
14 end
15 end.
Легко доказать, что этот алгоритм строит каркас произвольного графа за О(n+m) шагов.
Рис. 15
Если каркас D графа G построен ПОИСКОМ В ШИРИНУ, то путь от корня k до произвольной вершины v будет не только единственным, но и КРАТЧАЙШИМ. Это следует из свойств поиска в ширину.
Ответы.
Ответ 1. {(2—3), (3—4), (5—6), (6—1), (2—5), (1—4)}.