最大流-前置push-relabel演算法實現
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
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
去掉,增加當前方向的流量的同時,將反向流量減少,從而同時更新了反向邊