Двусвязные компоненты
Задан связный неориентированный граф
.
является двусвязным, если он остается связным при выбрасывании любой его вершины с инцидентными ребрами.
содержит т очку сочленения
, если для некоторой пары вершин
и
все пути из
в
проходят через
.
Удаление точки сочленения (точки разрыва) разбивает граф, по крайней мере, на 2 несвязные части.
![]() |
На мн-ве ребер
графа
можно определить отношение эквивалентности:
, если
или оба ребра лежат в одном цикле.
Пусть
– классы эквивалентности (
) и
– соответствующие им множества вершин. Тогда
– это двусвязные компоненты
.
Лемма, определяющая двусвязные компоненты, точки сочленения и их свойства (Ахо):
1. Графы
– двусвязные
.
2.
мн-во
содержит не более одной вершины.
3.
– точка сочленения
для некоторых
.
=> Точки сочленения разделяют двусвязные компоненты.
Лемма (Ахо). Пусть построено глубинное остовное дерево. Вершина
будет точкой сочленения тогда и только тогда, когда выполняется одно из условий:
1.
– корень и имеет более 1 сына (поддеревья
связаны только через
).
2.
– не корень и для некоторого его сына
нет обратных ребер, соединяющих
и\или ее потомков с предками
.
![]() |
Пусть при поиске в глубину вершинам присвоена новая нумерация в порядке их обхода. Тогда, если
– потомок
,
– обратное ребро и
, то
– предок
.
Определим функцию «нижний»
:
обратное ребро
, где
– потомок
(или
), а
– предок
(не отец при
, т.к.
– обратное ребро).
![]() |
(На рисунке значения
– черные,
– красные).
Значения функции
нужно определять рекурсивно, вместе с поиском в глубину, как минимальное из 3 значений:
1.
(начальное значение).
2.
, где
– сын
.
3.
, если
обратное ребро
.
Следствие последней леммы:
– не корень и точка сочленения
, где
– сын
.
При поиске в глубину будем помещать все выделенные ребра (древесные и обратные) в стек и выталкивать оттуда мн-ва ребер, принадлежащих отдельным двусвязным компонентам. Для этого достаточно при рекурсивном подъеме находить древесные ребра
, для которых
: все ребра от
и выше в стеке
одной компоненте.
В программе ниже используются массивы:
Old[n] – старая или новая (непроверенная) вершина,
NV[n] – новый номер вершины,
Low[n] – значения функции,
Fath[n] – номера вершин-отцов.
Граф задан списками смежных вершин L[n].
Подготовка массивов, выделение корней глубинных остовных деревьев (для отдельных компонент связности), вызов рекурсивной функции поиска в глубину:
dcomp = 0; newnum = 0;
for (k = 0; k < n; k++) {
Old[k] = false; Fath[k] = -1;
}
for (k = 0; k < n; k++)
if (!Old[k]) search2(k);
Поиск в глубину с выделением двусвязных компонент:
void search2(int v) {
Old[v] = true; Low[v] = NV[v] = ++newnum;
for (w
L[v])
if (Old[w] == 0) { // (v,w) - древесное
push(v, w); Fath[w] = v; search2(w);
if (Low[w]>=NV[v]) {dcomp++; //v – ТС
do rib=pop(); добавить(rib, dcomp);
while (rib!= (v,w));
} Low[v] = min(Low[v], Low[w]);
}
else if (NV[w]<NV[v] && w!=Fath[v]) { // обр
push(v,w); Low[v] = min(Low[w], NV[w]);
}
}
Трудоемкость данного алгоритма составляет
.
Сильно связные компоненты
Задан ориентированный граф
. Для него могут быть определены компоненты трех типов, в зависимости от условий связности произвольной пары вершин
и
, принадлежащих одной компоненте:
1. Слабо связные – из
и
существует маршрут, если все дуги
заменить на ребра.
2. Односторонние – существует либо путь из
в
, либо из
в
, либо оба вместе.
3. Сильно связные – существуют пути из
в
и обратно (т.е. цикл, образованный дугами).
Слабо связные компоненты можно найти поиском в глубину на графе, в котором все дуги заменены ребрами.
Односторонние можно выделить с помощью построения и объединения глубинных остовных деревьев со всеми возможными корневыми вершинами.
На множестве вершин
можно определить отношение эквивалентности:
пути из
в
и обратно.
Пусть
– классы эквивалентности, а
– множества дуг, соединяющих вершины
. Тогда
– сильно связные компоненты
.
1-й алгоритм выделения ССК (1 поиск в глубину, Ахо)
При поиске в глубину на орграфе образуются 4 типа дуг:
1. Древесные.
2. Прямые – от предков к потомкам (не древесные).
3. Обратные – от потомков к предкам.
4. Поперечные – соединяют вершины, не являющиеся предками и потомками друг друга.
(на рисунке: 1 – синие, 2 – зеленые, 3 – красные, 4 – розовые; цифры – номера вершин в порядке обхода в глубину).
![]() |
Лемма: если
– поперечная дуга, то
.
Док-во:
1.
ведет в уже пройденную вершину
, иначе эта дуга была бы древесной.
2. Если проверяется дуга
, то поиск в глубину из вершины
пока не закончен (необходимо проверить все возможные пути из
). Пусть
была пройдена раньше, чем
, т.е.
. Это возможно только в том случае, когда
лежит на некотором пути из
(состоящем из древесных дуг), но тогда дуга
– прямая.
Основная лемма об ССК (без док-ва, Ахо):
Пусть
– ССК, а
– глубинный остовный лес на
. Тогда вершины
вместе с дугами, входящими в
, образуют дерево.
Следствие:
- любые вершины
имеют общего предка;
- предок
с минимальным номером, присвоенным при поиске, является корнем дерева (и однозначно определяет дерево).
Если перенумеровать
в том порядке, в каком для
заканчивается поиск в глубину (т.е., сначала
, затем
,…), то выполняется следующая
Лемма:
граф
состоит из вершин, являющихся потомками
и не принадлежащих ни одному из
.
Практическое следствие: при поиске в глубину будем помещать вершины в стек в порядке их обхода; при обнаружении корня
какой-либо ССК вытолкнем из стека все вершины до
включительно – все они принадлежат одной ССК.
Каждая вершина принадлежит только одной ССК, но могут существовать дуги, соединяющие вершины из разных ССК.
Рассмотрим ССК
и
, где
выделяется раньше, чем
.
![]() |
Варианты соединений:
1.
обе дуги –
и
. Невозможен, т.к. в этом случае
образуют одну ССК.
2.
только дуга
. Поиск для
не закончится, пока не будут проверены все возможные пути из ее вершин, в том числе и включающие дугу
. Поэтому
не может быть выделена раньше, чем
.
3.
только дуга
. Если по этой дуге мы впервые приходим в
, то
будет древесной дугой, а
корнем дерева
. В противном случае ССК
уже выделена (ее вершины вытолкнуты из стека) и поперечную дугу
вообще не нужно обрабатывать.
Пути, состоящие из древесных дуг, ведут от предков к потомкам в дереве.
Прямые дуги просто дублируют участки путей на дереве, поэтому их можно вообще не рассматривать.
Пути от потомков к предкам в дереве определяют обратные и, потенциально, поперечные дуги.
Пусть
поперечная дуга
, где
– потомок
или
. Если
обратная дуга
, где
– потомок
или
, а
– предок
, то вершины
принадлежат одной ССК.
![]() |
Для каждой вершины
нужно найти ее предка
, достижимого из
по путям, содержащим обратные и поперечные дуги, и имеющего минимальный номер
– это корень ССК, содержащей
.
Введем функцию «нижняя связь»
:
поперечная или обратная дуга из
или некоторого потомка
в вершину
, причем корень ССК, к-рая содержит
, является предком
.
Лемма:
явл-ся корнем ССК
.
В приведенной ниже программе используется стек для выделения вершин ССК.
Значения
вычисляются рекурсивно в массиве LCon.
Для того, чтобы не учитывать поперечные дуги, соединяющие разные ССК, исп-ся массив InS: InS[v] = 1, если v – в стеке.
Подготовка массивов, выделение корней глубинных остовных деревьев, вызов рекурсивной функции поиска в глубину:
sccomp = 0; newnum = 0;
for (k = 1; k <= n; k++) {
Old[k] = 0; InS[k] = 0;
}
for (k = 1; k <= n; k++)
if (Old[k] == 0) search_SCC(k);
Поиск в глубину с выделением ССК:
void search_SCC(int v) { Old[v]=1;
LCon[v]=NV[v]=++newnum; push(v); InS[v]=1;
for (w
L[v])
if (Old[w] == 0) { // (v,w) - древесная
search_SCC(w);LCon[v]=min(LCon[v],LCon[w]);
}
else if (NV[w]<NV[v] && InS[w]==1) // О\П
LCon[v] = min(LCon[v], NV[w]);
if (LCon[v] == NV[v]) { sccomp++; //v–корень ССК
do x = pop(); InS[x] = 0;
сохранить_вершину(x, sccomp);
while (x!= v);
}
}
Трудоемкость данного алгоритма составляет
.
Следующий рисунок иллюстрирует результаты работы алгоритма поиска в глубину с расчетом новых номеров NV и значений LCon (красные цифры).
При вычислении значений LCon не учитывались поперечные дуги, ведущие в вершины уже выделенных ССК (удаленные из стека).

2-й алгоритм выделения ССК (Кормен)
Данный алгоритм использует поиск в глубину дважды.
При 1-м поиске для каждой вершины вычисляется ее новый номер, но не в порядке прихода в нее (
), а в порядке завершения поиска (завершения проверки всех дуг, исходящих из данной вершины) –
.
После этого исходный граф
«транспонируется», т.е. в нем изменяется направление всех дуг. В результате получается граф
:
.
Далее проводится поиск в глубину на
, причем вершины выбираются в порядке убывания значений
, вычисленных при 1-м поиске. Полученные на данном шаге глубинные остовные деревья соответствуют сильно связным компонентам исходного графа
.
Поиск в глубину с вычислением значений LV:
lastnum = 0; // тек. значение номера
for (k = 1; k <= n; k++) Old[k] = 0;
for (k = 1; k <= n; k++)
if (Old[k] == 0) search(k);
void search(int v) {
Old[v] = 1;
for (w
L[v])
if (Old[w] == 0) search(w);
LV[v] = ++lastnum;
}
Иллюстрация работы предыдущего алгоритма (древесные дуги синие, для вершин указаны значения
):
![]() |
Исходный порядок выбора вершин:
.
Порядок первого прихода в вершину:
.
Пусть
– произвольная пара вершин
. Очевидно, что
достижима из
на графе
, тогда и только тогда, когда
достижима из
на
. Поэтому графы
и
содержат одни и те же сильно связные компоненты (точнее, мн-ва вершин, образующих ССК).
При поиске в глубину все вершины одной ССК образуют одно дерево, т.е. имеют одного общего предка (основная лемма).
Дерево любой ССК является поддеревом некоторого глубинного дерева, и каждая вершина графа принадлежит какой-либо ССК. Поэтому корни глубинных деревьев являются также корнями ССК.
Выберем вершину с максимальным значением 
:
.
– последняя вершина
, в к-рой закончился поиск в глубину, следовательно,
– это корень некоторого глубинного дерева (и корень некоторой ССК). Любая вершина этого дерева достижима из
на графе
.
Проведем поиск в глубину на «транспонированном» графе
, начав с вершины
. Очевидно, что
будет корнем некоторого глубинного дерева со мн-вом вершин
.
Любая вершина
будет достижима из
на
, т.е.
достижима из
на исходном графе
.
Предположим, что
такая вершина
, что
недостижима из
на
.
Тогда
и
должны принадлежать разным глубинным деревьям
и
на графе
.
является максимальным среди значений
, следовательно, построение
началось и закончилось раньше, чем
.
Но построение
не может закончиться, пока не проверены связи всех его вершин, а
достижима из
, т.е.
должна принадлежать
– противоречие.
Следовательно, все вершины
достижимы из
и наоборот, т.е.
– это мн-во вершин одной ССК.
При поиске в глубину на
будем помещать все пройденные вершины в стек и отмечать, как проверенные.
После выделения
(проверки всех связей вершины
) нужно вытолкнуть полученные вершины из стека и продолжить поиск в глубину, начиная с непроверенной вершины, имеющей максимальное значение
. Очередное выделенное дерево будет содержать все вершины следующей ССК.
Таким образом, от графа будут последовательно «отщепляться» сильно связные компоненты.
Общая трудоемкость алгоритма составляет
, если используются списки смежных вершин, или
– для матрицы смежности.
Иллюстрация работы алгоритма поиска в глубину на графе
. Вершины выбирались в порядке убывания значений
:
.

Выделено 3 ССК со мн-вами вершин:
,
,
.






