Означення 1. Вершину v зв'язаного неорієнтованого графа G називають точкою сполучення або вершиною, що розрізає (розділяє) граф, якщо вилучення цієї вершини разом з інцендентними їй ребрами (що мають її за кінець) призводить до порушення зв'язності графа.
Теорема 1. Нехай орієнтоване дерево T є остовним (каркасним) деревом нерієнтованого графа G(V, E), побудоване пошуком у глибину, і вершини a, b сполучено ребром графа G(V, E). Тоді або a є нащадком b, або b є нащадком а.
Теорема 2. Нехай орієнтоване дерево T є остовним (каркасним) деревом нерієнтованого графа G(V, E), побудоване пошуком у глибину. Вершина a є точкою сполучення графа G(V, E) тоді й лише тоді, коли вершина a:
або є коренем дерева T, що має більше одного безпосереднього нащадка;
або не є коренем дерева T, та існує такий нащадок a, який разом зі своїми нащадками не сполучено жодним ребром у графі G(V, E) з жодним з предків a у дереві T.
Доведення.
Нехай вершина a є коренем дерева T. Тоді для довільних двох вершин, відмінних від a, існує ланцюг (шлях без врахування напряму дуг дерева T), що проходить через вершину a:
якщо корінь a має лише одного безпосереднього нащадка b, то, вилучивши ланки (b; a) і (a; b), отримаємо ланцюг, що не проходить через a;
якщо корінь a має щонайменше два різні безпосередні нащадки, то довільний ланцюг, що сполучає їх, проходить через корінь a (див. пошук у глибину, починаючи від одного з таких нащадків).
Якщо a не є коренем побудованого дерева, позначимо через p безпосереднього предка a.
Нехай для деякого нащадка a немає жодного ребра графа G(V, E), що сполучає його чи його нащадків з p. Тоді всі шляхи у графі G(V, E), що ведуть від цього нащадка a до p, проходять через a. У цьому випадку вершина a є точкою сполучення, бо її вилучення разом з інцендентними їй ребрами робить неможливим сполучення шляхом вказаного нащадка a з p.
Нехай для всіх нащадків a існує ребро графа G(V, E), що сполучає цього нащадка або котрогось з наступних нащадків з вершиною p або її предком. Тоді вилучення вершини a разом з інцендентними їй ребрами:
не порушує можливості сполучення шляхами вершин графа G(V, E) без a та її нащадків;
не порушує можливості сполучення шляхами нащадків a з p, а через p — з іншими вершинами графа G(V, E), відмінними від a.
У цьому випадку вершина a не є точкою сполучення.
У початковий момент виконання поданого далі алгоритму:
У процесі виконання алгоритму обчислюємо такі величини:
g (v) — порядковий номер у порядку приєднання вершини v до дерева T, яке будуємо пошуком у глибину;
f (w) — найменший порядковий номер у порядку приєднання вершини x до дерева T, при якій існує ребро, що не перетворене на дугу дерева T і сполучає x або з w, або з нащадком w.
Нехай вершина w є безпосереднім нащадком вершини v і справджується така нерівність:
g (v) < f (w). Тоді немає ребра початкового графа, що не перетворене на дугу дерева T і сполучає нащадка w з безпосереднім предком v. Згідно з доведеною теоремою вершина v є точкою сполучення у цьому випадку.
За параметр v для першого виклику вибираємо довільну вершину заданого графа, наприклад з номером 1.
Рекурсивний алгоритм пошуку точок сполучення — РАПТС(v)
Змінюємо мітку v на «використана».
Збільшуємо величину i на 1.
Надамо величини f (v) = g (v) = i.
Перебираємо всі суміжні з v вершини w. Якщо w має мітку «нова», робимо таке:
долучаємо дугу (v; w) до дерева, вважаючи v безпосереднім предком вершини w;
виконуємо РАПТС(w);
якщо g (v) ≤ f (w) і v не є коренем дерева, то оголошуємо вершину v точкою сполучення;
якщо v є коренем дерева, то збільшуємо кількість виявлених безпосередніх нащадків кореня на 1. Якщо ця кількість більша ніж 1, оголошуємо корінь точкою сполучення (останню дію можна виконати і по завершенню виконання алгоритму першого виклику);
замінюємо величину f (v) на min( f (v), f (w)).
Інакше, тобто якщо вершина w має мітку «використана», а v не є безпосереднім предком w, замінюємо величину f (v) на min( f (v), g (w)).
Подамо приклад програми мовою Turbo Pascal 7.0 з коментарями щодо структури вхідних і вихідних файлів.
{$I+}{$N+} {Верхнi межi:} const nv_max=2000; {кiлькостi вершин} ne_max=4000; {кiлькостi ребер } type pointe=^edge; edge=record {Ребро з вершини:} v: word; {сумiжна вершина} n: pointe end; {наступне ребро, iнцендентне данiй вершинi} pointer=array[1..nv_max] of pointe; words=array[1..nv_max] of word; bool=array[1..nv_max] of boolean; var p, {Предки у деревi} f,g:^words; {Функцiї f i g} n0, {Вказiвники на перше й} n:^pointer;{останнє iнцендентнi ребра} u, {Чи використано вершину} j:^bool;{Чи вершина -точка сполучення} e: pointe; nv, {Кiлькiсть вершин} ne, {Кiлькiсть ребер} v, {Номер вершини} i, {Номер появи вершини у деревi} j1,j2: word; {Номери кiнцiв ребра} o: text; {Файл даних} {Рекурсивний алгоритм пошуку точок сполучення} procedure STEP (v: word); var stop: boolean; w: word; BEGIN inc(i); u^[v]:=true; f^[v]:=i; g^[v]:=i; n^[v]:=n0^[v]; stop:=false; REPEAT w:=n^[v]^.v; if u^[w] and (w <> p^[v]) then begin if g^[w] < f^[v] then f^[v]:=g^[w] end else if not u^[w] then begin p^[w]:=v; STEP(w); if (v <> j1) and (g^[v] <= f^[w]) then j^[v]:=true else if (v= j1) then inc(j2); if f^[v] > f^[w] then f^[v]:=f^[w] end; if n^[v]^.n<>nil then n^[v]:=n^[v]^.n else stop:=true; UNTIL stop; END; BEGIN nv:=0; new(n0); new(n); for v:=1 to nv_max do n0^[v]:=nil; assign(o,'JOINT.DAT'); reset(o); {Зчитування рядкiв вхiдного файлу, кожний з яких мiстить лише номери кiнцiв ребра} REPEAT inc(ne); readln(o,j1,j2); if j1 > nv then nv:=j1; if j2 > nv then nv:=j2; new(e); if n0^[j1]=nil then n0^[j1] :=e else n^[j1]^.n:=e; n^[j1]:=e; e^.v:=j2; e^.n:=nil; new(e); if n0^[j2]=nil then n0^[j2] :=e else n^[j2]^.n:=e; n^[j2]:=e; e^.v:=j1; e^.n:=nil; UNTIL seekeof(o); close(o); new(p); new(u); new(j); new(f); new(g); for v:=1 to nv do begin u^[v]:=false; j^[v]:=false end; j1:=1; {Корiнь дерева} j2:=0; {Кiлькiсть безпосереднiх нащадкiв кореня} p^[j1]:=0; i:=0; STEP(j1); j^[j1]:=j2 > 1; {Запис у вихiдний файл у порядку зростання номерiв вершин, що є точками сполучення} assign (o,'JOINT.RES'); rewrite(o); v:=0; repeat inc(v) until j^[v] or (v=nv); if j^[v] then write(o,v); j1:=v+1; for v:=j1 to nv do if j^[v] then write(o,' ',v); writeln(o); close(o); END.>