最大流算法之EK(最短路徑增廣算法)

這是網絡流最基礎的部分——求出源點到匯點的最大流(Max-Flow)。
最大流的算法有比較多,本次介紹的是其中複雜度較高,可是比較好寫的EK算法。(不涉及分層,純粹靠BFS找匯點及回溯找最小流量獲得最終的答案)c++

EK算法,全名Edmonds-Karp算法(最短路徑增廣算法)。 算法

首先簡單介紹一下網絡流的基本術語
源點:起點。全部流量皆今後點流出。只出不進
匯點:終點。全部流量最後聚集於此。只進不出
流量上限:有向邊(u,v)(及弧)容許經過的最大流量
增廣路:一條合法的從源點流向匯點的路徑
計算最大流就是不停尋找增廣路找到最大值的過程。數組

合法的網絡流具備如下性質
1.f(i,j) <= c(i,j);//任意有向邊的流量必定小於等於該邊的流量限制
2.f(i,j) = -f(j,i);//從i流向j的流量與j流向i的流量互爲相反數反向邊
3.out(i) = in(i);//除源點、匯點外,任意一點i流入的流量與流出的相等(只是路過markdown

這裏寫圖片描述(截自洛谷)網絡

EK算法思路
1.經過BFS拓展合法節點(每一個節點在本次BFS中僅遍歷一次),找到匯點,並記錄每一個節點的前面節點(pre)(若找不到增廣路,算法結束)
2.經過BFS的記錄,從匯點回溯回源點,記錄下每條弧流量的**最小值**minn, ans += minn(不然就會超出某條邊的限制流量)
3.將全部通過的邊的流量減去minn,反向邊加上minn
4.重複上述步驟,直到找不到增廣路,算法結束。函數

樸素版EK
最爲簡單的寫法,經過鄰接矩陣存儲。
優勢:代碼簡單,一目瞭然。
缺點:輕易爆內存,(N^2)的空間太大,N >10000基本就廢了(MLE)。
源代碼:測試

#include <bits/stdc++.h> 
using namespace std;
#define INF 0x3f3f3f
#define maxn 10005

int n, m, st, en, flow[maxn][maxn], pre[maxn];
int q[maxn], curr_pos, st_pos, end_pos;
bool wh[maxn];
int max_flow;

void Init()//初始化
{
    int i, a, b, c;
    scanf("%d%d%d%d", &n, &m, &st, &en);
    for(i = 0; i != m; ++i)
    {
        scanf("%d%d%d", &a, &b, &c);
        flow[a][b] += c;
    }
    return ;
}

bool Bfs(int st, int en)//廣搜找源點
{
    st_pos = -1, end_pos = 0;
    memset(wh, 0, sizeof wh);
    wh[st] = 1;
    q[0] = st;
    while(st_pos != end_pos)
    {
        curr_pos = q[++st_pos];
        for(int i = 1; i != n+1; ++i)
        {
            if(!wh[i] && flow[curr_pos][i] > 0)
            {
                wh[i] = 1;
                pre[i] = curr_pos;
                if(i == en)
                {
                    return true;
                }
                q[++end_pos] = i;
            }
        }
    }
    return false;
} 

int EK(int start_pos, int end_pos)
{
    int i, minn;
    while(Bfs(start_pos, end_pos))//回溯
    {
        minn = INF;

        for(i = end_pos; i != start_pos; i = pre[i])
        {
            minn = min(minn, flow[pre[i]][i]);
        } 

        for(i = end_pos; i != start_pos; i = pre[i])
        {
            flow[pre[i]][i] -= minn;
            flow[i][pre[i]] += minn;//反向弧加上該值(具體緣由下文詳解)
        } 
        max_flow += minn;
    }
    return max_flow;
}

int main()
{
    //freopen("test.in", "r", stdin);
    //freopen("test.out", "w", stdout);

    Init();

    printf("%d", EK(st, en));

    //fclose(stdin);
    //fclose(stdout);
}

通過洛谷測評(P3376 【模板】網絡最大流),50分,MLE五個點優化

關於反向邊加上最小流值的緣由:
若是存在a->b有流,那麼若是某一點c->b有流,則流量實際上能夠再從b流到a,能夠理解爲把本來a->b的流量懟了回去
BFS遍歷獲得的剩下部分的增廣路的流量其實是本來a->b的流,而本來a->b以後流向匯點的增廣路的部分流量變爲由c->b的流量
增廣路依舊合法spa

這證實了鄰接矩陣是不可行的,因而咱們想到了麻煩一點可是省空間的存儲方法——邊表至少從m*m優化到n*m,實則一般n*(m/10)便足夠。code

定義一個結構體

struct qi{
    int st, en, num;//弧的始點,終點,權值
}flow[maxm];

相對應的廣搜也要稍做調整,注意一下pre的存儲方法:
pre[i]=k,i是點,k是弧的編號

bool Bfs(int st, int en)
{
    st_pos = -1, end_pos = 0;
    memset(wh, 0, sizeof wh);
    wh[st] = 1;
    q[0] = st;
    while(st_pos != end_pos)
    {
        curr_pos = q[++st_pos];
        for(int i = 0; i < 2*m; ++i)
        {
            if(flow[i].st == curr_pos && flow[i].num > 0 && !wh[flow[i].en])
            {
                ne = flow[i].en;
                wh[ne] = 1;
                pre[ne] = i;
                if(ne == en)
                {
                    return true;
                }
                q[++end_pos] = flow[i].en;
            }
        }
    }
    return false;
}

因此Tracback的遍歷循環也要改變。

for(i = end_pos; i != st; i = flow[pre[i]].st)//注意一下i如何取下一個的
{
    minn = min(minn, flow[pre[i]].num);
}

事實證實:MLE的問題完美解決,測試結果:94ms , 18785kb,TLE三個點

那麼還須要優化一下時間
注意BFS拓展時的循環:i:=0->2*m//m是弧的個數(由於有反向邊,故是2*m)
因爲題目範圍是m <= 100000,每次最大循環達到2e5,太浪費了。
因而咱們在邊表再作一點手腳:開二維數組對每一個始點全部的弧分別記錄
x[i][m]指以i爲始點的第m條弧(再開一個num[i]記錄每一個始點弧的個數)。
那麼代碼就更加繁瑣了。

首先是讀入優化:

int read()
{
    input = 0;
    a = getchar();
    while(a < '0' || a > '9')
        a = getchar();
    while(a >= '0' && a <= '9')
    {
        input = input*10+a-'0';
        a = getchar();
    }
    return input;
}

而後是初始化:

void Init()
{
    int i, a, b, c;
    memset(num, -1, sizeof num);
    n = read(), m = read(), st = read(), en = read();
    for(i = 0; i != m; ++i)
    {
        flow[i].st = read();
        flow[i].en = read();
        flow[i].num = read();
        re[flow[i].st][++num[flow[i].st]] = i;
        re[flow[i].en][++num[flow[i].en]] = i+m;//初始化時將第i條弧的反向邊編號爲i+m
    }
    for(int i = m; i != 2*m; ++i)
    {
        flow[i].st = flow[i-m].en;
        flow[i].en = flow[i-m].st;
        flow[i].num = 0;
    }
    pre[st] = -1;
    return ;
}

BFS部分:

bool Bfs(int st, int en)
{
    int i, j;
    st_pos = -1, end_pos = 0;
    memset(wh, 0, sizeof wh);
    wh[st] = 1;
    q[0] = st;
    while(st_pos != end_pos)
    {
        curr_pos = q[++st_pos];
        for(i = 0; i < num[curr_pos]+1; ++i)
        {
            j = re[curr_pos][i];//當前可拓展節點的候選節點,即當前節點的第i條弧的終點
            if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
            {
                ne = flow[j].en;
                wh[ne] = 1;
                pre[ne] = j;
                if(ne == en)
                {
                    return true;
                }
                q[++end_pos] = flow[j].en;
            }
        }
    }

    return false;
}

Traceback只需改變反向邊部分:

flow[pre[i]+m].num += minn;

主函數部分不須要調整。

最終測評結果:392ms , 56988kb,AC。

經過上述過程,咱們得知網絡流的存儲結構應該爲邊表而非鄰接矩陣。固然,使用vector來存儲弧也是能夠的,這樣會更大程度上節省空間,但會對時間形成必定影響,根據實際狀況自行取捨。


最後附上完整代碼:

/*
Max_flow- EK
2017/04/07
While(BFS can find the end)
for i,i->j:=st -> end: 
f[i][j] -= minn;f[j][i] += minn; ans += minn;//so we can delete at least one edge(the one which satisfies f[i][j] == minn) 
*/
#include <bits/stdc++.h> 
using namespace std;
#define INF 0x3f3f3f
#define maxm 200005
#define maxn 10005

struct qi{int st, en, num;}flow[maxm];
int n, m, st, en, pre[maxn], re[maxn][maxn/10], num[maxn];
int q[maxn], curr_pos, st_pos, end_pos, ne, max_flow;
bool wh[maxn];
int input;
char a;

int read()
{
    input = 0;
    a = getchar();  
    while(a < '0' || a > '9')
        a = getchar();
    while(a >= '0' && a <= '9')
    {
        input = input*10+a-'0';
        a = getchar();
    }
    return input;
}

void Init()
{
    int i, a, b, c;
    memset(num, -1, sizeof num);

    n = read(), m = read(), st = read(), en = read();
    for(i = 0; i != m; ++i)
    {
        flow[i].st = read();
        flow[i].en = read();
        flow[i].num = read();
        re[flow[i].st][++num[flow[i].st]] = i;
        re[flow[i].en][++num[flow[i].en]] = i+m;
    }
    for(i = m; i != 2*m; ++i)
    {
        flow[i].st = flow[i-m].en;
        flow[i].en = flow[i-m].st;
    }
    return ;
}

bool Bfs(int st, int en)
{
    int i, j;
    st_pos = -1, end_pos = 0;
    memset(wh, 0, sizeof wh);
    wh[st] = 1;
    q[0] = st;

    while(st_pos != end_pos)
    {
        curr_pos = q[++st_pos];
        for(i = 0; i < num[curr_pos]+1; ++i)
        {
            j = re[curr_pos][i];
            if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
            {
                ne = flow[j].en;
                wh[ne] = 1;
                pre[ne] = j;
                q[++end_pos] = flow[j].en;
                if(ne == en)
                    return true;         
            }
        }
    }

    return false;
} 

int EK(int start_pos, int end_pos)
{
    int i, minn;
    while(Bfs(start_pos, end_pos))
    {
        minn = INF;

        for(i = end_pos; i != st; i = flow[pre[i]].st)
        {
            minn = min(minn, flow[pre[i]].num);
        } 

        for(i = end_pos; i != st; i = flow[pre[i]].st)
        {
            flow[pre[i]].num -= minn;
            flow[pre[i]+m].num += minn;
        } 

        max_flow += minn;
    }
    return max_flow;
}

int main()
{
    //freopen("test.in", "r", stdin);
    //freopen("test.out", "w", stdout);

    Init();

    printf("%d", EK(st, en));

    //fclose(stdin);
    //fclose(stdout);
}

自此,EK算法的講解便結束了。
箜瑟_qi 2017.04.07 2:32


2017.04.21略加修改:

/*
Max_flow- EK
2017/04/07
*/
#include <bits/stdc++.h> 
using namespace std;
#define INF 0x3f3f3f
#define maxm 200005
#define maxn 10005

struct Edge{
    int st, en, num;
    Edge(){}
    Edge(int s, int e, int n):
        st(s), en(e), num(n){}
}flow[maxm];

int n, m, st, en, pre[maxn], re[maxn][maxn/10], num[maxn];
int q[maxn], curr_pos, st_pos, end_pos, ne, max_flow;
bool wh[maxn];

int input;
char a;

static int read()
{
    a = getchar();
    input = 0;
    while(a < '0' || a > '9')   a = getchar();
    while(a >= '0' && a <= '9')
    {
        input = input*10+a-'0';
        a = getchar();
    }
    return input;
}

static void Init()
{
    int i, a, b, c, cur;
    memset(num, -1, sizeof num);
    n = read(), m = read(), st = read(), en = read();
    for(i = 0; i != m; ++i)
    {
        cur = 2*i;
        a = read(), b = read(), c = read();
        flow[cur] = Edge(a, b, c);
        flow[cur^1] = Edge(b, a, 0);
        re[flow[cur].st][++num[flow[cur].st]] = cur;
        re[flow[cur].en][++num[flow[cur].en]] = cur^1;
    }
    return ;
}

static bool Bfs(int st, int en)
{
    int i, j;
    st_pos = -1, end_pos = 0;
    memset(wh, 0, sizeof wh);
    wh[st] = 1;
    q[0] = st;
    while(st_pos != end_pos)
    {
        curr_pos = q[++st_pos];
        for(i = 0; i < num[curr_pos]+1; ++i)
        {
            j = re[curr_pos][i];
            if(flow[j].st == curr_pos && flow[j].num > 0 && !wh[flow[j].en])
            {
                ne = flow[j].en;
                wh[ne] = 1;
                pre[ne] = j;
                q[++end_pos] = flow[j].en;
                if(ne == en)
                    return true;         
            }
        }
    }
    return false;
} 

static int EK(int start_pos, int end_pos)
{
    int i, minn;
    while(Bfs(start_pos, end_pos))
    {
        minn = INF;

        for(i = end_pos; i != st; i = flow[pre[i]].st)
        {
            minn = min(minn, flow[pre[i]].num);
        } 

        for(i = end_pos; i != st; i = flow[pre[i]].st)
        {
            flow[pre[i]].num -= minn;
            flow[pre[i]^1].num += minn;
        } 
        max_flow += minn;
    }
    return max_flow;
}

int main()
{
    //freopen("test.in", "r", stdin);
    //freopen("test.out", "w", stdout);

    Init();
    printf("%d", EK(st, en));

    //fclose(stdin);
    //fclose(stdout);
}
相關文章
相關標籤/搜索