一般增廣路算法(EdmondsKarp)
算法流程:
每次用BFS找一條最短的增廣路徑,然後沿着這條路徑修改流量值。當沒有增廣路時,算法停止,此時的流就是最大流。
增廣路算法的效率:
EK算法的時間複雜度是O(VE^2),時間效率較慢,在稀疏圖中效率還是比較高的。
算法實現:
鄰接矩陣
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=20; //最大點數
const int inf=0x3f3f3f3f;
int pre[maxn]; //保存前驅節點
bool vis[maxn]; // 標記一個點是否被訪問過
int mp[maxn][maxn]; //臨接矩陣保存殘留網絡
int n,m;//輸入n個頂點,m條邊
bool bfs(int st,int ed)
{
int t,i;
queue<int>q;
memset(pre,0,sizeof(pre));
memset(vis,0,sizeof(vis));
vis[st]=1;
q.push(st);
while(!q.empty())
{
t=q.front();
q.pop();
if(t==ed)
return true;//找到一條增廣路
for(i=1;i<=n;i++)
{
if(!vis[i]&&mp[t][i])
{
q.push(i);
pre[i]=t; //記錄前驅
vis[i]=1;
}
}
}
return false;
}
int EK(int st,int ed)//st源點,ed匯點
{
int maxflow=0; //記錄最大流
while(bfs(st,ed)) //尋找增廣路
{
int mi=inf;
for(int i=ed;i!=st;i=pre[i])//回溯前驅找最小流量
mi=min(mi,mp[pre[i]][i]);//尋找增廣路中的最小殘餘容量
for(int i=ed;i!=st;i=pre[i])
{
mp[pre[i]][i]-=mi;
mp[i][pre[i]]+=mi;
}
maxflow+=mi;
}
return maxflow;
}
鄰接表
const int MAXN = 1e5 + 7;
const int INF = 0x3F3F3F3F;
int n, m, head[MAXN], ecnt;
int pre[MAXN], vis[MAXN];
struct Edge {
int to, flow, next;
} edge[MAXN<<2];
void init() {
for(int i=0; i<=n; i++)
head[i] = -1;
ecnt=0;
}
void addedge(int u, int v, int c) {
edge[ecnt].to=v,edge[ecnt].flow=c,edge[ecnt].next=head[u],head[u]=ecnt++;
edge[ecnt].to=u,edge[ecnt].flow=0,edge[ecnt].next=head[v],head[v]=ecnt++;//後向弧,記錄殘餘流量
}
bool bfs(int s, int t) {
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
queue<int>que;
vis[s]=1;
que.push(s);
while(!que.empty()) {
int now = que.front();
que.pop();
if(now == t) {
return 1;
}
for(int i = head[now]; ~i; i = edge[i].next) {
int to = edge[i].to, flow = edge[i].flow;
if(!vis[to] && flow > 0) {
vis[to] = 1;
pre[to] = i;
que.push(to);
}
}
}
return 0;
}
int EK(int s, int t) {
int maxflow=0, mi=INF;
while(bfs(s, t)) {
mi = INF;
for(int i=t, pos; i != s; i = edge[pos^1].to) {
pos = pre[i];
mi = min(mi, edge[pos].flow);
}
for(int i = t, pos; i != s; i = edge[pos^1].to) {
pos = pre[i];
edge[pos].flow -= mi;
edge[pos^1].flow += mi;
}
maxflow += mi;
}
return maxflow;
}
Dinic 算法
Dinic算法可以說是EK算法的進階版,即在尋找增廣路的基礎上,增加了分層圖的概念。
算法流程:
1.確定層次圖(每個點的層次即在殘餘網絡中從源點到該點所經歷的最少邊數)
利用 BFS 對原來的圖進行分層,即對每個結點進行標號, 標號的值記錄的是當前結點距離源點的最短距離(經歷的最少邊數),在構建層次圖的時候所走的邊的殘餘流量必須大於0。
2.尋找增廣路
用 DFS 按照層次圖的順序尋找一條從源點到匯點的增廣路。找到一條路後要根據這條增廣路徑上的所有邊的殘餘流量的最小值更新所有邊的殘餘流量,即正向弧減去最小值, 反向弧加上最小值。
3.重複步驟 2, 當找不到一條增廣路的時候, 重複步驟 1, 重新建立層次圖, 直到從源點不能到達匯點爲止。
增廣路算法的效率:
Dinic算法的時間複雜度是O(V^2 E),適用於邊多的稠密圖,也適用於解決二分圖等問題。
算法實現:
const int maxn=555, maxm=1e6+7;//最大記錄點數及邊數
int head[maxn];//某點的鄰接鏈表頭
int cur[maxn];//向前弧優化,記錄當前節點考慮到的弧的編號
int d[maxn];//層次圖標記,記錄到源點的距離
int s,t,sign;//源點、匯點、總邊數
struct node{
int to,w,next;
}edge[maxm];
void init(){//初始化
memset(head,-1,sizeof(head));
sign=0;
}
void add_edge(int u,int v,int w){
edge[sign].to=v;
edge[sign].w=w;
edge[sign].next=head[u];
head[u]=sign++;
edge[sign].to=u;
edge[sign].w=0;
edge[sign].next=head[v];
head[v]=sign++;
}
int bfs(){//廣搜確定層次圖
queue<int>q;
memset(d,0,sizeof(d));
d[s]=1;
q.push(s);
while(!q.empty()){
int top=q.front();
q.pop();
for(int i=head[top];~i;i=edge[i].next){
int to=edge[i].to;
if(edge[i].w>0&&d[to]==0){//還能流並且沒被走過
d[to]=d[top]+1;
if(to==t)
return 1;
q.push(to);
}
}
}
return d[t]!=0;
}
int dfs(int top,int flow){
if(top==t)
return flow;
int ans=0,x=0;
for(int i=cur[top];~i;i=edge[i].next){//用cur記錄當前節點考慮到的弧的編號
int to=edge[i].to;
if(edge[i].w>0&&d[to]==d[top]+1){
x=dfs(to,min(flow-ans,edge[i].w));//記錄允許的流
edge[i].w-=x;//正向邊剩餘流量減去x
edge[i^1].w+=x;//反向邊剩餘流量加上x
if(edge[i].w)
cur[top] = i;
ans+=x;
if(ans==flow)
return flow;
}
}
if(ans==0)
d[top]=0;
return ans;
}
int dinic(int n){
int ans=0;
while(bfs()){//有路就走,可調整
for(int i=0;i<=n;i++)
cur[i]=head[i];
ans+=dfs(s,inf);
}
return ans;
}