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去掉,增加當前方向的流量的同時,將反向流量減少,從而同時更新了反向邊。
