Edmonds-Karp算法(EK算法)簡單講解及實現(鄰接表)

最大流問題介紹

圖定義

給定一張有向圖,a->b 邊的權值表示當前情況下,從a到b能夠發送的流量上限是多少,比如 a->b = 10 表示當前能夠從a發10流量給b,當然我們也可以選擇不發那麼多。

例子:
假設a->b 之間未有任何流量的發送,此時我們發8流量,那麼 a->b = 10 變爲 a->b = 2a->b 能夠發送的流量上限由10 變爲 2

源點與匯點

源點只發不收,匯點只收不發,其他點作爲中轉站,既可以發也可以收,這像極了快遞物流網絡。。。

源點:賣家
匯點:買家
其他點:快遞物流站點

最大流問題描述

從源點能夠想方設法發送到匯點的最大流量的值,而求解這個問題就是求解這個最大值

你可能會想:這不是直接把源點發送的流量拉滿就行了🐎?

然而事實是這樣的:兩點之間發送流量最大值取決於兩點之間管道最細的地方的大小(木桶原理),前面塞太多後面放不下。。。

Ford-Fulkerson方法

Ford-Fulkerson不是算法,是一種方法,而通過bfs實現這種方法,叫做Edmonds-Karp算法

回退邊與增廣圖

Ford-Fulkerson定義了一個非常具有創造性的“回退邊”

如果我們在a->b中塞入x流量,表示我們買了x貨物,於是我們可以退貨。
回退邊描述了這種退貨的行爲,回退邊的權值是我們已經塞入的流量的大小(聯繫現實生活中,買了x貨物,最多退貨量爲x)

在這裏插入圖片描述

我們在原圖g的基礎上增加回退邊,構成原圖的增廣圖ga,增廣圖包含的回退邊的權值等於已經塞入的流量的大小

僞代碼

Ford-Fulkerson方法提供了一種求解最大流的思路,是一個很巧妙的貪心思路,這是他的僞代碼:

sum = 0
while True:
	構造原圖g的增廣圖ga
	在增廣圖ga上找到一條從源點到匯點的最短路徑p,這個最短指的是經過的點最少而不是經過的邊權值之和最小
	if 找不到p :
		break
	記錄p一路走來遇到的"最細"的管道大小f(即經過的所有邊中權值的最小值)
	通過f來更新原圖中路徑p上的邊:
	for(a->b) in p:
		(a->b)能夠通過的流量上限 -= f
		(b->a)能夠通過的流量上限 += f
	sum += f
	
sum就是圖最大流

很抽象

圖解

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

Edmonds-Karp算法

EK算法之所以是算法,在於它確定了在增廣圖上找最短路徑的方法,即BFS,因爲bfs的特性,b到終點一定是最短路

複雜度

bfs用鄰接表,複雜度爲O(e),更新邊複雜度O(e),而每次最少增長1的流量,假設圖最大流答案是f,則最多需要fbfs,所以總複雜度爲(O(e)+O(e) * f) = O(ef)

代碼實現細節

  1. 因爲邊具有屬性,鄰接表adj[x]不再存儲下一個頂點的信息,而是存儲所有從x出發的邊,在邊集合中的下標
  2. 一條邊具有兩個屬性:起點,當前允許的最大流量
  3. 因爲回退邊的存在,總共邊的數量是原圖邊數量的兩倍
  4. 在bfs的同時記錄路徑使用生成樹 father[] 數組即可做到,father[x]=-1表示x未被訪問
  5. bfs的同時記錄最細流量,使用min_flow[]數組,假設有邊a->b,那麼有 min_flow[b] = min(該邊允許的流量最大值, min_flow[a])
  6. bfs返回值爲true表示找到,否則沒找到路徑
  7. 邊的權值爲0則不可走,因爲每次最小壓入1流量
  8. bfs的時候,正向邊或回退邊都可以走,只要權值不爲0

代碼

ps:如果您在着手實現深圳大學算法課程的實驗,請手下留情,不要全部copy,否則代碼查重系統將會展現他那殘忍無情的精準

#include <bits/stdc++.h>

using namespace std;

typedef struct edge
{
	int st, ed, val;	// 起點, 終點, 還能通過多少流量(其中起點不必要,只是方便打印調試信息)
	edge(){}
	edge(int a, int b, int c){st=a;ed=b;val=c;}	
}edge;

int n,e,src,dst,ans;		// 頂點數, 初始邊數, 源, 目, 答案 
vector<vector<int>> adj;	// adj[x][i]表示從x出發的第i條邊在邊集合中的下標 
vector<edge> edges;			// 邊集合 
vector<int> min_flow;		// min_flow[x]表示從起點到x的路徑中最細流 
vector<int> father;			// 生成樹 

bool bfs_augment()
{
	for(int i=0; i<n; i++) father[i]=-1;
	for(int i=0; i<n; i++) min_flow[i]=1145141919; // inf
	father[src] = src;
	
	queue<int> q; q.push(src);
	while(!q.empty())
	{
		int x=q.front(),y; q.pop();
		if(x==dst) return true;
		for(int i=0; i<adj[x].size(); i++)
		{
			edge e = edges[adj[x][i]];
			y = e.ed;
			if(father[y]!=-1 || e.val==0) continue;
			father[y] = x;
			min_flow[y] = min(e.val, min_flow[x]);
			q.push(y);
		}
	}
	return false;
}

void graph_update()
{
	int x, y=dst, flow=min_flow[dst], i;
	cout<<"更新流量: "<<flow<<" 路徑: ";
	ans += flow;
	vector<int> path;
	while(y!=src)	// 沿着生成樹找起點並沿途更新邊 
	{
		path.push_back(y);
		x = father[y];
		for(i=0; i<adj[x].size(); i++) if(edges[adj[x][i]].ed==y) break;	
		edges[adj[x][i]].val -= flow;
		for(i=0; i<adj[y].size(); i++) if(edges[adj[y][i]].ed==x) break;
		edges[adj[y][i]].val += flow;
		y = x;
	}
	path.push_back(y);
	for(int i=path.size()-1; i>=0; i--) cout<<path[i]<<" "; cout<<endl;
}

int main()
{
	cin>>n>>e>>src>>dst;
	adj.resize(n); 
	father.resize(n); 
	min_flow.resize(n); 
	edges.resize(e*2);
	for(int i=0; i<e; i++)
	{
		int st, ed, limit; cin>>st>>ed>>limit;
		edges[2*i] = edge(st, ed, limit);
		edges[2*i+1] = edge(ed, st, 0);
		adj[st].push_back(2*i); adj[ed].push_back(2*i+1);
	}
	
	while(1)
	{
		if(!bfs_augment()) break;
		graph_update();
		// for(int i=0; i<edges.size(); i++) cout<<edges[i].st<<" -> "<<edges[i].ed<<" val="<<edges[i].val<<endl;
	}
	cout<<"最大流:"<<ans<<endl;
	
	return 0;
}

/*
6 7 0 5
0 2 2
0 1 2
1 4 1
2 4 5
2 3 2
3 5 1
4 5 2

7 11 0 6
0 1 3
0 3 3
1 2 4
2 0 3
2 3 1
2 4 2
3 4 2
3 5 6
4 1 1
4 6 1
5 6 9

*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章