Означення 1. Запровадимо такі поняття й позначення.
Для довільної вершини v:
Мережею називають орієнтований граф (G, V, E ) разом з ваговою функцією с: E → N (з множини ребер E у множину натуральних чисел N) та вершинами a, z, що мають відповідно нульові степені входу й виходу:
| in (a) | = 0 = | out (z) | — стислий запис останніх двох висловлювань.
Потоком у мережі (G, V, E) називають функцію f: E → N0 (з множини ребер E у множину невід'ємних цілих чисел N0), при якій:
| ∑ | f (e) = | ∑ | f (e). |
| e ∈ out (v) | e ∈ in (v) |
Мережа є моделлю водогону, у якій:
c(e) — максимальна швидкість транспортування у напрямку, поданому дугою e;
f (e) — (реальна) швидкість транспортування у напрямку, поданому дугою e.
Рівність сум в означенні потоку описує відсутність накопичення рідини на проміжних станціях, поданих вершинами графа.
Теорема 1. Для довільного потоку f маємо:
| ∑ | f (e) = | ∑ | f (e), |
| e ∈ out (a) | e ∈ in (z) |
тобто сумарні потоки через джерело a і стік z збігаються.
Доведення. Нехай S — довільна підмножина V, що містить a, але не містить z, T = V \ S. Додавши почастинно рівності з означення потоку за всіма вершинами S, відмінними від a, отримаємо еквівалентні рівності:
| ∑ | ∑ | f (e) = | ∑ | ∑ | f (e); |
| v ∈ S \ {a} | e ∈ out (v) | v ∈ S \ {a} | e ∈ in (v) |
| ∑ | ∑ | f (e) – | ∑ | ∑ | f (e) = 0; |
| v ∈ S \ {a} | e ∈ out (v) | v ∈ S \ {a} | e ∈ in (v) |
| ∑ | ∑ | f (e) – | ∑ | ∑ | f (e) = | ∑ | f (e). |
| v ∈ S | e ∈ out (v) | v ∈ S | e ∈ in (v) | e ∈ out (a) |
Позначимо через (S; T) множину дуг, спрямованих з S у T, через (T; S) — множину дуг, спрямованих з T в S. У лівій частині останньої рівності, якщо обидві вершини дуги e належать до S, то потік по e буде враховано в обох сумах, а відповідні доданки взаємно знищаться:
| ∑ | f (e) – | ∑ | f (e) = | ∑ | f (e). |
| e ∈ (S; T) | e ∈ (T; S) | e ∈ out (a) |
Отже, для довільної множини вершин S, що містить a і не містить z, різниця потоків, які виходять з S і входять в S дорівнює потоку, що витікає з a.
Виберемо: S = V \{z}, T = {z}, при яких:
(T; S) = ∅ — порожня множина, а відповідна сума у лівій частині останньої рівності дорівнює 0;
(S; T) = in (z) — множина дуг, спрямованих у z.
Врахувавши все це, маємо:
| ∑ | f (e) = | ∑ | f (e), |
| e ∈ in (z) | e ∈ out (a) |
що й потрібно було довести.
Означення 2. Запровадимо такі поняття й позначення:
Величиною потоку f називають величину:
| ∑ | f (e) = | ∑ | f (e), |
| e ∈ out (a) | e ∈ in (z) |
яку позначають через val ( f ).
Нехай S — довільна підмножина V, T = V \ S. Перерізом (S; T ) називають множину дуг, спрямованих з S у T. Якщо S містить джерело a і T містить стік z, то такий переріз називають a – z перерізом.
Величину
| С(S, T ) = | ∑ | c(e) |
| e ∈(S; T) |
називають пропускною спроможністю перерізу.
Потік fmax називають максимальним, якщо його величина не менша від величини будь-якого можливого потоку f у мережі: val ( f ) ≤ val ( fmax).
a – z переріз (S; T ) називають мінімальним перерізом, якщо С(S, T ) не перевищує пропускну спроможність довільного a – z перерізу.
Теорема 2. Нехай f — довільний потік у мережі S — довільна підмножина V, що містить джерело a і не містить стік z, T = V \ S. Тоді val ( f ) ≤ С(S, T ).
Доведення. Як встановлено вище, величина потоку val ( f ) дорівнює такій різниці:
| ∑ | f (e) – | ∑ | f (e) ≤ | ∑ | f (e) ≤ | ∑ | c (e) = С(S, T). |
| e ∈ (S; T) | e ∈ (T; S) | e ∈ (S; T) | e ∈ (S; T) |
Наслідок 1. Якщо val ( f ) = С(S, T ) при деяких потоці f та a – z перерізі (S; T ), то f — максимальний потік, (S, T ) — мінімальний переріз.
Наслідок 2. Рівність val ( f ) = С(S, T ) справджується тоді й лише тоді, коли:
| ∀ e ∈ (S; T ) f (e) = c(e); |
|
∀ e ∈ (T; S ) f (e) = 0. |
Пошук максимального потоку здійснимо шляхом збільшення потоку, починаючи з нульового, таким чином. Формуємо ланцюг — послідовність дуг, що сполучають вершини a – z без урахування напряму дуг. Для кожного такого ланцюга збільшуємо загальний потік, якщо це можливо, змінивши потік лише вздовж ланок ланцюга:
збільшуємо потік вздовж дуг, спрямованих вздовж напрямку руху від a до z без виходу за межі пропускної спроможності;
зменшуємо потік вздовж дуг, спрямованих проти напрямку руху від a до z без виходу за область невід'ємних чисел.
Кожній вершині поставимо у відповідність впорядковану пару, в якій:
Алгоритм Форда-Фалкерсона
(Ford-Fulkerson algorithm)
Встановлюємо, що кожної вершини її попередник невизначений і резерв від'ємний (невизначений). Означимо резерв для вершини a як ∞, щоб не обмежувати резерв решти вершин. Покладемо A = {a}.
Якщо A — порожня множина, то припиняємо виконання алгоритму, бо потік максимізовано. Інакше вибираємо довільну вершину v з множини A і вилучаємо її з A.
Розглянемо всі вершини w, що задовольняють такі умови:
Для кожної такої вершини w обчислимо мінімум з різниці с((v; w)) – f ((v; w)) та резерву вершини v. Якщо обчислена на попередньому кроці величина більша за резерв вершини w, то:
Розглянемо всі вершини w, що задовольняють такі умови:
Для кожної такої вершини w обчислимо мінімум з f ((v;w)) та резерву вершини v. Якщо обчислена на попередньому кроці величина більша за резерв вершини w, то:
Якщо резерв і попередник вершини z не визначено, то переходимо до виконання пункту 2.
Якщо резерв і попередник вершини z визначено, то:
використовуючи попередників вершин, відновлюємо ланцюг a – z;
для кожної дуги ланцюга, орієнтація якої збігається з напрямком руху від a до z вздовж ланцюга, збільшуємо потік на визначений резерв вершини z;
для кожної дуги ланцюга, орієнтація якої протилежна до напрямку руху від a до z вздовж ланцюга, зменшуємо потік на визначений резерв вершини z;
переходимо до виконання пункту 1.
Теорема 3. Алгоритм Форда-Фалкерсона будує максимальний потік у мережі.
Доведення. Позначимо через S множину всіх тих вершин, для яких визначено резерв під час останнього проходження алгоритму, T = V \ S. Множина S непорожня, бо містить вершину a. При цьому:
якщо e — довільна дуга з (S; T ), то f (e) = c(e), бо інакше для кінця дуги e визначено попередника;
якщо e — довільна дуга з (T; S), то f (e) = 0, бо інакше для кінця початку e визначено попередника.
Згідно з наслідками 1–2 f є максимальним потоком, а (S; T ) — мінімальним перерізом.
Наслідок 3. Потік f у мережі є максимальним тоді й лише тоді, коли існує переріз (S; T), при якому
Подамо приклад програми мовою Turbo Pascal 7.0 з коментарями щодо стуктури вхідних і вихідних даних.
{$I+}{$N+} {Верхнi межi:}
const nv_max=2000; {кiлькостi вершин}
l_max=2147483647; {потоку}
type pointe=^edge;
edge=record {Дуга:}
v, {початок}
w: word; {кiнець}
c, {пропускна спроможнiсть}
f: longint; {потiк}
{вказiвник на наступну дугу}
b, {з тим самим початком}
e: pointe end; {... кiнцем}
pointa=^forsetA;
forsetA=record {Елемент списку A}
v: word; {вершина}
n: pointa end; {наступний елемент}
pointer=array[1..nv_max] of pointe;
longers=array[1..nv_max] of longint;
var p:^pointer; {Дуги з попередниками}
r:^longers; {Резерви вершин}
n0, {Вказiвники на першу й}
n, {останню дуги з даним початком}
m0, {Вказiвники на першу й}
m:^pointer; {останню дуги з даним кiнцем}
A1, {Перший елемент списку A}
A , {Поточний елемент списку A}
Aw: pointa; {Елемент w списку A}
e: pointe; {Ребро}
nv, {Кiлькiсть вершин}
aa, {Джерело}
z, {Стiк}
v,w: word; {Дуга: початок, кiнець}
c, {i пропускна спроможнiсть}
rw: longint; {Можливий резерв вершини}
o: text; {Файл даних}
stop: boolean; {Потрiбно припинити цикл}
{Долучення вершини w до множини A}
procedure includew; BEGIN
if A1=nil then begin
new(A1); {A порожня}
A1^.v:=w;
A1^.n:=nil end else begin
A:=A1; {A не порожня}
stop:=false;
while (A^.v<>w) and (A^.n<>nil)do A:=A^.n;
if A^.v<>w then begin
new(Aw); {Якщо w не належить до A}
A^.n:=Aw;
Aw^.v:=w;
Aw^.n:=nil end end END;
{Запис у вихiдний файл даних про потiк:
у кожному рядку - початок i кiнець дуги
й потiк вздовж неї. Дуги перераховано у
порядку зростання початку, а для сталого
початку - у тому самому порядку, як вони
зустрiчалися у вхiдному файлi}
procedure output; BEGIN
assign (o,'FLOW.RES'); rewrite(o);
for v:=1 to nv do begin
if n0^[v]<>nil then begin
e:=n0^[v];
stop:=false;
repeat writeln(o,v,' ',e^.w,' ',e^.f);
if e^.b=nil then stop:=true
else e:=e^.b
until stop end end;
close(o); halt END;
BEGIN
nv:=0; new(n0); new(n); new(m0); new(m);
for v:=1 to nv_max do begin
n0^[v]:=nil;
m0^[v]:=nil end;
assign(o,'FLOW.DAT'); reset(o);
{Зчитування рядкiв вхiдного файлу, кожний
з яких мiстить: початок дуги, кiнець дуги
i пропускну спроможнiсть}
REPEAT readln(o,v,w,c);
if v>nv then nv:=v;
if w>nv then nv:=w;
new(e);
e^.v:=v;
e^.w:=w;
e^.c:=c;
e^.f:=0;
e^.b:=nil;
e^.e:=nil;
{Облiк дуг з початком v}
if n0^[v]=nil then n0^[v] :=e
else n^[v]^.b:=e;
n^[v]:=e;
{Облiк дуг з кiнцем w}
if m0^[w]=nil then m0^[w] :=e
else m^[w]^.e:=e;
m^[w]:=e;
UNTIL seekeof(o); close(o);
new(p); new(r); new(A1);
aa:=1; {Джерело}
z:=nv; {Стiк}
REPEAT {Крок 1}
for v:=1 to nv do begin
p^[v]:=nil;
r^[v]:=-1 end;
r^[aa]:=l_max;
A1^.v:=aa;
A1^.n:=nil;
REPEAT if A1=nil then output else begin
{Кроки 2-5}
v:=A1^.v; {Вилучення першого елемента
зi списку A}
if A1^.n=nil then A1:=nil
else begin
A:=A1;
A1:=A1^.n;
dispose(A) end;
n^[v]:=n0^[v];
stop:=false;
repeat {Розгляд дуг (v;w)}
w:=n^[v]^.w;
if n^[v]^.f < n^[v]^.c then begin
rw:=n^[v]^.c-n^[v]^.f ;
if r^[v] < rw then rw:=r^[v];
if r^[w] < rw then begin
r^[w]:=rw;
p^[w]:=n^[v];
if w<>z then includew end end;
if n^[v]^.b<>nil then n^[v]:=n^[v]^.b
else stop:=true;
until stop;
if m0^[v]<>nil then begin
m^[v]:=m0^[v];
stop:=false;
repeat {Розгляд дуг (w;v)}
w:=m^[v]^.v;
if 0 < m^[v]^.f then begin
rw:=m^[v]^.f;
if r^[v] < rw then rw:=r^[v];
if r^[w] < rw then begin
r^[w]:=rw;
p^[w]:=m^[v];
if w<>z then includew end end;
if m^[v]^.e<>nil then m^[v]:=m^[v]^.e
else stop:=true;
until stop end end
UNTIL r^[z]>0;
w:=z; {Крок 6}
repeat e:=p^[w];
if w =e^.w then begin
w:=e^.v;
e^.f:=e^.f+r^[z] end else begin
e^.f:=e^.f-r^[z];
w:=e^.w end
until w=aa;
UNTIL A1=nil; output END.
Подана далі програма без динамічного розподілу пам'яті не увійшла до журнальної публікації, але може виявитися користною для першого знайомства з прикладом програмної реалізації.
const nv_=555; ne_=555;
var b: array[0..nv_] of boolean;
p,r,ep: array[1..nv_] of word;
v1,v2,c,f,e1,e2,ne1,ne2: array[0..ne_] of word;
a,z, nv,ne,j,k,v,w,d: word;
o: text;
label l1,l2;
procedure fin;
BEGIN
d:=0;
for j:=ne1[a-1]+1 to ne1[a] do d:=d+f[e1[j]];
writeln(o,d);
for j:=1 to ne do writeln(o,f[j]);
close(o);
halt
END;
function lt1(i,j: word): boolean;
BEGIN
lt1:=(v1[i]<v1[j]) or (v1[i]=v1[j]) and (v2[i]<v2[j]);
END;
procedure QSort1(l,r: word);
var x,y,i,j: word;
BEGIN
x:=e1[l+random(r-l+1)];
i:=l; j:=r;
while i<=j do begin{1}
while lt1(e1[i],x) do i:=i+1;
while lt1(x,e1[j]) do j:=j-1;
if i<=j then begin{2}
y:=e1[i];
e1[i]:=e1[j];
e1[j]:=y;
i:=i+1;
j:=j-1 end{2}
end{1};
if l<j then qsort1(l,j);
if i<r then qsort1(i,r);
END;
function lt2(i,j: word): boolean;
BEGIN
lt2:=(v2[i]<v2[j]) or (v2[i]=v2[j]) and (v1[i]<v1[j]);
END;
procedure QSort2(l,r: word);
var x,y,i,j: word;
BEGIN
x:=e2[l+random(r-l+1)];
i:=l; j:=r;
while i<=j do begin{1}
while lt2(e2[i],x) do i:=i+1;
while lt2(x,e2[j]) do j:=j-1;
if i<=j then begin{2}
y:=e2[i];
e2[i]:=e2[j];
e2[j]:=y;
i:=i+1;
j:=j-1 end{2}
end{1};
if l<j then qsort2(l,j);
if i<r then qsort2(i,r);
END;
BEGIN
assign(o,'flow.in');
reset(o);
readln(o,a,z);
ne:=0;
nv:=0;
repeat inc(ne);
readln(o,v1[ne],v2[ne],c[ne]);
if nv<v1[ne] then nv:=v1[ne];
if nv<v2[ne] then nv:=v2[ne];
f[ne]:=0
until seekeof(o);
close(o);
assign(o,'flow.out');
rewrite(o);
for j:=1 to ne do begin
e1[j]:=j;
e2[j]:=j end;
randomize;
qsort1(1,ne);
qsort2(1,ne);
ne1[0]:=0; ne1[nv]:=ne;
ne2[0]:=0; ne2[nv]:=ne;
for j:=1 to ne-1 do begin
if v1[e1[j]]<v1[e1[j+1]] then ne1[v1[e1[j]]]:=j;
if v2[e2[j]]<v2[e2[j+1]] then ne2[v2[e2[j]]]:=j end;
ne1[v1[e1[ne]]]:=ne;
ne2[v2[e2[ne]]]:=ne;
for j:=1 to nv-1 do begin
if ne1[j]=0 then ne1[j]:=ne1[j-1];
if ne2[j]=0 then ne2[j]:=ne2[j-1] end;
{1} l1:
for j:=1 to nv do begin
p[j]:=0;
r[j]:=0;
b[j]:=false end;
r[a]:=65535;
b[a]:=true;
{2} l2:
v:=0;
repeat inc(v)
until (b[v]) or (v=nv);
if not b[v] then fin;
b[v]:=false;
{3}
for j:=ne1[v-1]+1 to ne1[v] do
if f[e1[j]]<c[e1[j]] then begin
d:=c[e1[j]]-f[e1[j]];
if d>r[v] then d:=r[v];
w:=v2[e1[j]];
if d>r[w] then begin
r[w]:=d;
p[w]:=v;
ep[w]:=e1[j];
if w<>z then b[w]:=true end end;
{4}
for j:=ne2[v-1]+1 to ne2[v] do
if 0<f[e2[j]] then begin
d:=f[e1[j]];
if d>r[v] then d:=r[v];
w:=v1[e2[j]];
if d>r[w] then begin
r[w]:=d;
p[w]:=v;
ep[w]:=e2[j];
if w<>z then b[w]:=true end end;
{5}
if r[z]=0 then goto l2;
{6}
v:=z;
repeat if p[v]=v1[ep[v]] then f[ep[v]]:=f[ep[v]]+r[z] else
if p[v]=v2[ep[v]] then f[ep[v]]:=f[ep[v]]-r[z] else
v:=p[v]
until v=a;
goto l1;
END.