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;
    }

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

  • 相关阅读:
    变态跳台阶
    早期(编译器)优化--Java语法糖的味道
    早期(编译器)优化--javac编译器
    虚拟机字节码操作引擎-----基于栈的字节码解释引擎
    虚拟机字节码执行引擎-----方法调用
    虚拟机字节码执行引擎-----运行时栈帧结构
    虚拟机类加载机制--类加载器
    空间索引详解
    svn安装与使用
    IntelliJ IDEA 常用设置 (二)
  • 原文地址:https://www.cnblogs.com/zyq1758043090/p/11852534.html
Copyright © 2011-2022 走看看