zoukankan      html  css  js  c++  java
  • 网络流之最大流算法总结(FF, EK, Dinic)

    这里以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
    ", 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
    ", 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
    ", 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
    ", Dinic(1, n));
    	}
    	return 0;
    }

     以上就是几种常见的最大流算法,这几种算法都属于増广路算法,也就是不断求增广路来更新最大流,关于最大流的算法还有很多,可以根据图的不同选取想要的算法。

  • 相关阅读:
    无限维
    黎曼流形
    why we need virtual key word
    TOJ 4119 Split Equally
    TOJ 4003 Next Permutation
    TOJ 4002 Palindrome Generator
    TOJ 2749 Absent Substrings
    TOJ 2641 Gene
    TOJ 2861 Octal Fractions
    TOJ 4394 Rebuild Road
  • 原文地址:https://www.cnblogs.com/zyq1758043090/p/11852534.html
Copyright © 2011-2022 走看看