- •Глава I Основные понятия
- •§1. Введение
- •§2. Алгоритмы и их сложности
- •§3. Запись алгоритмов
- •Глава II Графы и сети
- •§1. Графы, сети
- •§2. Машинное представление графов и сетей.
- •Глава III Сортировка данных
- •§1. Сложность задачи сортировки
- •§2. Алгоритм сортдерево
- •Глава IV Поиск в графе
- •§1. Поиск в глубину.
- •§2. Поиск в ширину.
- •§3. Цепи и пути в графах.
- •§4. Пути в лабиринте.
- •Глава V Задача о минимальном остове
- •Глава VI Пути в сетях
- •§1 Общий случай. Алгоритм Форда-Беллмана.
- •§2 Случай неотрицательных весов. Алгоритм Дейкстры.
- •§3. Случай бесконтурной сети.
- •§4. Задача о максимальном пути и сетевые графики.
- •§3. Задача о maxmin пути.
- •§6. Задача о кратчайших путях между всеми парами узлов.
Глава IV Поиск в графе
Многие алгоритмы на графах основаны на систематическом переборе всех вершин графа, при котором каждая вершина просматривается в точности один раз, при этом переход от уже рассмотренной вершины к еще не рассмотренной вершине осуществляется по ребрам графа. В этой главе мы рассмотрим два стандартных, широко используемых на практике метода такого перебора: поиск в глубину и поиск в ширину. Опишем оба этих метода поиска только для неориентированных графов. Модификация этих методов для случая орграфов тривиальна.
§1. Поиск в глубину.
Идея этого метода поиска состоит в следующем. Поиск начинается с некоторой фиксированной вершины v, называемой корневой вершиной поиска или корнем. При этом считаем v просмотренной вершиной. Затем выбираем какое-нибудь ребро {v,w}, инцидентное v, проходим его и попадаем в вершину w.
Тот факт, что в процессе поиска использовано именно ребро {v,w} отмечается обычно следующим образом. Ребро {v,w} ориентируется из v в w. полученную дугу (v,w) считают дугой корневого дерева с корнем v. Такое дерево называется ПВГ-деревом с корнем v, или короче ПВГ(v)—деревом. При переходе от v к w, вершина v объявляется отцом вершины w и обозначается OTEЦ[w], вершина w объявляется просмотренной, и поиск продолжается из вершины w.
В общем случае, когда мы находимся в некоторой вершине v, возникают две возможности:
Все вершины, смежные с v, уже просмотрены. В этом случае возвращаемся к вершине ОТЕЦ[v] и продолжаем поиск из нее, а вершину v с этого момента считаем использованной.
Существует еще не просмотренная вершина w, смежная с вершиной v. Тогда ребро {v,w} превращаем в дугу (v,w), добавляя ее к ПВГ(v)-дереву; полагаем ОТЕЦ[w]=v; проходим по дуге из v в w и продолжаем поиск из w.
Поиск в глубину завершается тогда, когда все вершины графа будут просмотрены. Здесь возможны две ситуации:
Корневая вершина использована (т.е. все смежные с ней вершины просмотрены), однако в графе еще есть непросмотренные вершины. В этом случае выбираем произвольную непросмотренную вершину, объявляем ее корнем и вновь начинаем поиск уже из этой вершины. Такая ситуация возникает только в случае несвязного графа.
Корневая вершина и все другие использованы. Поиск на этом заканчивается.
Представим формальное описание изложенного алгоритма. Массив МЕТКА, используемый в алгоритме имеет по одному элементу для каждой вершины графа и служит указателем на то, что просмотрена или нет данная вершина. Считаем, что METKA[v]=0, если v не просмотрена, и METKA[v]=l в противном случае. Массив ОТЕЦ описан ранее. Понятно, что он имеет длину n, где n — число вершин в графе. В множестве Т будем хранить дуги ПВГ-деревьев.
Вначале изложим версию алгоритма поиска в глубину, основанную на рекурсивной процедуре ПВГ(v) — поиск в глубину из вершины v. В рассматриваемом алгоритме связность графа не предполагается.
АЛГОРИТМ 4.1. ПОИСК В ГЛУБИНУ
Данные: неориентированный граф G=(V,E), заданный списками смежностей ЗАПИСЬ[v].
Результаты: массив ОТЕЦ, множество Т.
procedure ПВГ (v); (*поиск в глубину с корнем v)
begin METKA[v]:=1;
for w ЗАПИСЬ[v] do
if METKA[w]=0 then
begin OTEЦ[w]:=v; T:=T{(v,w)};ПВГ(w);
end;
end;
begin (*Главная программа)
T := Ø;
for v V do METKA[v]:=0; (*инициализация)
for v V do
if METKA[v]=0 then
begin OTEЦ[v]:=nil; ПВГ(v);
end;
end.
На рис.4.1 показано, как поиск в глубину исследует неориентированный графG. Предполагается, что в списках смежности этого графа вершины встречаются в порядке возрастания номеров, и, что цикл в строках 11-14 алгоритма 4.1 выполнялся в порядке возрастания номеров вершин.
В просмотренном графе G дуги ПВГ-деревьев обозначены стрелками в соответствии с ориентацией, полученной в ходе поиска. Такие дуги часто называют прямыми дугами поиска. Прочие ребра графа, называемые иногда обратными дугами поиска, обозначены на рисунке пунктирами. Числа в скобках, стоящие у вершин просмотренного графа, соответствуют очередности, в которой они просматривались в ходе поиска. Отметим, что вершины с номерами 1, 9 и 11 являются корнями ПВГ-деревьев.
Разберем теперь нерекурсивную версию процедуры ПВГ(v). Рекурсия устраняется стандартным способом с помощью стека. Каждая вершина помещается в стек сразу, как только будет достигнута, то есть просмотрена, и удаляется из стека после того, как будет использована. Поиск новой непросмотренной вершины ведется из той вершины, которая последней попала в стек.
Для организации такого процесса понадобится дополнительно массив указателей УКАЗ длины n, где n=|V|. Предполагается, что в начале работы процедуры поиска выполняются равенства УKA3[v]=HAЧAЛO[v], для всех v из V, иначе говоря, УКАЗ[v] дает адрес первой записи в списке ЗАПИСЬ[v]. В процессе работы процедуры поиска УКАЗ[v] меняется таким образом, что указывает на следующую запись в списке ЗАПИСЬ[v] после той, которая содержит последнюю просмотренную из нее вершину. Смысл переменных МЕТКА, ОТЕЦ и Т тот же, что и ранее.
procedure
ПВГ1(v);
(* нерекурсивная версия *)
begin
СТЕК :=Ø;
СТЕК
v;
METKA[v]:=l;
while СТЕК
≠ Ø
do
begin u
СТЕК;
while
(УКАЗ[u]
≠ nil)
and
(МЕТКА[УКАЗ[u]^.строка]=1)
do
УКАЗ[u]
:= УКАЗ[u]^.след;
if
УКАЗ[u]
≠ nil
then
(*найдена
непросмотренная вершина *)
begin
w
:= УКАЗ[u]^.строка:
СТЕК
w;
OTEЦ[w]:=u;
METKA[w]:=l;
T
:= T(u,w);
УКАЗ[u]:=
УКАЗ[u]^.след;
end;
else
СТЕК (*
вершина u
использована *)
end;
end.
Теорема 4.1. Сложность алгоритма 4.1 ПОИСК В ГЛУБИНУ есть O(n+m).
Понятно, что цикл в строке 10 (инициализация) требует n операций. Далее, проверка условия, стоящего в строке 13, осуществляется ровно n раз. Вызов процедуры ПВГ(v) влечет за собой просмотр всех вершин связной компоненты графа, содержащей v При этом каждая вершина из связной компоненты просматривается ровно один раз, поскольку просматриваются (т.е, выполняется строка 5) только те вершины w, для которых справедливо равенство METKA[w]=0, а сразу после просмотра, в результате вызова ПВГ(w), имеем равенство METKA[w]=l.
В тот момент, когда мы находимся в какой-либо вершине v, для поиска новой, еще не просмотренной вершины w, смежной с v, требуется анализировать ребра, инцидентные v. В процедуре ПВГ1(v) подробно описано, что это можно сделать, используя массив УКАЗ так, что каждое ребро графа анализируется ровно два раза: от v к w и от w к v. Следовательно, при работе алгоритма 4.1 анализируются все ребра и все вершины графа, каждое не более двух раз. При каждом анализе количество операций ограничено константой. Отсюда сложность алгоритма 4.1 есть O(n+m).
Как уже отмечалось выше алгоритм 4.1 различает связные и несвязные графы. Легко модифицировать этот алгоритм (а именно, каждый раз при выполнении условия в строке 13 заводить новое множество Т) так, чтобы он вычислял связные компоненты графа. Из этого замечания и теоремы 4.1 вытекает
Следствие. Связные компоненты неориентированного графа могут быть найдены за время O(n+m).
Поиск в глубину в ориентированном графе отличается от поиска в неориентированном графе только тем, что ребра проходятся в соответствии с их ориентацией. Поскольку в главе 2 мы условились считать, что в списке ЗАПИСЬ[у] встречаются только те вершины w, для которых (v,w)E, то алгоритм 4.1 корректно работает и в ориентированных графах.