最大流-前置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: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定義為:

  1. (u,v)是流網絡的邊,即容量非零,則等於剩余容量C[u][v] - F[u][v]
  2. 否則,等於反向流量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去掉,增加當前方向的流量的同時,將反向流量減少,從而同時更新了反向邊。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM