На этом шаге мы рассмотрим алгоритм отыскания блоков и точек сочленения.
Пусть G = (V, Е) - обыкновенный связный граф. Определено отношение ≈ на множестве ребер Е графа G:
e ≈ f ⇔ e=f или e, f лежат на некотором цикле.
Это отношение является эквивалентностью, причем каждый его класс совпадает с множеством ребер некоторого блока.
Предположим, что в графе G из некоторой вершины v0 проведен поиск в глубину. Поиск в глубину строит d-дерево (V, Т) и массив num, состоящий из номеров, которые присваиваются вершинам. Получим сначала в терминах d-дерева признак того, что даннная вершина является точкой сочленения.
Аналогично проверяется
Пусть v - произвольная вершина. Обозначим через V поддерево d-дерева с корнем v. Для произвольного непустого множества X ⊆ V через VB(X, v) будем обозначать множество всех таких вершин w, что w > v и для некоторой вершины х ∈ X существует обратное ребро xw. Если X одноэлементно, т.е. X = {x}, то вместо VB({x}, v) будем писать VB(x, v).
Обратно, пусть w ∈ VB(s, v). Тогда w ≥ t и существует обратное ребро xw, причем х ≤ s. Ясно, что х - потомок вершины w в d-дереве. Поэтому существует простая (w, x)-цепь P, причем P содержит ребра vt и vs. Добавляя к цепи P ребро xw, получим требуемый цикл.
Предположим, что v - корень d-дерева. Тогда х, у, очевидно, являются потомками v. В силу леммы 2 существуют сыновья s1, s2 вершины v такие, что vs1 ≈ vx, vs2 ≈ vy. Поскольку vx ≈ vy, имеем vs1 ≈ vs2. Отсюда, в частности, следует, что s1 ≈ s2.
Пусть v не является корнем d-дерева, и t - отец вершины v. Нетрудно понять, что либо vt ≈ vx, либо vt ≈ vy. Без ограничения общности можно считать, что vt ≈ vx. Применение леммы 1 показывает, что х - потомок вершины v. В силу леммы 2 существует такой сын s вершины v, что vx ≈ vs. Ясно, что vt ≈ vs, и из леммы 3 следует пустота множества VB(s, v).
Проверим обратное утверждение. Предположим сначала, что выполнено условие (1), т.е. вершина v - корень, имеющий двух сыновей s1 и s2. Убедимся, что ребра vs1 и vs2 принадлежат разным блокам. Допустим, рассуждая от противного, что ребра vs1 и vs2 лежат в одном блоке. Тогда существует цикл C, содержащий эти ребра. Если С имеет вид u1, ..., up, u1, то можно считать, что u1 = v, u2 = s1, up = s2. Поскольку s2 не является потомком s1, найдется наименьшее число q>2 такое, что uq не является потомком s1. Ясно, что q < p и ребро uq-1uq является обратным. Следовательно, uq - предок вершины s1, а потому uq = v. Вершина v содержится в C более одного раза, что противоречит определению цикла.
Пусть выполнено условие (2). Обозначим через t отца вершины v. Поскольку VB(s, v) пусто, из леммы 3 получаем, что ребра vs, vt лежат в разных блоках. Понятно, что v - общая вершина этих блоков, следовательно, v - точка сочленения.
Пусть X - произвольное множество вершин графа G. Обозначим через num(X) множество {num[v] | v ∈ X}.
На множестве V вершин графа G рассмотрим следующую функцию
L[v] = min num({v} ∪ VB(v, v)).
Функция L обладает двумя важными свойствами:
VB(ŝ, v) ⊆ VB(ŝ, s), VB(ŝ, s) \ VB(ŝ, v) ⊆ {v}.
Предположим, что VB(s, v) = 0. Тогда VB(ŝ, s) ⊆ {v}. Отсюда следует, что
{s}∪VB{ŝ, s) ⊆ {ŝ, v}.
Вычисляя минимум номеров вершин, входящих в левую и правую части этого включения, получим
L[s] > min(num[s], num[v]) = num[v].
Обратно, если множество VB(ŝ, v) непусто, то существует w ∈ VB(ŝ, v). Ясно, что num[w] < num[v]. Поскольку VB(ŝ, v) ⊆ VB(ŝ, s), имеем
{s} ∪ VB(ŝ, v) ⊆ {s} ∪ VB{ŝ, s).
Отсюда следует, что
num[v] > num[w] ≥ min num({s} ∪ VB(ŝ, v)) ≥ L[s].
Следовательно, L[s] < num[v].
Из лемм 4 и 5 вытекает следующее утверждение.
L[v] = min (num[v], L[s1], ..., L[sp], num(VB(v, v))), где s1, ..., sp - все сыновья вершины v.
Кроме того, {v} ∪ VB(ŝi, v) = {v} ∪ VB(ŝi, si) при 1 ≤ i ≤ p. Следовательно,
{v} ∪ VB(v, v) = {v} ∪ VB(ŝ1 s1) ∪ ... ∪ VB{ŝp, sp) ∪ VB(v, v).
Обозначим через W множество, стоящее в правой части этого равенства. Так как v∈W и num[v] < num[si] (1 ≤ i ≤ р), выполнено равенство
min num(W) = min num{W ∪ {s1, ..., sp}).
Теперь нетрудно видеть, что
L[v] = min (num[v], L[s1], ..., L[sp], num(VB(v, v))).
Дадим формальное описание алгоритма нахождения блоков и точек сочленения.
Алгоритм 2.
1.procedure BiComp(v) 2. begin 3. num[v]:= i; L[v]:= i; i:=i + 1; 4. for u ∈ list[u] do 5. if num[u] = 0 then 6. begin 7. SE <= vu; father[u]:= v; BiComp(u); 8. L[v]:= min(L[v], L[u]); 9. if L[u] ≥ num[v] then 10. {получить новый блок; для этого из стека SE надо вытолкнуть все ребра вплоть до ребра vu} 11. end 12. else if num[u] < num[v] and u ≠ father[v] then 13. begin 14. SE <= vu; L[v]:= min(L[v], num[u]); 15. end 16. end; 17. begin 18. i:= 1; SE:= nil; father[v0]:= 0; 19. for v ∈ V do num[v]:= 0; 20. BiComp(v0) 21. end.
Процедура BiComp(v) является является модификацией процедуры DFS(v) из алгоритма 1. В момент завершения работы процедуры BiComp(v) значение L[v] уже вычислено. Вычисление L[v] производится по формуле из леммы 6. В самом деле, в строке 3 выполняется присваивание L[v]:= num[v]. Далее в строке 8 учитываются значения функции L для каждого из сыновей вершины v. Наконец, в строке 14 находится минимальное из двух чисел: текущего значения L[v] и num[u], где u - очередной элемент из множества VB(v, v).
Необходимое и достаточное условие для вершины быть точкой сочленения, сформулированное в теореме 2, проверяется в строке 9. Разумеется, это условие нельзя применять к корневой вершине vq. Чтобы узнать, является ли vq точкой сочленения, нужно подсчитать количество сыновей этой вершины в d-дереве.
Для нахождения множества ребер очередного блока алгоритм 2 использует стек SE, элементами которого являются ребра графа G.
Пусть q > 1. Обозначим через u1, v1 ту пару вершин, для которой неравенство в строке 9 выполнится в первый раз. Ясно, что v1 - точка сочленения графа G. К этому моменту процедура BiComp(u1) завершилась и точек сочленения не обнаружила. Поэтому после проверки условия в L[u1] ≥ num[v1] из стека SE будут удалены ребра некоторого блока В. Теперь можно считать, что алгоритм обрабатывает граф G1 составленный из всех блоков графа G, отличных от В. К графу G1 применимо предположение индукции, поэтому блоки, отличные от B, также будут найдены правильно.
На следующем шаге мы рассмотрим алгоритм отыскания компонент сильной связности в орграфе.