Опубликован: 05.01.2015 | Доступ: свободный | Студентов: 2177 / 0 | Длительность: 63:16:00
Лекция 18:

Поиск на графе

Свойства лесов DFS

Как было сказано в разделе 18.2, деревья, которые описывают рекурсивную структуру вызовов функции DFS, позволяют понять, как выполняется поиск в глубину. В данном разделе мы ознакомимся со свойствами этого алгоритма, изучая свойства деревьев DFS.

Если добавить в дерево DFS дополнительные узлы для пропусков рекурсивных вызовов в случае уже посещенных вершин, то получится компактное представление о динамике поиска в глубину, как на рис. 18.9. Эта древовидная структура заслуживает подробного изучения. Она является представлением графа: каждой вершине дерева соответствует вершина графа, а каждому его ребру — ребро графа. Можно показывать или оба представления обрабатываемого ребра (по одному в каждом направлении), как показано в левой части рис. 18.9, или только одно представление, как показано в центральной и правой частях рисунка. Первый вариант наглядно показывает, что алгоритм обрабатывает каждое ребро, а второй — что дерево DFS представляет собой просто еще одно представление графа. Прямой обход внутренних узлов дерева в прямом порядке посещает вершины в порядке их просмотра при поиске в глубину; более того, порядок посещения ребер при прямом обходе дерева совпадает с порядком просмотра ребер в графе алгоритмом DFS.

Вообще-то дерево DFS, показанное на рис. 18.9, содержит ту же информацию, что и трасса на рис. 18.5 или пошаговая иллюстрация обхода методом Тремо на рис. 18.2 и рис. 18.3. Ребра, ведущие к внутренним узлам, соответствуют ребрам (коридорам) к еще не посещенным вершинам (перекресткам); ребра, ведущие к внешним узлам, соответствуют случаям, когда DFS проверяет ребра, ведущие к уже посещенным вершинам (перекресткам); а заштрихованные узлы соответствуют ребрам к вершинам, для которых в данный момент выполняется рекурсивный поиск в глубину (когда мы открываем дверь в коридор, в котором дверь на противоположном конце уже открыта). При такой интерпретации прямой обход дерева сообщает нам ту же информацию, что и подробный способ обхода лабиринта.

Чтобы перейти к более сложным свойствам графов, разграничим ребра графа в соответствии с ролью, которую они играют в поиске. Имеются два различных класса ребер:

  • ребра, представляющие рекурсивные вызовы (древесные (tree) ребра);
  • ребра, соединяющие в дереве DFS вершину с ее предшественником, который не является ее родителем (обратные (back) ребра).

При изучении деревьев DFS для орграфов в "Орграфы и DAG-графы" мы рассмотрим и другие типы ребер — не только для учета направления ребер, но и потому, что в графе возможны ребра, которые идут поперек дерева и соединяют узлы, не являющиеся в этом дереве ни предками, ни потомками.

 Различные представления дерева DFS

Рис. 18.9. Различные представления дерева DFS

Если добавить в дерево рекурсивных вызовов DFSребра, которые мы проверяем, но не проходим, то получится полное описание процесса DFS (слева). У каждого узла дерева имеются потомки, представляющие все смежные с ним узлы в порядке их перебора алгоритмом DFS. Прямой обход дерева дает ту же информацию, что и рис. 18.5: сначала мы проходим ребро 0-0, затем 0-2, далее пропускаем 2-0, затем проходим по 2-6, пропускаем 6-2, потом проходим по 6-4, 4-3 и т.д. Вектор ord определяет порядок посещения вершин дерева при прямом обходе, он совпадает с порядком посещения вершин графа алгоритмом DFS. Вектор st является представлением родительскими ссылками дерева рекурсивных вызовов DFS (см. рис. 18.6).

Для каждого ребра графа в этом дереве имеются две ссылки — для каждого из двух раз, когда встречается это ребро. Первая ссылка ведет в незаштрихованный узел и соответствует либо выполнению рекурсивного вызова (если ссылка ведет во внутренний узел), либо пропуску рекурсивного вызова, поскольку ссылка указывает на предшествующий узел, для которого в данный момент выполняется рекурсивный вызов (если она ведет во внутренний узел). Вторая ссылка ведет в заштрихованный внешний узел и всегда соответствует пропуску рекурсивного вызова — либо потому, что она ведет назад к родителю (кружки), либо потому, что ведет к потомку родителя, для которого в данный момент выполняется рекурсивный вызов (квадратики). Если удалить заштрихованные узлы (в центре), а затем заменить внешние узлы ребрами, то получится другой чертеж графа (справа).

Поскольку существуют два представления каждого ребра графа, и каждое ребро соответствует ссылке в дереве DFS, мы разобьем все ссылки дерева на четыре класса, воспользовавшись прямыми номерами (preorder number) и родительскими ссылками (соответственно, в массивах ord и st), которые вычисляет код поиска в глубину. Будем называть ссылку из вершины v на w в дереве DFS, которая представляет ребро дерева, так:

  • древесная ссылка (tree link), если v не помечена;
  • родительская ссылка (parent link), если st[w] содержит v,

а ссылку из v на w, которая представляет обратное ребро, так:

  • обратная ссылка (back link), если ord[w] < ord[v];
  • нисходящая ссылка (down link), если ord[w] > ord[v].

Каждое древесное ребро в графе соответствует древесной ссылке и родительской ссылке в дереве DFS, а каждое обратное ребро в графе соответствует обратной ссылке и нисходящей ссылке в дереве DFS.

В графическом представлении DFS, показанном на рис. 18.9, древесные ссылки указывают на светлые кружки, родительские ссылки — на серые кружки, обратные ссылки — на светлые квадратики, а нисходящие ссылки — на серые квадратики. Каждое ребро графа представлено либо одной древесной ссылкой и одной родительской ссылкой, либо одной нисходящей ссылкой и одной обратной ссылкой.

Эти термины достаточно запутанны, но их следует изучить. Например, учтите, что даже если родительские ссылки и обратные ссылки указывают на предков в дереве, они различны: родительская ссылка — это всего лишь другое представление древесной ссылки, а обратная ссылка дает новую информацию о структуре графа.

Приведенные выше определения предоставляют достаточно информации, чтобы провести различие между ссылкой древовидной структуры, родительской ссылкой, обратной ссылкой и нисходящей ссылкой в реализации класса DFS. Учтите, что условие ord[w] < ord[v] выполняется и для родительских, и для обратных ссылок, т.е., чтобы узнать, что v-w представляет собой обратную ссылку, необходимо проверить, что st[w] не равно v. На рис. 18.10 представлена распечатка результатов классификации ссылок дерева DFS для всех ребер некоторого графа в порядке их рассмотрения алгоритмом DFS. Это еще одно полное представление базового процесса поиска, которое может служить промежуточным этапом между рисунками 18.5 и 18.9.

 Трасса поиска в глубину (классификация ссылок дерева)

Рис. 18.10. Трасса поиска в глубину (классификация ссылок дерева)

В данном варианте рис. 18.5 приведена классификация ссылок в дереве DFS, соответствующих представлениям каждого ребра графа. Древесные ребра (которые соответствуют рекурсивным вызовам) представлены как древесные ссылки при первой встрече и как родительские ссылки при второй встрече, а обратные ребра — как обратные ссылки при первой встрече и как нисходящие ссылки при второй встрече.

Четыре описанных выше типа ссылок соответствуют четырем различным способам обработки ребер при поиске в глубину, которые были описаны (в терминах обхода лабиринта) в конце раздела 18.1. Древесная ссылка соответствует встрече первого из двух представлений древесного ребра при работе DFS, что приводит к рекурсивному вызову (к еще не просмотренным вершинам); родительская ссылка соответствует встрече с другим представлением древесного ребра (при просмотре списка смежности первым таким рекурсивным вызовом) и игнорированию этого ребра. Обратная ссылка соответствует встрече первого из двух возможных представлений обратного ребра, указывающего на вершину, для которой рекурсивный поиск еще не закончен; нисходящая ссылка соответствует встрече вершины, для которой рекурсивный поиск уже закончен. На рис. 18.9 древесные и обратные ссылки соединяют светлые узлы, представляют первую встречу соответствующего ребра и входят в представление графа; родительские и нисходящие ссылки ведут в серые узлы и означают вторую встречу с соответствующим ребром.

Мы подробно рассмотрели это древовидное представление динамических характеристик рекурсивного алгоритма DFS не только потому, что оно представляет собой полное и компактное описание как графа, так и работы алгоритма, но и потому, что оно позволяет понять множество важных алгоритмов обработки графов. В оставшейся части данной главы и в нескольких последующих лекциях мы рассмотрим ряд примеров задач обработки графов, где можно сделать выводы относительно структуры графа, рассматривая дерево DFS.

Поиск на графе является обобщением обхода дерева. На дереве алгоритм DFS в точности эквивалентен рекурсивному обходу дерева; на графах он соответствует обходу остовного дерева этого графа, которое строится при выполнении поиска. Как мы уже знаем, конкретный вид дерева обход зависит от представления графа. Поиск в глубину соответствует прямому обходу дерева. В разделе 18.6 мы познакомимся с алгоритмом поиска на графе, который аналогичен обходу дерева по уровням, и выясним, как он соотносится с алгоритмом DFS, а в разделе 18.7 мы рассмотрим общую схему, которая охватывает все методы обхода.

При обходе графов с помощью DFS мы использовали вектор ort для присвоения вершинам прямых номеров в порядке начала их обработки. Вершинам можно присвоить и обратные номера (postorder numbers), т.е. номера в порядке завершения их обработки (непосредственно перед выходом из функции рекурсивного поиска). В процессе обработки графа выполняется не просто обход вершин — как мы вскоре увидим, прямая и обратная нумерация предоставляет сведения о глобальных свойствах графа, которые помогают справиться с решением некоторых задач. Для алгоритмов, рассматриваемых в данной главе, достаточно прямой нумерации, а обратная нумерация пригодится нам в последующих лекциях.

Мы описываем динамику поиска в глубину на неориентированных графах общего вида с помощью леса DFS (DFS forest), в котором каждое дерево DFS представляет один связный компонент графа. Пример леса DFS показан на рис. 18.11.

Если граф представлен списками смежности, то порядок обхода ребер, связанных с каждой вершиной, не совпадает с порядком для представления матрицей смежности, поэтому получится другой лес DFS (см. рис. 18.12). Деревья и леса DFS — это представления графов, которые описывают не только динамику поиска в глубину, но и внутреннее представление графов. Например, просмотрев потомков любого узла на рис. 18.12 слева направо, мы увидим их порядок в списке смежности вершины, соответствующей этому узлу. Для одного и того же графа можно получить множество лесов — каждый новый порядок узлов в списках смежности приводит к появлению другого леса.

 Лес DFS

Рис. 18.11. Лес DFS

Лес DFS в верхней части рисунка соответствует поиску в глубину на графе, представленном матрицей смежности в нижней правой части рисунка. Граф состоит из трех связных компонентов, поэтому и лес содержит три дерева. Вектор ord содержит прямую нумерацию узлов дерева (порядок, в котором они просматриваются алгоритмом DFS), а вектор st содержит представление леса родительскими ссылками. Вектор cc связывает каждый компонент с индексом связного компонента (см. программу 18.4). Как и на рис. 18.9, ребра, ведущие к кружкам, — это древесные ребра, а ребра, ведущие к квадратикам — обратные ребра; заштрихованные узлы указывают, что инцидентное ребро было обнаружено раньше, при поиске в другом направлении.

 Другой лес DFS

Рис. 18.12. Другой лес DFS

Данный лес описывает поиск в глубину на том же графе, что и на рис. 18.11, но здесь используется представление графа списками смежности, что меняет порядок поиска, поскольку он определяется порядком узлов в списках смежности. Вообще-то этот порядок виден из самого леса: это порядок, в каком перечислены потомки каждого узла дерева. Например, узлы списка смежности для вершины 0 расположены в порядке 5 2 1 6, для вершины 4 — в порядке 6 5 3 и т.д. Как и раньше, при поиске просматриваются все вершины и ребра графа, в порядке, который точно описан прямым обходом дерева. Векторы ord и st зависят от представления графа и динамики поиска (поэтому они отличаются от приведенных на рис. 18.11), но вектор cc зависит только от свойств графа, поэтому он не изменился.

Особенности структуры конкретного леса позволяют понять, как ведет себя DFS на том или ином графе, но большая часть важных свойств DFS определяется свойствами графа, которые не зависят от структуры леса. Например, оба леса, показанные на рис. 18.11 и рис. 18.12, содержат по три дерева (как и любой другой лес DFS того же графа), поскольку это просто различные представления одного и того же графа, состоящего из трех связных компонентов. Ведь из доказательства того, что поиск в глубину посещает все узлы и ребра графа (см. леммы 18.2—18.4), следует, что число связных компонентов графа равно числу деревьев в лесе DFS. Этот пример демонстрирует основное применение поиска на графе в данной книге: огромное множество реализаций классов обработки графов основано на изучении свойств графа путем обработки конкретного его представления (леса, соответствующего поиску).

В принципе, анализ структуры деревьев DFS помогает повысить производительность алгоритма. Например, стоит ли пытаться повысить быстродействие алгоритма с помощью переупорядочивания списков смежности перед началом поиска? Для многих важных классов алгоритмов на основе поиска в глубину ответ на этот вопрос отрицательный, поскольку они и так оптимальны — время их выполнения даже в худшем случае не зависит ни от структуры графа, ни от порядка ребер в списках смежности (ведь они обрабатывают каждое ребро в точности один раз). Но все-таки леса DFS обладают характерной структурой, которая заслуживает изучения хотя бы потому, что она отличает их от другой фундаментальной схемы, которая будет рассмотрена ниже в этой главе.

На рис. 18.13 показано дерево DFS крупного графа, на котором видны базовые характеристики динамики поиска в глубину. Это высокое и узкое дерево демонстрирует несколько свойств просматриваемого графа и процесса поиска в глубину.

  • Существует как минимум один длинный путь, который соединяет существенную часть узлов.
  • Во время поиска у большинства вершин имеется по меньшей мере одна еще не просмотренная смежная вершина.
  • Из каждого узла редко выполняется более одного рекурсивного вызова.
  • Глубина рекурсии пропорциональна количеству вершин графа.

Такое поведение типично для поиска в глубину, хотя эти характеристики и не гарантируются для всех графов. Проверка подобных фактов для интересующих нас моделей графов и различных видов графов, возникающих на практике, требует тщательных исследований. Но все-таки этот пример позволяет почувствовать специфику алгоритмов на основе DFS, которая часто подтверждается на практике. рис. 18.13 и аналогичные иллюстрации других алгоритмов поиска на графах (см. рис. 18.24 и рис. 18.29) помогают лучше понять различия в их поведении.

 Поиск в глубину

Рис. 18.13. Поиск в глубину

Здесь показан процесс поиска в глубину в случайном евклидовом графе с соседними связями (слева). На рисунке показаны вершины и ребра дерева DFS в графе в моменты, когда процедура поиска просмотрела 1/4, 1/2, 3/4 и все вершины графа (сверху вниз). Дерево DFS (только древесные ребра) показано справа. Как видно из этого примера, деревья поиска в глубину для таких графов (да и для многих других видов графов, часто встречающихся на практике) обычно имеют узкую и длинную форму. Как правило, еще не просмотренная вершина находится поблизости.

Упражнения

18.15. Нарисуйте лес DFS, который получается при работе стандартного DFS на графе, заданном матрицей смежности:

3-71-47-80-55-23-82-90-64-92-66-4.

18.16. Нарисуйте лес DFS, который получается при работе стандартного DFS на графе, заданном списками смежности:

3-71-47-80-55-23-82-90-64-92-66-4.

18.17. Напишите программу трассировки поиска в глубину, которая в стиле рис. 18.10 выводит характеристику каждого из двух представлений всех ребер графа: древесная ссылка, родительская ссылка, обратная ссылка или нисходящая ссылка в дереве DFS.

18.18. Напишите программу, которая вычисляет представление родительскими ссылками полного дерева DFS (включая внешние узлы) с помощью вектора из E целых чисел от 0 до V— 1. Указание. Первые V компонентов этого вектора должны совпадать с компонентами вектора st, описанного в тексте.

18.19. Добавьте в класс DFS остовного леса (программа 18.3) функции-члены (и соответствующие члены данных), которые возвращают высоту самого высокого дерева леса, количество обратных ребер и процент ребер, обработанных для просмотра всех вершин.

18.20. Определите эмпирически средние значения величин из упражнения 18.19 для графов различных размеров, построенных на основе различных моделей графов (см. упражнения 17.64—17.76).

18.21. Напишите функцию, выполняющую построение графа вставками в первоначально пустой граф ребер, выбранных случайным образом из заданного вектора. Используя эту функцию вместе с реализацией АТД графа списками смежности, определите эмпирически свойства распределения величин, описанных в упражнении 18.19, для всех представлений списками смежности различных крупных графов, построенных на основе различных моделей графов (см. упражнения 17.64—17.76).

Бактыгуль Асаинова
Бактыгуль Асаинова

Здравствуйте прошла курсы на тему Алгоритмы С++. Но не пришел сертификат и не доступен.Где и как можно его скаачат?

Александра Боброва
Александра Боброва

Я прошла все лекции на 100%.

Но в https://www.intuit.ru/intuituser/study/diplomas ничего нет.

Что делать? Как получить сертификат?