- •А.Н. Горитов
- •Учебное пособие
- •Учебное пособие
- •Введение
- •1 Введение в предмет
- •1.1 Непрерывная и дискретная информация
- •1.2 Данные и эвм
- •1.3 Объекты предметной области
- •1.4 Представление информации об объектах
- •1.5 Абстрактные алфавиты. Кодирование
- •2 Основные типы и структуры данных эвм
- •2.1 Архитектурные особенности эвм, наиболее существенные для представления данных
- •2.2 Основные понятия о типах и структурах данных
- •2.3 Массивы
- •2.4 Строки
- •2.5 Записи
- •2.6 Записи с вариантами
- •2.7 Множества
- •3 Последовательный файл
- •3.1 Основные свойства последовательных файлов
- •3.2 Сортировка последовательных файлов
- •4 Полустатические структуры
- •4.1 Стек, очередь и дек как полустатические структуры
- •4.2 Представление полустатических структур с помощью массивов
- •5 Линейные динамические структуры
- •5.1 Основные свойства динамических структур
- •5.2 Реализация связного списка массивом
- •5.3 Кольцевой связный список
- •5.4 Линейный двусвязный список
- •6 Представление динамических структур с помощью указателей
- •6.1 Указатели
- •6.2 Представление стека
- •6.3 Представление очереди
- •6.4 Ведение динамических списков с помощью указателей
- •6.5 Алгоритм составления кольцевого двусвязного списка
- •7 Древовидные структуры данных
- •7.1 Основные понятия и определения
- •7.2 Представление деревьев в эвм
- •7.3 Основные операции с бинарными деревьями
- •7.4 Сильно ветвящиеся деревья
- •8 Алгоритмы на графах
- •8.1 Машинное представление графов
- •8.2 Поиск в глубину в графе
- •8.3 Поиск в ширину в графе
- •8.4 Стягивающие деревья (каркасы)
- •8.5 Отыскание фундаментального множества циклов в графе
- •8.6 Эйлеровы пути в графе
- •8.7 Алгоритмы с возвратом
- •8.8 Нахождение кратчайших путей в графе
- •8.9 Кратчайшие пути от фиксированной вершины
- •8.10 Алгоритм Дейкстры
- •8.11 Пути в бесконтурном графе
- •Литература
8.2 Поиск в глубину в графе
Основные требования к методам поиска в графе:
метод должен легко реализовываться алгоритмом решения интересующей нас задачи;
каждое ребро графа анализируется один раз (или ограниченное число раз, что существенно не меняет ситуации).
Существует много алгоритмов на графах, в основе которых лежит систематический перебор вершин графа, причем каждая вершина просматривается точно один раз.
Рассмотрим один метод поиска в неориентированном графе, который является основой многих графовых алгоритмов. Этот метод называется поиском в глубину (depth first search).
Общая идея метода состоит в следующем. Начинаем поиск с некоторой фиксированной вершины v0. Затем выбираем произвольную вершину u, смежную с v0, и повторяем наш просмотр от u . В общем случае предположим, что мы находимся в некоторой вершине v. Если существует новая (ещё не просмотренная) вершина u, u-v, то мы рассматриваем эту вершину (она перестает быть новой), начиная с неё, продолжаем поиск. Если же не существует новой вершины, смежной с v, мы говорим, что вершина v использована, возвращаемся в вершину, из которой мы попали в v, и продолжаем поиск (если v=v0, то поиск закончен).
Иначе говоря, поиск в глубину из вершины v основывается на поиске в глубину из всех новых вершин, смежных с v.
Это можно легко записать с помощью рекурсивной процедуры WG(v):
PROCEDURE WG(v); {поиск в глубину из вершины v}
{перемен. НОВЫЙ, СПИСОК – глобальные}
BEGIN рассмотреть v; НОВЫЙ[v] := false; FOR u∈СПИСОК[v] DO
IF НОВЫЙ[u] THEN WG(u)
END {вершина использована}
Весь процесс поиска в глубину в произвольном, необязательно связном графе производится по следующему алгоритму:
BEGIN
FOR v∈V DO НОВЫЙ[v] := true; {инициализация} FOR v∈V DO
IF НОВЫЙ[v] THEN WG(v)
END.
Можно убедиться в том, что этот алгоритм просмотрит каждую вершину ровно один раз и его сложность порядка О(n+m). Дополнительно каждый вызов WG(v) влечет за собой просмотр всех вершин компоненты связности графа, содержащей v (если они ещё не просмотрены, т.е. НОВЫЙ[u]=true для каждой вершины u, принадлежащей этой компоненте). При этом каждая вершина просматривается не более одного раза.
Сам алгоритм начинает поиск поочередно от каждой ещё не просмотренной вершины, следовательно, просматриваются все вершины графа (необязательно связного).
Чтобы оценить сложность алгоритма, заметим, что число шагов в обоих циклах FOR (строки 2,3) ≈ n (не считая шагов, выполненных при вызове WG). Эта программа выполняется не более чем n раз во втором цикле сразу после посещения каждой из вершин для каждого из её новых соседей, суммарно не более чем (n+m) раз. Полное число шагов, выполняемых циклом в третьей строке процедуры WG (не считая шагов, выполняемых WG(n)), для всех вызовов этой процедуры будет порядка m, где m – число ребер. Это дает общую сложность алгоритма О(n+m).
Т.к. поиск в глубину играет важную роль в разработке алгоритмов на графах, представим также нерекурсивную версию процедуры WG.
Стандартным способом устранения рекурсии является использование стека. Каждая просмотренная вершина помещается в стек и удаляется из него после использования.
8(6) [13]
предполагаем, что в начале поиска P[u] – указатель на 1-ую запись списка СПИСОК[u] для каждой вершины u; массивы Р, НОВЫЙ – глобальные}
PROCEDURE WG1(v); {поиск в глубину в графе, начиная с вершины v}
BEGIN СТЕК := 0; СТЕК <= v; рассмотреть v; НОВЫЙ[v] := false;
WHIKE СТЕК ≠ 0 DO
BEGIN t := top(СТЕК); {t – верхний элемент стека} {найти первую новую вершину в списке СПИСОК[t]}
IF P[t] = nil THEN b := false
6 ELSE b := not НОВЫЙ[P[t]^.строка];
7 WHILE b DO BEGIN
P[t] := P[t]^.след;
IF P[t] = nil THEN b := false
ELSE b := not НОВЫЙ[P[t]^.строка]
END;
IF P[t] ≠ nil THEN {найдена новая вершина}
BEGIN t := P[t]^.строка; СТЕК <= t;
рассмотреть t; НОВЫЙ[t] := false
END
ELSE {вершина t использована}
17 t <= СТЕК {удалить верхний элемент стека}
18 END
Примечание. В круглых скобках пронумеруем
вершины графа в той очередности, в
которой они просматриваются в
процессе поиска в глубину.
3(9) [12]
2(2) [2]
5(5) [9]
1(1) [1]
9(7) [10]
13(10) [11]
11(13) [8]
10(12) [7]
Метод легко переносится и на ориентированные графы. Нетрудно проверить, что и в этом случае результатом вызова процедуры WG(v), а также WG1(v), будет посещение за О(n+m) шагов всех вершин u таких, что существует путь из v и u.
Таким образом можно считать, что при поиске в глубину чем позднее будет посещена вершина, тем раньше она будет использована. Это связано с тем, что просмотренные, но еще не использованные вершины скапливаются в стеке.