Front Push-Relabel Algorithm
接口定義
- Input:容量數組
vector<vector<int>> capacity
,大小為n
;源點int source
,匯點int sink
; - Output:最大流
int maxflow
;
算法描述
數據結構
flow
:n*n的二維數組,表示結點間的流量,flow[u][v]
非零當且僅當capacity[u][v]
非零。excess
:n維數組,表示結點的溢出流量。height
:n維數組,表示結點的高度。L
:除去源點和匯點后,其余結點組成的鏈表,遍歷此鏈表進行discharge
操作。current
:n維數組,表示結點u
當前考慮推送的相鄰結點。
算法步驟
初始化
源點高度設置為n,其余結點必須先嘗試推送到所有其他結點,還有溢出的流才能將流返還源點。初始化鏈表L
。遍歷源點的所有相鄰邊,填滿這些邊的流量,並設置相鄰結點的溢流。
// initialize data structure
int n = capacity.size();
vector<vector<int>> flow(n, vector<int>(n, 0));
vector<int> excess(n, 0);
vector<int> height(n, 0);
height[source] = n;
list<int> L;
for (int u = 0; u < n; u++) {
if (u != source && u != sink) {
L.push_back(u);
}
}
vector<int> current(n, 0);
// initialize perflow
for (int v = 0; v < n; v++) {
if (capacity[source][v] > 0) {
flow[source][v] = capacity[source][v];
excess[v] = capacity[source][v];
excess[source] -= capacity[source][v];
}
}
push操作
僅當結點u
存在溢流,邊(u,v)
存在殘留容量,且u
的高度恰好比v
的高度大1時(符合這一條件的邊稱為許可邊),將多余流量盡可能從u
推送到v
。注意殘留容量residual
定義為:
- 若
(u,v)
是流網絡的邊,即容量非零,則等於剩余容量C[u][v] - F[u][v]
; - 否則,等於反向流量
F[v][u]
,表示允許將溢流倒回,降低邊(v,u)
的流量;
因此,除了修改兩個結點的溢流外,還需分上面的兩種情況修改邊的流量。
void push(vector<vector<int>>& C, vector<vector<int>>& F, vector<int>& E, int u, int v) {
int residual = C[u][v] > 0 ? C[u][v] - F[u][v] : F[v][u];
int delta = min(E[u], residual);
E[u] -= delta;
E[v] += delta;
if (C[u][v] > 0) {
F[u][v] += delta;
}
else {
F[v][u] -= delta;
}
}
relabel操作
僅當結點u
存在溢流,其不存在許可邊。則將u
的高度設置為其最低的存在殘留容量的相鄰結點的高度加1,使得它們之間的邊成為許可邊。
void relabel(vector<vector<int>>& C, vector<vector<int>>& F, vector<int>& H, int u) {
int min_height = INT_MAX;
for (int v = 0; v < C.size(); v++) {
int residual = C[u][v] > 0 ? C[u][v] - F[u][v] : F[v][u];
if (residual > 0) {
min_height = min(min_height, H[v]);
}
}
H[u] = min_height + 1;
}
discharge操作
反復嘗試將結點u
的溢流推送出去,直到結點u
不存在溢流。使用current
數組存儲當前u
考慮推送的目標,如果目標不可推送(非許可邊),則考慮下一個鄰接點。如果所有鄰接點均嘗試過且溢流仍然非零,則relabel
,並重新將current
指向頭部。如果成功推送則不動current
。
void discharge(vector<vector<int>>& C, vector<vector<int>>& F, vector<int>& E, vector<int>& H, vector<int>& current, int u) {
while (E[u] > 0) {
int v = current[u];
if (v >= C.size()) {
relabel(C, F, H, u);
current[u] = 0;
}
else {
int residual = C[u][v] > 0 ? C[u][v] - F[u][v] : F[v][u];
if (residual > 0 && H[u] == H[v] + 1) {
push(C, F, E, u, v);
}
else {
current[u]++;
}
}
}
}
算法主體
從鏈表L
的頭部開始discharge
結點,如果將結點的溢流釋放后高度發生了變化,則需要重新將結點放到表頭並遍歷其余結點進行釋放。實際上這是為了保證鏈表中結點u
之前的結點不存在溢流(relabel
以后可能會將流倒回之前的結點),這樣算法結束時鏈表中所有結點均不存在溢流。
結束時,源點的溢流應該等於最大流的相反數,這是因為源點沒有流入只有流出,而流出的流量之和就等於最大流。
int getMaxFlow(vector<vector<int>> capacity, int source, int sink) {
// initialize data structure
int n = capacity.size();
vector<vector<int>> flow(n, vector<int>(n, 0));
vector<int> excess(n, 0);
vector<int> height(n, 0);
height[source] = n;
list<int> L;
for (int u = 0; u < n; u++) {
if (u != source && u != sink) {
L.push_back(u);
}
}
vector<int> current(n, 0);
// initialize perflow
for (int v = 0; v < n; v++) {
if (capacity[source][v] > 0) {
flow[source][v] = capacity[source][v];
excess[v] = capacity[source][v];
excess[source] -= capacity[source][v];
}
}
// relabel to front
auto u = L.begin();
while (u != L.end()) {
int old_height = height[*u];
discharge(capacity, flow, excess, height, current, *u);
if (height[*u] > old_height) {
int tmp = *u;
L.erase(u);
L.push_front(tmp);
u = L.begin();
}
u++;
}
// compute max flow
return -excess[source];
}
優化寫法
可以簡化residual
的計算,允許數組flow
存在負值,用來表示反向流量,這樣殘留容量residual
可以統一成一個表達式:residual = C[u][v] - F[u][v]
。相應的需要修改push
操作,將ifelse
去掉,增加當前方向的流量的同時,將反向流量減少,從而同時更新了反向邊。