Означення 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.