Алгоритмы поиска в глубину и ширину в графах
Одним из способов построения максимального остовного леса может служить поиск в глубину. Именно при поиске в глубину всякое ребро, по которому из текущей вершины мы попадаем в неотмеченную ранее вершину, будет древесным. Каждое ребро, не являющееся древесным, будет обратным. Максимальный остовный лес, находимый с помощью алгоритма поиска в глубину, называют остовным лесом поиска в глубину или глубинным остовным лесом.
Заметим, что классификация ребер зависит от хода работы алгоритма, который определяется стартовой вершиной и расположением вершин в списках смежности.
Таким образом, алгоритм поиска в глубину в неориентированном графе может быть использован для вычисления множества фундаментальных циклов графа, т.е. решения одной из задач глобального анализа.
 Отметим, что не всякий максимальный остовный лес может быть получен с помощью алгоритма поиска в глубину. Например, для графа, изображенного на рис. 5.23, выделенный максимальный остовный лес нельзя получить при поиске в глубину, из какой бы вершины мы не начинали поиск.
Для организации работы алгоритма поиска в глубину используется способ хранения данных, называемый стеком. Элементы в стеке упорядочиваются в порядке поступления. В стек можно добавлять новые элементы и из него можно извлекать элементы. При этом доступен только последний добавленный элемент — вершина стека, чем и определяется режим работы стека (последним пришел — первым ушел). Образно стек можно представить в виде стопки тарелок. Из стопки можно взять только верхнюю тарелку и только наверх можно добавить новую. Обычно стек реализуется в виде списка.
В алгоритме поиска в глубину используется стек вершин. Мы будем считать, что из стека можно считывать информацию без изменения его содержимого, но в том порядке, в каком ее удаляли бы из стека. Указанная операция считывания понадобится нам для поиска фундаментальных циклов.
Алгоритм поиска в глубину
Вход. Граф , заданный списками смежности.
Выход. Множества Tree древесных и Back обратных ребер соответственно; множество фундаментальных циклов, массив , содержащий D-номера вершин.
0. Просмотреть массив лидеров и сформировать множество вершин графа. Это множество используется для хранения новых (не обработанных алгоритмом) вершин. В ходе работы алгоритма обработанные вершины будут удаляться из этого множества.
В процессе работы алгоритма для каждой вершины v необходимо знать, какие вершины из ее списка смежности обработаны на предыдущих шагах работы алгоритма. Для этого формируется массив размера , в котором хранятся номера первых еще не обработанных алгоритмом вершин списка смежности каждой вершины . Первоначально все элементы массива полагают равными 1.
Стек вершин STACK и список вершин фундаментального цикла Cycle положить пустыми .
Множества Tree древесных ребер, Back обратных ребер и фундаментальных циклов положить пустыми . Массив D-номеров обнулить.
Задать начальную вершину для поиска . (Обычно это первая вершина массива лидеров.) Счетчик D-номеров вершин count положить равным 1 (count=1).
1. Если множество не пусто , перейти на шаг 2, если иначе — на шаг 8 (выход).
2. Если стек пуст и алгоритм стартует из вершины (count=1), то перейти на шаг 3, если иначе, то выбрать из множества произвольную вершину, из которой поиск в глубину будет продолжен , и перейти на шаг 3.
3. Поместить текущую вершину в стек STACK. Присвоить вершине D-номер . Увеличить счетчик D-номеров на 1 . Удалить вершину из множества новых вершин. Перейти на шаг 4.
4. Проверить по элементу , что не достигнут конец списка смежности текущей вершины . Если в списке смежности есть еще не обработанные вершины, получить из списка смежности очередную вершину , увеличить на 1 и перейти на шаг 6.
Если список смежности вершины v обработан полностью, удалить вершину из стека STACK и перейти на шаг 5.
5. Если стек STACK пуст, вернуться на шаг 1, если иначе, то в качестве текущей вершины взять вершину, находящуюся в вершине стека, и перейти на шаг 4.
6. Если вершина новая , то добавить ребро в множество древесных ребер
сделать вершину текущей и вернуться на шаг 3. Если вершина w не новая , то перейти на шаг 7.
7. Если ребра нет среди древесных , поместить ребро в список обратных ребер
Поместить вершину в список Cycle. Читать содержимое стека STACK, начиная с вершины стека, до появления вершины и помещать в список Cycle (включая вершину ), т.е. формировать фундаментальный цикл, соответствующий обратному ребру .
Добавить список Cycle в множество фундаментальных циклов 
Список Cycle положить пустым . Вернуться на шаг 4.
8. Закончить работу.
Пусть для неориентированного графа, изображенного на рис. 5.20,а, списки смежности имеют вид (5.1). При выполнении поиска в глубину выделенные ребра являются древесными, остальные — обратными. Около каждой вершины указан присвоенный ей D-номер. Фундаментальные циклы — в порядке их нахождения — таковы:
 и  .
Поиск в глубину в неориентированном графе можно использовать и для нахождения числа его компонент. Очевидно, что это число равно числу опустошений стека от начала до конца поиска в глубину. Регистрируя удаляемые из стека вершины, можно после очередного его опустошения распечатать номера вершин, принадлежащих данной компоненте.
В ориентированном графе поиск в глубину реализуется во многом аналогично поиску в глубину в неориентированном графе. В этом случае возникает остовный ориентированный лес поиска в глубину, дуги которого — это в точности те дуги, по которым в процессе работы алгоритма переходят от очередной вершины к новой, еще не отмеченной вершине. Можно показать, что это максимальный остовный лес исходного графа.
Слабыми компонентами этого глубинного остовного леса будут некоторые ориентированные деревья: поэтому, используемая далее терминология из теории деревьев относится к той или иной слабой компоненте глубинного остовного леса. В ориентированном графе вершинам также присваиваются D-номера. Но классификация дуг при поиске в глубину в ориентированном графе сложнее по сравнению с аналогичной классификацией ребер при поиске в глубину в неориентированном графе. Различают четыре класса дуг:
1) древесные дуги — каждая такая дуга ведет от отца к сыну в глубинном остовном лесу; 2) прямые дуги — каждая такая дуга ведет от подлинного предка к подлинному потомку (но не от отца к сыну) в глубинном остовном лесу; 3) обратные дуги — от потомков к предкам (включая все петли); 4) поперечные дуги — все дуги, не являющиеся ни древесными, ни прямыми, ни обратными.
Следовательно, в результате работы алгоритма будут получены множества Tree — древесных дуг, Back — обратных дуг, Forward — прямых дуг, — поперечных дуг и массив , содержащий D-номера вершин.
В процессе работы алгоритма по сравнению с алгоритмом поиска в глубину в неориентированном графе имеется ряд особенностей. Так, если очередная вершина , извлеченная из списка смежности текущей вершины , новая, т.е. , то дуга является древесной. Следует добавить дугу в множество древесных дуг , сделать вершину текущей и начать ее обработку.
Если вершина не новая , то дуга будет либо прямой, либо обратной, либо поперечной.
Если D-номер вершины v строго меньше D-номера вершины , то дуга является прямой. Добавляем ее в множество прямых дуг Forward .
Если D-номер вершины не меньше D-номера вершины , необходимо проверить, есть ли в стеке STACK вершина . Если вершина находится в стеке, то дуга является обратной и ее следует добавить в множество обратных дуг Back . Если вершины в стеке нет, то дуга является поперечной. Тогда нужно добавить ее в множество поперечных дуг . Далее следует перейти к рассмотрению очередной вершины из списка смежности текущей вершины (если он не пуст).
Если стек пуст, но не все вершины ориентированного графа обработаны, поиск продолжают из любой необработанной вершины.
В случае ориентированного графа поиск контуров на базе поиска в глубину существенно сложнее, чем в случае неориентированного графа, и здесь он не рассматривается. Однако можно доказать следующий критерии бесконтурности ориентированного графа: ориентированный граф является бесконтурным тогда и только тогда, когда при поиске в глубину от некоторой начальной вершины множество обратных дуг оказывается пустым.
Заметим также, что для ориентированного графа нет такой простой связи между числом опустошений стека и числом компонент, как для неориентированного графа.
На базе алгоритма поиска в глубину может быть разработан эффективный алгоритм поиска бикомпонент в ориентированном графе. Однако здесь мы не будем останавливаться на задаче поиска бикомпонент, так как эта задача обсуждается в рамках общей задачи о путях в графах.
Можно показать, что алгоритм поиска в глубину имеет сложность порядка .
Пример 5.7. Проведем поиск в глубину на ориентированном графе, представленном на рис. 5.24. Поиск начнем из вершены . Пусть списки смежности вершин имеют следующий вид:
 (5.2)
Результаты выполнения поиска в глубину представлены на рисунке: около каждой вершины указан ее D-номер, все древесные дуги выделены более толстыми стрелками, а прямые, обратные и поперечные помечены буквами соответственно.
Первое опустошение стека происходит после обработки первых пяти вершин (в порядке обхода): .
После опустошения поиск продолжается из вершины , которой присваивается D-номер 6. (Напомним, что в приведенном выше алгоритме после опустошения стека для продолжения поиска выбирается любая из необработанных вершин.)
Алгоритм поиска в ширину в ориентированном графе
В отличие от алгоритма поиска в глубину, алгоритм поиска в ширину использует не стек, а очередь вершин. Мы дадим здесь вариант поиска в ширину, когда, начиная поиск в некоторой начальной вершине , мы останавливаемся при первом же опустошении очереди. Это значит, что некоторые из вершин могут остаться неотмеченными. Таким образом может быть решена задача распознавания достижимости от заданной вершины. Мы совместим также поиск в ширину с вычислением длины кратчайшего пути от до любой вершины графа. Будем предполагать, что вершины графа пронумерованы без пропусков числами от 0 до .
Поиск в ширину рассматриваем только для ориентированного графа.
Вход. Граф , заданный списками смежности; — начальная вершина (не обязательно первый элемент массива лидеров).
Выход. Массив меток вершин, где каждая метка равна длине пути от до .
0. Очередь положить пустой . Все вершины пометить как недостижимые из вершины , присваивая элементам массива значение .
Стартовую вершину пометить номером 0, т.е. длину пути от стартовой вершины до самой себя положить равной 0 . Поместить вершину в очередь . Перейти на шаг 1.
1. Если очередь не пуста , то из "головы" очереди извлечь (с удалением из очереди) вершину и перейти на шаг 2. Если очередь пуста, перейти на шаг 3.
2. Если список смежности вершины пуст, вернуться на шаг 1.
Если список смежности вершины и не пуст, для каждой вершины w из списка смежности, где , т.е. вершины, которую еще не посещали, положить длину пути из стартовой вершины до вершины равной длине пути от до вершины плюс одна дуга , т.е. отметить вершину и поместить ее в очередь . После просмотра всех вершин списка смежности вернуться на шаг 1.
3. Распечатать массив . Закончить работу.
Алгоритм поиска в ширину может быть дополнен процедурой "обратного хода", определяющей номера вершин, лежащих на кратчайшем пути из вершины в данную вершину . Для этого необходимо завести массив размера , каждый элемент которого содержит номер той вершины, из которой был осуществлен переход в вершину при ее пометке.
Если вершина находится в списке смежности вершины , заполнение элемента массива происходит при изменении метки вершины с на единицу. При этом в элементе сохраняется номер вершины и . Для начальной вершины можно положить равным 0, в предположении, что начальная вершина имеет номер 0 и остальные вершины пронумерованы от 1 до N.
Сложность рассмотренного алгоритма поиска в ширину, известного под названием "Алгоритм волнового фронта", составляет .
Пример 5.8. На рис. 5.25 дан пример работы алгоритма волнового фронта (при поиске из вершины ) для ориентированного графа, изображенного на рис. 5.24. Списки смежности ориентированного графа имеют вид (5.2).
Около каждой вершины поставлена метка , которую получает вершина при поиске в ширину. Выделены дуги, составляющие кратчайшие пути из вершины в остальные. Отметим, что вершины и не достижимы из и их начальные метки остались без изменения. При рассмотренном ходе алгоритма массив будет содержать следующие номера вершин:
Для остальных вершин соответствующие значения не определены, поскольку они не "посещались".
Используя массив , восстановим кратчайший путь из вершины в вершину . Поскольку , a , искомый путь есть .
Если заметили ошибку, опечатку или есть предложения, напишите в комментариях.
|