Алгоритм Тарьяна: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
[непроверенная версия][непроверенная версия]
Содержимое удалено Содержимое добавлено
Нет описания правки
Строка 4: Строка 4:


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


== Неформальное описание алгоритма ==
== Неформальное описание алгоритма ==
Строка 12: Строка 12:
Алгоритм Тарьяна можно понимать как вариацию алгоритма [[поиск в глубину|поиска в глубину]], в котором при посещении вершины и окончании обработки вершины выполняются дополнительные действия. Посещение вершины происходит при движении от корня к листьям, а окончание обработки вершины — на обратном пути. При посещении вершины она проталкивается во вспомогательный стек, при окончании обработки компоненты сильной связности все её вершины выталкиваются из этого стека{{sfn|Джереми Сик и др.|2006}}.
Алгоритм Тарьяна можно понимать как вариацию алгоритма [[поиск в глубину|поиска в глубину]], в котором при посещении вершины и окончании обработки вершины выполняются дополнительные действия. Посещение вершины происходит при движении от корня к листьям, а окончание обработки вершины — на обратном пути. При посещении вершины она проталкивается во вспомогательный стек, при окончании обработки компоненты сильной связности все её вершины выталкиваются из этого стека{{sfn|Джереми Сик и др.|2006}}.


== Алгоритм в псевдокоде ==
Индексы компонент связности всех вершин хранятся в векторе id, индексированном номерами вершин. Вектор low отслеживает вершину с наименьшим номером в прямом порядке обхода, достижимую из каждого узла через последовательность прямых связей, за которыми следует одна восходящая связь. Воспользовавшись поиском в глубину с тем, чтобы рассматривать вершины в обратном топологическом порядке, мы вычисляем для каждой вершины v максимальную точку, достижимую через обратную связь из предшественника (low[v]). Когда для вершины v выполняется pre[v]=low[v], мы выталкиваем её из стека, а также все вершины выше её и всем им присваиваем номер следующей компоненты{{Нет АИ|7|10|2012}}.<!-- мутно. переписать в терминах рисунка -->
''// входные данные: ориентированный граф G = (V, A) ''
''// выходные данные: множество компонент сильной связности ''
''index'' := 0
''S'' := empty stack
'''for each''' ''v'' '''in''' ''V'' '''do'''
'''if''' ''v''.index is undefined '''then'''
strongconnect(''v'')
'''function''' strongconnect(''v'')
''// В index храним количество ранее обработанных вершин, v.index - это "время входа" в вершину v''
''v''.index := ''index''
''v''.lowlink := ''index''
''index'' := ''index'' + 1
''S''.push(''v'')
''// Поле onStack нужно, чтобы проверять принадлежность вершины стеку за O(1)''
''v''.onStack := true
''// Перебираем дуги, исходящие из v''
'''for each''' (''v'', ''w'') '''in''' ''A'' '''do'''
'''if''' ''w''.index is undefined '''then'''
''// Вершина w ранее не посещалась; запустимся из неё рекурсивно''
strongconnect(''w'')
''v''.lowlink := min(''v''.lowlink, ''w''.lowlink)
'''else if''' ''w''.onStack '''then'''
''// Вершина w находится в стеке S, значит, принадлежит той же компоненте сильной связности, что и v''
''// Если w не в стеке, значит, дуга (''v'', ''w'') ведёт в ранее обработанную компоненту сильной связности и должна быть проигнорирована
''// Замечание: в следующей строке намеренно используется w.index вместо w.lowlink - это отсылает к исходной статье Тарьяна''
''// Если заменить w.index на w.lowlink, алгоритм останется корректным''
''v''.lowlink := min(''v''.lowlink, ''w''.index)
''// Вершина v - корень текущей компоненты сильной связности, все верхние вершины стека образуют эту компоненту''
'''if''' ''v''.lowlink = ''v''.index '''then'''
создать новую компоненту сильной связности
'''repeat'''
''w'' := ''S''.pop()
''w''.onStack := false
добавить ''w'' в текущую компоненту сильной связности
'''while''' ''w'' ≠ ''v''
вывести текущую компоненту сильной связности


== Время работы ==
== Время работы ==
Алгоритм имеет временну́ю сложность <math>\mathrm{O}(|V|+|E|)</math>, где <math>|E|</math> — количество рёбер, а <math>|V|</math> — вершин графа{{sfn|Джереми Сик и др.|2006}}.
Алгоритм имеет временну́ю сложность <math>\mathrm{O}(|V|+|A|)</math>, где <math>|A|</math> — количество дуг, а <math>|V|</math> — вершин графа{{sfn|Джереми Сик и др.|2006}}.


== См. также ==
== См. также ==

Версия от 07:43, 30 сентября 2022

Анимация алгоритма Тарьяна

Алгоритм Тарьяна — алгоритм поиска компонент сильной связности в орграфе, работающий за линейное время.


Этот алгоритм основан на том, что:

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

Неформальное описание алгоритма

Алгоритм Тарьяна можно понимать как вариацию алгоритма поиска в глубину, в котором при посещении вершины и окончании обработки вершины выполняются дополнительные действия. Посещение вершины происходит при движении от корня к листьям, а окончание обработки вершины — на обратном пути. При посещении вершины она проталкивается во вспомогательный стек, при окончании обработки компоненты сильной связности все её вершины выталкиваются из этого стека[1].

Алгоритм в псевдокоде

    // входные данные: ориентированный граф G = (V, A) 
    // выходные данные: множество компонент сильной связности 
   
    index := 0
    S := empty stack
    for each v in V do
        if v.index is undefined then
            strongconnect(v)
   
    function strongconnect(v)
        // В index храним количество ранее обработанных вершин, v.index - это "время входа" в вершину v
        v.index := index
        v.lowlink := index
        index := index + 1
        S.push(v)
        // Поле onStack нужно, чтобы проверять принадлежность вершины стеку за O(1)
        v.onStack := true
      
        // Перебираем дуги, исходящие из v
        for each (v, w) in A do
            if w.index is undefined then
                // Вершина w ранее не посещалась; запустимся из неё рекурсивно
                strongconnect(w)
                v.lowlink := min(v.lowlink, w.lowlink)
            else if w.onStack then
                // Вершина w находится в стеке S, значит, принадлежит той же компоненте сильной связности, что и v
                // Если w не в стеке, значит, дуга (v, w) ведёт в ранее обработанную компоненту сильной связности и должна быть проигнорирована
                // Замечание: в следующей строке намеренно используется w.index вместо w.lowlink - это отсылает к исходной статье Тарьяна
                // Если заменить w.index на w.lowlink, алгоритм останется корректным
                v.lowlink := min(v.lowlink, w.index)
      
        // Вершина v - корень текущей компоненты сильной связности, все верхние вершины стека образуют эту компоненту
        if v.lowlink = v.index then
            создать новую компоненту сильной связности
            repeat
                w := S.pop()
                w.onStack := false
                добавить w в текущую компоненту сильной связности
            while wv
            вывести текущую компоненту сильной связности

Время работы

Алгоритм имеет временну́ю сложность , где  — количество дуг, а  — вершин графа[1].

См. также

Алгоритм поиска компонент сильной связности с двумя стеками — аналогичный алгоритм, использующий поиск в глубину.

Примечания

Ссылки

Литература

  • Tarjan R. E. Depth-first search and linear graph algorithms (англ.) // SIAM Journal on Computing. — 1972. — Vol. 1, no. 2. — P. 146–160. — doi:10.1137/0201010.
  • Роберт Седжвик. Алгоритмы на графах = Graph algorithms. — 3-е изд. — Россия, Санкт-Петербург: «ДиаСофтЮП», 2002. — С. 496. — ISBN 5-93772-054-7.
  • Джереми Сик, Лай-Кван Ли, Эндрю Ламсдэйн. C++ Boost Graph Library. — Питер, 2006. — С. 202—205. — 304 с. — ISBN 5-469-00352-3.