1. 程式人生 > >演算法導論 26章 最大流(一)

演算法導論 26章 最大流(一)

最大流問題
為了求一點到另一點的最短距離,我們可以把公路地圖模型化為有向圖。同理,我們可以將公路模型化為一個“流網路”(flow network),並運用它來解決物流相關的問題。其中最大流問題是其中一種,即:在不違背每條路(即有向圖的邊)的容量的情況下,可以把物質從源點送到匯點的最大速率是多少。

最大流定義和性質
流網路是一個有向圖G=(V,E),其中每條邊(u,v)均有一個容量c(u,v) >= 0;如果邊c(u,v)不屬於E,則c(u,v) = 0.流網路一般只有兩個頂點:源點s和匯點t。
對於流網路G = (V,E),容量函式為c,流函式為f。該網路滿足下述三個性質:
1. 容量限制:對於所有的u,v屬於E,f(u,v) <= c(u,v);
2. 反對稱性: 對於所有的u,v屬於E,f(u,v) = -f(v,u);
3. 流守恆性:對於所有的u屬於V-{s,t},有:
這裡寫圖片描述

從一個頂點u到另一個頂點v的流f的值為f(u,v),可正可負亦可為0。對於源點s來說,從它出發的流即為整個流網路的總流,它的值為:![這裡寫圖片描述](https://img-blog.csdn.net/20150322153643828) 而對於最大流問題,就是希望找出從源點s到匯點t的最大值流。

最大流的例子
這裡寫圖片描述
如上圖,是一個流網路,其中對於邊的數值,左邊為該邊上的流值,右邊是改變允許通過的最大流值,即容量。可以看出最大流的值為23,可以通過累加源點的輸出總流或者匯點的輸入總流獲得。

Ford-Fulkerson方法解決最大流問題
1、殘留網路。
給定一個流網路G=(V,E)和流f,在輸入流f後G的殘留網路為Gf = (V,Ef),其中:
這裡寫圖片描述

,即由所有剩下的容量大於0的邊所組成。如下圖a是從源點s輸入流11和8後得到的殘留網路b。
這裡寫圖片描述
其中,最初的原圖G也是最初的殘留網路。b中的粗線表示一條增廣路徑。
2、增廣路徑。
增廣路徑是用Ford-Fulkerson解決最大流問題的關鍵。增廣路徑p是殘留網路Gf中,從源點s到匯點t的一條簡單路徑,該路徑的每條邊的容量都大於0,這意味我們可以通過這條路徑繼續輸入流。
由此我們可以得到用Ford-Fulkerson方法求取最大流問題的演算法了,如下:
1、在Gf中找到一條增廣路徑,求取該條路徑的最小容量:這裡寫圖片描述
2、更新該條路徑上所有邊(u,v)的容量c(u,v)和f(u,v),同時還需要更新c(v,u)和f(v,u);
3、迴圈1和2直到殘留網路中不再有增廣路徑了;

用偽程式碼表示如下:

Ford - Fulkerson(G, s, t)
{
    for each edge(u, v) in E[G]
    {
        f[u, v] = 0;
        f[v, u] = 0;
    }
    while (there exists an augmenting path p)
    {
        cf(p) = min{ cf(u, v) :(u, v) is in p };
        for each edge(u, v) in p
        {
            f[u, v] = f[u, v] + cf(p);
            f[v, u] = -f[u, v];
            cf(u, v) = cf(u, v) - cf(p);
            cf(v, u) = -f[v, u];
        }
    }
}

根據以上演算法,可以看出,唯一未解之處在於如何尋找該增廣路徑。根據尋找增廣路徑所使用的演算法的不同,可以得到具體不同的演算法。如果我們運用廣度優先搜尋來遍歷整個Gf依次判斷是否還存在增廣路徑,這種演算法被稱為Edmonds-Karp演算法。

多源點多匯點最大流問題
對於該問題,我們可以引入一個超級源點,將其與各個源點相連,並且將相連邊的容量設為無窮大,同理,也引入一個超級匯點,將其與各個匯點相連,且將相連邊容量設為無窮,如此,該問題就成為了單源點單匯點問題了。

接下來我將給出用C++實現Edmonds-Karp演算法的具體程式碼。首先我們約定:為了便於實現,所有的頂點將用數字1,2,3…編號,從下標為1開始存入資料,並且頂點編號和下標索引對應,即G[i]儲存的是i號節點的鄰接連結串列。

//Ford-Fulkerson方法求最大流,用廣度優先搜尋尋找增廣路徑,為Edmonds-Karp演算法
#include<iostream>
#include<queue>
#include<fstream>
#include<vector>

#define MAX 0x7fffffff

using namespace std;
enum color{ WHITE, GRAY, BLACK };//用來標記頂點顏色

struct edgeNode
{//邊節點
    size_t adjvertex;//該邊的關聯的頂點
    int capacity;//邊當前容量
    int flow;//邊當前流量
    edgeNode *nextEdge;//下一條邊
    edgeNode(size_t adj, int w) :adjvertex(adj), capacity(w), flow(0), nextEdge(nullptr){}
};

class AGraph
{//有向圖
private:
    vector<edgeNode*> G;
    size_t nodenum;
public:
    AGraph(size_t n) :nodenum(n){ G.resize(n + 1); }
    void initGraph();//初始化有向圖
    edgeNode* search(size_t, size_t);//查詢邊
    edgeNode* addEdge(size_t, size_t, int);//向圖中新增邊
    void deleteEdge(size_t, size_t);//有向圖中刪除邊
    bool BFS(size_t, size_t, vector<size_t>&);//廣度優先搜尋,返回一個前驅陣列
    int Edmonds_Karp(size_t, size_t);//Ford_Fulkerson方法的一種具體實現,採用廣度BFS搜尋增廣路經
    void print();
    ~AGraph();
};

bool AGraph::BFS(size_t s, size_t t, vector<size_t> &p)
{
    bool augment_path = false;//記錄是否存在增廣路徑
    queue<size_t> Q;
    vector<size_t> dis(nodenum + 1), color(nodenum + 1);
    for (size_t i = 1; i <= nodenum; ++i)
    {
        dis[i] = MAX;
        p[i] = i;
        color[i] = WHITE;
    }
    color[s] = GRAY;
    dis[s] = 0;
    p[s] = s;
    Q.push(s);
    while (!Q.empty())
    {
        size_t u = Q.front();
        if (u == t) augment_path = true;//若可以遍歷到匯點,則存在
        Q.pop();
        edgeNode *curr = G[u];
        while (curr != nullptr)
        {
            if (color[curr->adjvertex] == WHITE && curr->capacity > 0)
            {
                color[curr->adjvertex] = GRAY;
                dis[curr->adjvertex] = dis[u] + 1;
                p[curr->adjvertex] = u;
                Q.push(curr->adjvertex);
            }
            curr = curr->nextEdge;
        }
        color[u] = BLACK;
    }
    return augment_path;
}

int AGraph::Edmonds_Karp(size_t s, size_t t)
{//若用兩個二維陣列分別儲存邊(u,v)的flow和capacity,就可以省去search和addEdge了,
    //可以改善效率(但是提高不了漸進時間),不過增加了使用空間
    vector<size_t> parent(nodenum + 1);
    while (BFS(s, t, parent))//用BFS尋找從s到t的增廣路徑,parent記錄前驅
    {//若存在
        int increase_flow = MAX;
        size_t tmp = t;
        while (tmp != parent[tmp])
        {//則確定該增廣路徑上的最小容量
            edgeNode *curr = search(parent[tmp], tmp);
            if (curr->capacity < increase_flow) increase_flow = curr->capacity;
            tmp = parent[tmp];
        }
        tmp = nodenum;
        while (tmp != parent[tmp])
        {//然後對該路徑上邊增加流量,同時將減小當前容量
            edgeNode *r = search(parent[tmp], tmp);
            r->flow = r->flow + increase_flow;
            r->capacity -= increase_flow;
            //當我們增加了f(u,v),意味著逆邊(v,u)增加了容量c(v,u),
            //即使該邊最初是不存在的,因為v可以將該流返回給u,
            //addEage功能請參考該函式註釋
            edgeNode *p = addEdge(tmp, parent[tmp], 0);
            p->flow = -r->flow;//根據流對稱性獲得對稱邊的當前流
            p->capacity -= p->flow;//同時更新其當前容量
            tmp = parent[tmp];
        }
    }
    //沒有增廣路徑時,說明已找到最大流
    int maximum_flow = 0;
    edgeNode *p_s = G[s];
    while (p_s != nullptr)
    {//統計源點的流總量即可
        maximum_flow += p_s->flow;
        p_s = p_s->nextEdge;
    }
    return maximum_flow;
}

void AGraph::initGraph()
{
    size_t start, end;
    int w;
    ifstream infile("F:\\maximumflow.txt");
    while (infile >> start >> end >> w)
        addEdge(start, end, w);
}

edgeNode* AGraph::search(size_t start, size_t end)
{
    edgeNode *curr = G[start];
    while (curr != nullptr && curr->adjvertex != end)
        curr = curr->nextEdge;
    return curr;
}

edgeNode* AGraph::addEdge(size_t start, size_t end, int capacity)
{//新增邊
    edgeNode *curr = search(start, end);//先查詢
    if (curr == nullptr)
    {//若邊不存在,則新增
        edgeNode *p = new edgeNode(end, capacity);
        p->nextEdge = G[start];
        G[start] = p;
        return p;
    }
    return curr;//否則不新增,返回當前邊地址
}

void AGraph::deleteEdge(size_t start, size_t end)
{
    edgeNode *curr = search(start, end);
    if (curr != nullptr)
    {
        if (curr->adjvertex == end)
        {
            G[start] = curr->nextEdge;
            delete curr;
        }
        else
        {
            edgeNode *pre = G[start];
            while (pre->nextEdge->adjvertex != end)
                pre = pre->nextEdge;
            pre->nextEdge = curr->nextEdge;
            delete curr;
        }
    }
}

inline void AGraph::print()
{
    for (size_t i = 1; i != G.size(); ++i)
    {
        edgeNode *curr = G[i];
        cout << i;
        if (curr == nullptr) cout << " --> null";
        else
            while (curr != nullptr)
            {
                cout << " --<" << curr->capacity << ">--> " << curr->adjvertex;
                curr = curr->nextEdge;
            }
        cout << endl;
    }
}

AGraph::~AGraph()
{
    for (size_t i = 1; i != G.size(); ++i)
    {
        edgeNode *curr = G[i], *pre;
        while (curr != nullptr)
        {
            pre = curr;
            curr = curr->nextEdge;
            delete pre;
        }
    }
}

const int nodenum = 6;
/*
1 2 16 1 5 13
2 3 12 2 5 10
3 5 9 3 6 20
4 3 7 4 6 4
5 2 4 5 4 14
*/

int main()
{//測試資料採用的是上面的圖,其中所有的頂點按順時針方向從源點s(1號)開始依次編號
    AGraph graph(nodenum);
    graph.initGraph();
    graph.print();
    cout << endl;
    int maximum_flow = graph.Edmonds_Karp(1, nodenum);
    cout << "The maximum flow is " << maximum_flow << endl;
    //graph.print();
    getchar();
    return 0;
}

執行截圖
輸出中<>裡面是各邊最初的容量,可以理解為權值,只不過這個權值是可以改變的。

這裡寫圖片描述

最大二分匹配
對於一個無向圖G=(V,E),一個匹配時一個邊子集M,其包含於E,且對於所有頂點v屬於V,M中至多有一條邊與v相關聯。如果M中某條邊與v關聯,則說頂點v被匹配,否則是無匹配的。最大匹配就是最大勢的匹配,即對於任何一個匹配M’,有|M| > |M’|。

二分圖是這樣一種圖:確定點被分為L和R兩部分,L和R不相交,且E中的每條邊一個頂點在L,另一個頂點在R。我們假設二分圖中的每個頂點至少有一條邊與其關聯。下圖展現了匹配的概念。

這裡寫圖片描述

那麼如何尋找二分圖的最大匹配呢?我們知道,對於二分圖中的每個頂點v,至多能和M中的一條邊關聯,這就相當於對於v而言,從它出發的流最多為1,而且該邊的容量最大也只能為1。因此,尋找二分圖的最大匹配可以轉換為在各邊容量均為1的多源點多匯點的流網路中尋找最大流。

對於上圖,我們新增一個超級源點和一個超級匯點,得到單源點單匯點的流網路,如下圖:

這裡寫圖片描述

然後執行Edmonds-Karp演算法即可得到最大匹配值,為3。

相關推薦

演算法導論 26 ()

最大流問題 為了求一點到另一點的最短距離,我們可以把公路地圖模型化為有向圖。同理,我們可以將公路模型化為一個“流網路”(flow network),並運用它來解決物流相關的問題。其中最大流問題是其中一種,即:在不違背每條路(即有向圖的邊)的容量

26 (正在修改)

一、綜述 1.定義 定義1:流網路 定義2:殘留容量 定義3:增廣路徑 已知一個網路流G=(V,E)和流f,增廣路徑p為殘留網路G|f中從s到t的一條簡單路徑 能夠沿一條增廣路徑p的每條邊傳輸的網路流的最大量為p的殘留容量,由下式定義: c|f(p) = min{c|f

演算法9-1:小切割問題

最小切割問題 首先介紹什麼是切割。切割就是將一張圖中的頂點分成兩部分A和B。 接下來介紹一下什麼是容量。容量是A區到B區所有的邊權重之和。 最小切割就是求一張圖中使得容量最小的切割方式。 最小切割的應用 最小切割在國家的拆分時會用到。著名的蘇聯解體事件就是

之ek演算法】HDU1532 求

本來是繼續加強最短路的訓練,但是遇到了一個最短路 + 最大流的問題,最大流什麼鬼,昨天+今天學習了一下,應該對ek演算法有所瞭解,憑藉學習後的印象,自己完成並ac了這個最大流的模板題題目大意:都是圖論,只是這個圖給你的關係是網路關係,就是從s到t的路上,你運送的東西的量必

演算法導論(Edmonds-Karp演算法)

華電北風吹 天津大學認知計算與應用重點實驗室 2016-07-20 有向圖的最大流演算法程式碼模板。利用廣度優先搜尋尋找殘量網路增廣路。 參考程式碼: #include <iostr

演算法導論》筆記(18) 含部分習題

流網路,容量值,源結點,匯點,容量限制,流量守恆。反平行,超級源結點,超級匯點。 Ford-Fulkerson方法。殘存網路,增廣路徑,最小切割定理。f是最大流,殘存網路不包含增廣路徑,|f|等於最小切割容量三者等價。 基本的Ford-Fulkerson演算法。Edmond

圖的匹配問題與問題(六)——匈牙利演算法種簡潔實現

這個演算法更為簡潔,也好理解。和維基百科上介紹的演算法思路是一致的。 求最大匹配的一種顯而易見的演算法是:先找出全部匹配,然後保留匹配數最多的。但是這個演算法的時間複雜度為邊數的指數級函式。因此,需要尋求一種更加高效的演算法。下面介紹用增廣路求最大匹配的方法(稱作匈牙

hdu4183往返經過至多每一個點次/

namespace == != hdu scanf push i++ r+ tdi 題意:從s到t,每一個點有f值,僅僅能從f值小的到大的。到T後回來。僅僅能從f值大的到 小的,求可行否。 往返,事實上就是倆條路過去(每一個點最多一次)。所以想到流量為2,跑最大流。看是

小割

open ios class std push mage style def 題目 a 看完題目,根本沒想法,暴力的復雜度是指數級別的,枚舉所有的集合,當時有點緊張,暴力的都沒寫,其實沒思路的 時候最好寫暴力的算法,騙點分就可以了。後來,看了牛客網上大神的思路,然後

】hihocoder 1369 : 網絡·Ford-Fulkerson算法

max problem cstring PE empty AD 算法 def sizeof http://hihocoder.com/problemset/problem/1369?sid=1328132 參考 https://blog.csdn.net/a17993422

【算法導論

image -s nbsp 導論 bubuko com strong col str 請右鍵圖片——查看圖圖像( *︾▽︾) 最大流【算法導論】

演算法導論 第二演算法入門 筆記 (插入排序、迴圈不變式、演算法分析、最好和壞時間複雜度、選擇排序、分治法、合併排序)

插入排序: 排序問題的定義如下: 輸入:N個數{a1, a2,..., an }。 輸出:輸入序列的一個排列{a'1 ,a'1 ,...,a'n },使得a'n <=a' n<=...<

DINIC演算法JAVA板子

推薦一條部落格: https://www.cnblogs.com/SYCstudio/p/7260613.html 講解得比較細緻。 然後自己理解了一下寫了個JAVA得板子,和哪個差不多,去把HDU一道板子題A了: http://acm.hdu.edu.cn/showproble

dinic演算法模板

//最短增廣路,Dinic演算法 struct Edge { int from,to,cap,flow; };//弧度 void AddEdge(int from,int to,int cap) //增弧 { edges.push_back((Edge){from,to,cap

網路 - 演算法之EK

首先是網路流中的一些定義: V表示整個圖中的所有結點的集合. E表示整個圖中所有邊的集合. G = (V,E) ,表示整個圖. s表示網路的源點,t表示網路的匯點. 對於每條邊(u,v),有一個容量c(u,v)   (c(u,v)>=0),如果c(u,v)=0,則表示(

Dinic演算法

Dinic演算法模板   鄰接矩陣形式 #include<iostream> #include<cstring> #include<algorithm> #include<queue> #include<vector>

問題與Ford-Fulkerson演算法介紹

背景 我們有圖 G=(V, E),V是頂點的集合,E是邊的集合。 圖中邊的權重都為非負數 (滿足1,2兩點有時稱之為流網路)。 對於這個圖G,有兩個頂點很重要,一個是源頭s,一個是匯聚點t,我們想考慮的是從源頭s流向匯聚點t的流。 我們想要解決的問題:在一個

洛谷 P2740 [USACO4.2]草地排水Drainage Ditches (EK增廣路演算法模板)

題目:草地排水 思路:EK增廣路演算法求最大流模板 程式碼: #include<bits/stdc++.h> using namespace std; #define maxn

EK、Dinic、SAP三種演算法模板

EK   //Max_flow //@2018/05/02 Wednesday //EK algorithm [Edmonds Karp] O(V*E^2) O(v^2) //by Tawn #include <bits/stdc++.h> using nam

POJ3281 Dining 入門 Dinic演算法

**題意: ** 有N頭牛,F種食物可以製作,D種飲料可以製作 然後每行代表一頭牛的喜好,開頭兩個數fi,di表示這頭牛喜歡fi種食物,di種飲料,接下來fi個數表示喜歡的食物編號,di個數表示喜歡的飲料的編號 現在主人使用最優決策製作出F種食物和D種飲料,問