目錄
這裏以POJ1273這道題爲例,題目鏈接:http://poj.org/problem?id=1273
FF算法:最基礎的最大流算法
通過DFS増廣,直到不能増廣爲止。
記最大流的流量爲F,FF算法最多進行F次DFS,所以其複雜度爲O(F|E|),每一次DFS的複雜度不確定,但是最壞的情況幾乎是不存在的,所以還是比較快的。
最大流算法的精髓就是加了一條反向邊,給了程序有一個後悔的機會,在一次DFS結束之後,每條正向邊減去流向匯點的流量,每條反向邊加上流向匯點的流量。
以下面這個圖爲例,第一次尋找的増廣路是1->2->3->4,流量是5,如果沒有反向邊的話,就已經不能再増廣了,但是很明顯這不是最佳策略。
在引入反向邊後可以發現,有一條新的増廣路1->3->2->4,流量爲5,最後發現沒有増廣了,求得最大流爲10。
在FF算法中邊是用鄰接表來存儲的,但是每次正向邊和反向邊的加減是要同時進行,所以在知道正向邊的同時,要知道反向邊的位置,所以結構體中有一個rev來存儲反向邊的位置。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#define N 10020
using namespace std;
int n, m, inf=0x7f7f7f;
bool book[N];
struct edge {
int v;
int w;
int rev; //在反向邊中存儲的位置
};
vector<edge>e[N];
void add(int u, int v, int w) //加邊
{
e[u].push_back(edge{ v, w, e[v].size() });
e[v].push_back(edge{ u, 0, e[u].size() - 1 });
}
int dfs(int s, int t, int f)
{
if (s == t)
return f; //找到終點
book[s] = true;
for (int i = 0; i < e[s].size(); i++)
{
edge &G = e[s][i];
if (G.w > 0 && book[G.v] == false)
{
int d = dfs(G.v, t, min(f, G.w)); //兩者之間流量較小的一個
if (d > 0)
{
G.w -= d; //改變正向邊和反向邊
e[G.v][G.rev].w += d;
return d;
}
}
}
return 0;
}
int FF(int s, int t)
{
int ans = 0;
while (1)
{
memset(book, false, sizeof(book)); //每次找増廣路
int d = dfs(s, t, inf);
if (d == 0) //找不到增廣路返回總流量
return ans;
ans += d;
}
}
int main()
{
int u, v, w;
while (scanf("%d%d", &m, &n) != EOF)
{
for (int i = 1; i <= n; i++)
e[i].clear();
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
}
printf("%d\n", FF(1, n));
}
return 0;
}
EK算法:每次BFS尋找増廣路
EK算法是基於FF算法的,只是邊的存儲變爲鄰接矩陣存儲,求増廣路的過程變爲BFS,在正向邊和反向邊的改變上也有變化,但是總體上的思路是一樣的,或者說FF, EK, Dinic這三者的思想是相同的。
EK算法的時間複雜度是O(V*E^2)適合邊較少的稀疏圖。
因爲EK算法圖的存儲是用鄰接矩陣存儲,所以每次在輸入流量的時候應該加上原有的流量,在這道題上也有體現。
因爲EK算法不能像FF算法中遞歸改變邊的流量,所以在EK算法中記錄的每一個點的匹配點,通過匹配點來改變邊的流量,這一點只要仔細想想應該能明白。
#include <stdio.h>
#include <queue>
#include <string.h>
#include <algorithm>
#define N 220
using namespace std;
int n, m, e[N][N], pre[N], flow[N], inf=0x7f7f7f;
int bfs(int s, int t)
{
memset(pre, -1, sizeof(pre));
queue<int>q;
q.push(s);
flow[s] = inf; //最開始流量爲無窮
while (!q.empty())
{
int u = q.front();
q.pop();
for (int v = 1; v <= n; v++) //求増廣路
{
if (e[u][v] > 0 && v != s && pre[v] == -1) //注意v!=s
{
pre[v] = u;
q.push(v);
flow[v] = min(flow[u], e[u][v]); //現在的流量是流過來的流量和可以流走的量的最小值
}
}
}
if (pre[t] == -1) //如果沒有到達終點
return -1;
return flow[t]; //返回流量
}
int EK(int s, int t)
{
int ans = 0;
while (1)
{
int d = bfs(s, t);
if (d == -1) //無法在找増廣路
break;
ans += d;
int p = t;
while (p != s) //邊的流量發生改變
{
e[pre[p]][p] -= d;
e[p][pre[p]] += d;
p = pre[p];
}
}
return ans;
}
int main()
{
int u, v, w;
while (scanf("%d%d", &m, &n) != EOF)
{
memset(e, 0, sizeof(e));
while (m--)
{
scanf("%d%d%d", &u, &v, &w);
e[u][v] += w; //這裏要加上原有的流量
}
printf("%d\n", EK(1, n));
}
return 0;
}
Dinic算法:EK算法的優化
Dinic算法是EK算法的優化,實際上和FF算法也是很像的, Dinic通過BFS分層,在用DFS求増廣路,可以達到多路増廣的效果,基本上Dinic算法是比較優秀的算法了。
衆所周知,網絡流題目會卡FF和EK,但是不會卡Dinic[笑]。
可以看到加邊操作是和FF算法是一樣的,分層也是一個比較常規的操作。
Dinic算法引入了一個當前弧優化:在一次BFS分層中已經搜索過的邊不用再搜。
#include <stdio.h>
#include <string.h>
#include <queue>
#include <vector>
#include <algorithm>
#define N 220
using namespace std;
struct edge {
int v;
int w;
int rev;
};
vector<edge>e[N];
int n, m, inf=99999999, dis[N], iter[N];
bool book[N];
void add(int u, int v, int w) //加邊
{
e[u].push_back(edge{ v, w, e[v].size() });
e[v].push_back(edge{ u, 0, e[u].size() - 1 });
}
void bfs(int s) //分層
{
queue <int>q;
memset(dis, -1, sizeof(dis));
dis[s] = 0;
q.push(s);
while (!q.empty())
{
int u = q.front();
q.pop();
for (int v = 0; v < e[u].size(); v++)
{
edge G = e[u][v];
if (dis[G.v] == -1 && G.w) //有流量時才加入
{
dis[G.v] = dis[u] + 1;
q.push(G.v);
}
}
}
}
int dfs(int s, int t, int f)
{
if (s == t)
return f;
for (int &v = iter[s]; v < e[s].size(); v++) //當前弧優化
{
edge &G = e[s][v];
if (dis[G.v] == dis[s] + 1 && G.w) //只有層數是+1時才増廣
{
int d = dfs(G.v, t, min(G.w, f));
if (d > 0)
{
G.w -= d;
e[G.v][G.rev].w += d;
return d;
}
}
}
return 0;
}
int Dinic(int s, int t)
{
int ans = 0;
while (1)
{
bfs(s);
if (dis[t] == -1)
return ans;
int d;
memset(iter, 0, sizeof(iter));
while ((d = dfs(s, t, inf)) > 0) //多次dfs
ans += d;
}
return ans;
}
int main()
{
int u, v, w;
while (scanf("%d%d", &m, &n) != EOF)
{
for (int i = 1; i <= n; i++) //初始化
e[i].clear();
while (m--)
{
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
}
printf("%d\n", Dinic(1, n));
}
return 0;
}
下面是把 if (dis[G.v] == dis[s] + 1 && G.w)
改爲 if (dis[G.v] > dis[s] && G.w) 的結果
Dinic+鏈式前向星
與之前存邊的方式有所不同, 這種存邊是用鏈式前向星來存圖的。
據說這是當前網絡流的主流寫法。
有時候不用前向星的話,可能會被卡掉,在hdu4292上有所體現。
這種存圖的巧妙之處是把正向邊和反向邊一起存儲,這樣正向邊=反向邊^1(異或1),反向邊=正向邊^1。
可以看到除了存邊不同之外,其他的代碼幾乎一致。
#include <stdio.h>
#include <string.h>
#include <queue>
#include <algorithm>
#define N 220
using namespace std;
struct date {
int v;
int w;
int next;
}edge[N*N];
int n, m, cnt, inf=0x3f3f3f, head[N], dis[N], iter[N];
int add(int u, int v, int w)
{
edge[cnt].v = v; edge[cnt].w = w;
edge[cnt].next = head[u]; head[u] = cnt++;
edge[cnt].v = u; edge[cnt].w = 0;
edge[cnt].next = head[v]; head[v] = cnt++;
}
void bfs(int s)
{
memset(dis, -1, sizeof(dis));
queue<int>q;
q.push(s);
dis[s] = 0;
while (!q.empty())
{
int u = q.front();
q.pop();
for (int v = head[u]; v != -1; v = edge[v].next)
{
date G = edge[v];
if (dis[G.v] == -1 && G.w && G.v != s)
{
dis[G.v] = dis[u] + 1;
q.push(G.v);
}
}
}
}
int dfs(int s, int t, int f)
{
if (s == t)
return f;
for (int &v = iter[s]; v != -1; v = edge[v].next)
{
date &G = edge[v];
if (G.w && dis[G.v] == dis[s] + 1)
{
int d = dfs(G.v, t, min(G.w, f));
if (d > 0)
{
edge[v].w -= d;
edge[v ^ 1].w += d;
return d;
}
}
}
return 0;
}
int Dinic(int s, int t)
{
int ans = 0;
while (1)
{
bfs(s);
if (dis[t] == -1)
return ans;
for (int i = 1; i <= n; i++)
iter[i] = head[i];
int d;
while ((d = dfs(s, t, inf)) > 0)
ans += d;
}
}
int main()
{
int u, v, w;
while (scanf("%d%d", &m, &n) != EOF)
{
cnt = 0;
memset(head, -1, sizeof(head));
while (m--)
{
scanf("%d%d%d", &u, &v, &w);
add(u, v, w);
}
printf("%d\n", Dinic(1, n));
}
return 0;
}
以上就是幾種常見的最大流算法,這幾種算法都屬於増廣路算法,也就是不斷求增廣路來更新最大流,關於最大流的算法還有很多,可以根據圖的不同選取想要的算法。