![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Глава 19, Орграфы и ориентированные ациклические графы. лял бы s с более высоким номером в результате обратного обхода
Реализация алгоритма Косарайю для орграфа, представленного в виде матрицы смежности, даже проще, чем программа 19.10, поскольку в этом случае не нужно вычислять обратный граф в явном виде; решение этой задачи мы оставляем читателю на самостоятельную проработку (см. упражнение 19.125). Программа 19.11. Сильные компоненты (алгоритм Тарьяна)_____________________ Класс DFS представляет собой еще одну реализацию того же интерфейса, что применялся в программе 19.10. Он использует стек S для хранения каждой вершины до тех пор, пока не выяснится, что все вершины в верхней части стека до определенного уровня принадлежат одной и той же сильной компоненте. Вектор low, индексированный именами вершин, отслеживает вершину с наименьшим номером в прямом порядке обхода, достижимую из каждого узла через некоторую последовательность прямых связей, за которыми следует одна восходящая связь (см. рассуждения по тексту раздела). #include " STACK.ее" template < class 6raph> class SC { const Graph & G; STACK< int> S; int cnt, scnt; vector< int> pre, low, id; void scR(int w) { int t; int min = low[w] = pre[w] = cnt++; S.push(w); typename Graph:: adjIterator A(G, w); for (t = A.beg();! A.end(); t = A.nxt()) { if (pre[t] == -1) scR(t); if (low[t] < min) min = low[t]; } if (min < low[w]) { low[w] = min; return; } do { id[t = S.pop()] = scnt; low[t] = G.V(); } while (t! = w); scnt++; } public: SC(const Graph & G): G(G), cnt(O), scnt(O), pre(G.V(), -1), low(G.V()), id(G.V()) { for (int v = 0; v < G.V(); v++) if (pre[v] == -1) scR(v); } int count() const { return scnt; } bool stronglyreachable (int v, int w) const { return id[v] == id[w]; } >;
Часть 5. Алгоритмы на графах
Алгоритм Тарьяна - это аналог программы, которую мы изучали в главе 17 с целью обнаружения мостов в неориентированных графах (см. программу 18.7). Этот метод основан на двух наблюдениях, которые мы сделали в других контекстах. Во-первых, мы рассматриваем вершины в обратном топологическом порядке, поэтому, когда мы достигнем конца рекурсивной функции для вершины, которую знаем, мы не встретим ни одной вершины из той же сильной компоненты (в силу того, что все вершины, достижимые из этой вершины, уже подверглись обработке). Во-вторых, обратные связи в дереве обеспечивают второй путь из одной вершины в другую и связывают между собой сильные компоненты. Рекурсивная функция DFS использует те же вычисления, что и программа 18.7, с целью определения достижимой вершины с максимальным номером (через обратную связь) из любого потомка в каждой вершине. Она использует также вектор, индексированный именами вершин, для хранения сильных компонент и стек для отслеживания текущего пути поиска. Она заталкивает имена вершин в стек на входе в рекурсивную функцию, затем выталкивает их из стека и назначает номера компонентам после посещения завершающего элемента каждой сильной компоненты. Этот алгоритм построен на нашей способности фиксировать этот момент при помощи простой проверки (в основе которой лежит отслеживание предка с максимальным номером, достижимого через одну восходящую связь из всех потомков каждого узла) в конце рекурсивной процедуры, которая сообщает, что все вершины, встреченные с момента вхождения (за исключением тех, которые уже были назначены компоненте) принадлежат той же сильной компоненте. Реализация в виде программы 19.11 представляет собой полное описание рассматриваемого алгоритма в сжатой форме, содержащее все необходимые подробности, которых не хватает в данном выше общем описании. Рисунок 19.29 служит иллюстрацией работы рассматриваемого алгоритма на демонстрационном примере орграфа, показанного на рис. 19.1. Свойство 19.15. Алгоритм Тарьяна находит сильные компоненты орграфа за линейное время. Набросок доказательства: Если у вершины s нет потомков или восходящих связей в дереве DFS, либо если у нее есть потомок в дереве DFS с восходящей связью, которая указывает на s, и нет потомков с восходящими связями, которые направлены в верхнюю часть дерева, тогда она и все ее потомки (за исключением тех вершин, которые удовлетворяют тому же свойству, и их потомки) составляют сильную компоненту. Чтобы установить этот факт, заметим, что каждый потомок t вершины s, который не удовлетворяет сформулированному свойству, имеет потомка, обладающего восходящей связью, указывающей на вершину, которая в дереве выше t. Существует путь из s в t вниз по дереву, а мы можем найти путь из t в s следующим образом: спустимся вниз по дереву из t в вершину с восходящей связью, которая ведет в вершину, расположенную выше t, затем продолжаем тот же процесс из этой вершины, пока не достигнем s. Как обычно, этот метод линеен по времени, поскольку в данном случае к стандартному DFS добавляется несколько операций, выполняющихся за постоянное время. ■ Глава 19. Орграфы и ориентированные ациклические графы РИСУНОК 19.29. ВЫЧИСЛЕНИЕ СИЛЬНЫХ КОМПОНЕНТ (АЛГОРИТМЫ ТАРЬЯНА И ГАБОВА) В основе алгоритма Тарьяна лежит рекурсивный поиск в глубину, в который добавлена операция проталкивания вершин в стек. Он вычисляет индекс компоненты для каждой вершины в векторе id, индексированном именами вершин, используя векторы preи low (в центре). Дерево DFS (дерево поиска в глубину) для рассматриваемого примера графа показано в верхней части рисунка. Трассировка ребер показана внизу слева. В центре нижней части находится содержимое главного стека: мы заталкиваем в него вершины, достижимые через древесные ребра. Воспользовавшись поиском в глубину с тем, чтобы рассматривать вершины в обратном топологическом порядке, мы вычисляем для каждой вершины v максимальную точку, достижимую через обратную связь из предшественника (low[v]). Когда для вершины v выполняется pre[v]— low[v] (в данном случае это вершины 11, 1, 0 и 7), мы выталкиваем ее из стека, а также все вершины выше ее и всем им присваиваем номер следующей компоненты. В алгоритме Габова мы снова заталкиваем вершины в главный стек точно так же, как и в алгоритме Тарьяна, но в то же время мы поддерживаем и второй стек (внизу справа), в который заталкиваются вершины, лежащие на пути поиска (о последних известно, что они находятся в различных сильных компонентах), и выталкиваем все вершины после достижения каждого обратного ребра. Если мы завершаем обработку вершины v, когда v находится на верхушке второго стека (заштриховано), мы знаем, что все вершины, расположенные над v в главном стеке, находятся в одной и той же сильной компоненте. Часть 5. Алгоритмы на графах
Пример на рис. 19.29 показывает содержимое этого второго стека. Следовательно, данная диаграмма может служить иллюстрацией работы алгоритма Габова. Свойство 19.16. Алгоритм Габова находит сильные компоненты орграфа за линейное время. Формализацию только что изложенных аргументов и доказательство отношений, связывающих содержимое стеков, на котором оно основано, мы оставляем на самостоятельную проработку (см. упражнение 19.132) для читателей, имеющих склонность к математике. Этот метод также линеен по времени, поскольку в данном случае к стандартному поиску в глубину добавляется несколько операций, выполняющихся за постоянное время. ■
|