1. 程式人生 > >最大流-前置push-relabel演算法實現

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