算法导论 26章 最大流(一)
来源:互联网 发布:自学跳舞的软件 编辑:程序博客网 时间:2024/06/05 07:56
最大流问题
为了求一点到另一点的最短距离,我们可以把公路地图模型化为有向图。同理,我们可以将公路模型化为一个“流网络”(flow network),并运用它来解决物流相关的问题。其中最大流问题是其中一种,即:在不违背每条路(即有向图的边)的容量的情况下,可以把物质从源点送到汇点的最大速率是多少。
最大流定义和性质
流网络是一个有向图G=(V,E),其中每条边(u,v)均有一个容量c(u,v) >= 0;如果边c(u,v)不属于E,则c(u,v) = 0.流网络一般只有两个顶点:源点s和汇点t。
对于流网络G = (V,E),容量函数为c,流函数为f。该网络满足下述三个性质:
1. 容量限制:对于所有的u,v属于E,f(u,v) <= c(u,v);
2. 反对称性: 对于所有的u,v属于E,f(u,v) = -f(v,u);
3. 流守恒性:对于所有的u属于V-{s,t},有:
从一个顶点u到另一个顶点v的流f的值为f(u,v),可正可负亦可为0。对于源点s来说,从它出发的流即为整个流网络的总流,它的值为:![这里写图片描述](http://img.blog.csdn.net/20150322153643828) 而对于最大流问题,就是希望找出从源点s到汇点t的最大值流。
最大流的例子
如上图,是一个流网络,其中对于边的数值,左边为该边上的流值,右边是改变允许通过的最大流值,即容量。可以看出最大流的值为23,可以通过累加源点的输出总流或者汇点的输入总流获得。
Ford-Fulkerson方法解决最大流问题
1、残留网络。
给定一个流网络G=(V,E)和流f,在输入流f后G的残留网络为Gf = (V,Ef),其中:
,即由所有剩下的容量大于0的边所组成。如下图a是从源点s输入流11和8后得到的残留网络b。
其中,最初的原图G也是最初的残留网络。b中的粗线表示一条增广路径。
2、增广路径。
增广路径是用Ford-Fulkerson解决最大流问题的关键。增广路径p是残留网络Gf中,从源点s到汇点t的一条简单路径,该路径的每条边的容量都大于0,这意味我们可以通过这条路径继续输入流。
由此我们可以得到用Ford-Fulkerson方法求取最大流问题的算法了,如下:
1、在Gf中找到一条增广路径,求取该条路径的最小容量:;
2、更新该条路径上所有边(u,v)的容量c(u,v)和f(u,v),同时还需要更新c(v,u)和f(v,u);
3、循环1和2直到残留网络中不再有增广路径了;
用伪代码表示如下:
Ford - Fulkerson(G, s, t){ for each edge(u, v) in E[G] { f[u, v] = 0; f[v, u] = 0; } while (there exists an augmenting path p) { cf(p) = min{ cf(u, v) :(u, v) is in p }; for each edge(u, v) in p { f[u, v] = f[u, v] + cf(p); f[v, u] = -f[u, v]; cf(u, v) = cf(u, v) - cf(p); cf(v, u) = -f[v, u]; } }}
根据以上算法,可以看出,唯一未解之处在于如何寻找该增广路径。根据寻找增广路径所使用的算法的不同,可以得到具体不同的算法。如果我们运用广度优先搜索来遍历整个Gf依次判断是否还存在增广路径,这种算法被称为Edmonds-Karp算法。
多源点多汇点最大流问题
对于该问题,我们可以引入一个超级源点,将其与各个源点相连,并且将相连边的容量设为无穷大,同理,也引入一个超级汇点,将其与各个汇点相连,且将相连边容量设为无穷,如此,该问题就成为了单源点单汇点问题了。
接下来我将给出用C++实现Edmonds-Karp算法的具体代码。首先我们约定:为了便于实现,所有的顶点将用数字1,2,3…编号,从下标为1开始存入数据,并且顶点编号和下标索引对应,即G[i]存储的是i号节点的邻接链表。
//Ford-Fulkerson方法求最大流,用广度优先搜索寻找增广路径,为Edmonds-Karp算法#include<iostream>#include<queue>#include<fstream>#include<vector>#define MAX 0x7fffffffusing namespace std;enum color{ WHITE, GRAY, BLACK };//用来标记顶点颜色struct edgeNode{//边节点 size_t adjvertex;//该边的关联的顶点 int capacity;//边当前容量 int flow;//边当前流量 edgeNode *nextEdge;//下一条边 edgeNode(size_t adj, int w) :adjvertex(adj), capacity(w), flow(0), nextEdge(nullptr){}};class AGraph{//有向图private: vector<edgeNode*> G; size_t nodenum;public: AGraph(size_t n) :nodenum(n){ G.resize(n + 1); } void initGraph();//初始化有向图 edgeNode* search(size_t, size_t);//查找边 edgeNode* addEdge(size_t, size_t, int);//向图中添加边 void deleteEdge(size_t, size_t);//有向图中删除边 bool BFS(size_t, size_t, vector<size_t>&);//广度优先搜索,返回一个前驱数组 int Edmonds_Karp(size_t, size_t);//Ford_Fulkerson方法的一种具体实现,采用广度BFS搜索增广路经 void print(); ~AGraph();};bool AGraph::BFS(size_t s, size_t t, vector<size_t> &p){ bool augment_path = false;//记录是否存在增广路径 queue<size_t> Q; vector<size_t> dis(nodenum + 1), color(nodenum + 1); for (size_t i = 1; i <= nodenum; ++i) { dis[i] = MAX; p[i] = i; color[i] = WHITE; } color[s] = GRAY; dis[s] = 0; p[s] = s; Q.push(s); while (!Q.empty()) { size_t u = Q.front(); if (u == t) augment_path = true;//若可以遍历到汇点,则存在 Q.pop(); edgeNode *curr = G[u]; while (curr != nullptr) { if (color[curr->adjvertex] == WHITE && curr->capacity > 0) { color[curr->adjvertex] = GRAY; dis[curr->adjvertex] = dis[u] + 1; p[curr->adjvertex] = u; Q.push(curr->adjvertex); } curr = curr->nextEdge; } color[u] = BLACK; } return augment_path;}int AGraph::Edmonds_Karp(size_t s, size_t t){//若用两个二维数组分别存储边(u,v)的flow和capacity,就可以省去search和addEdge了, //可以改善效率(但是提高不了渐进时间),不过增加了使用空间 vector<size_t> parent(nodenum + 1); while (BFS(s, t, parent))//用BFS寻找从s到t的增广路径,parent记录前驱 {//若存在 int increase_flow = MAX; size_t tmp = t; while (tmp != parent[tmp]) {//则确定该增广路径上的最小容量 edgeNode *curr = search(parent[tmp], tmp); if (curr->capacity < increase_flow) increase_flow = curr->capacity; tmp = parent[tmp]; } tmp = nodenum; while (tmp != parent[tmp]) {//然后对该路径上边增加流量,同时将减小当前容量 edgeNode *r = search(parent[tmp], tmp); r->flow = r->flow + increase_flow; r->capacity -= increase_flow; //当我们增加了f(u,v),意味着逆边(v,u)增加了容量c(v,u), //即使该边最初是不存在的,因为v可以将该流返回给u, //addEage功能请参考该函数注释 edgeNode *p = addEdge(tmp, parent[tmp], 0); p->flow = -r->flow;//根据流对称性获得对称边的当前流 p->capacity -= p->flow;//同时更新其当前容量 tmp = parent[tmp]; } } //没有增广路径时,说明已找到最大流 int maximum_flow = 0; edgeNode *p_s = G[s]; while (p_s != nullptr) {//统计源点的流总量即可 maximum_flow += p_s->flow; p_s = p_s->nextEdge; } return maximum_flow;}void AGraph::initGraph(){ size_t start, end; int w; ifstream infile("F:\\maximumflow.txt"); while (infile >> start >> end >> w) addEdge(start, end, w);}edgeNode* AGraph::search(size_t start, size_t end){ edgeNode *curr = G[start]; while (curr != nullptr && curr->adjvertex != end) curr = curr->nextEdge; return curr;}edgeNode* AGraph::addEdge(size_t start, size_t end, int capacity){//添加边 edgeNode *curr = search(start, end);//先查找 if (curr == nullptr) {//若边不存在,则添加 edgeNode *p = new edgeNode(end, capacity); p->nextEdge = G[start]; G[start] = p; return p; } return curr;//否则不添加,返回当前边地址}void AGraph::deleteEdge(size_t start, size_t end){ edgeNode *curr = search(start, end); if (curr != nullptr) { if (curr->adjvertex == end) { G[start] = curr->nextEdge; delete curr; } else { edgeNode *pre = G[start]; while (pre->nextEdge->adjvertex != end) pre = pre->nextEdge; pre->nextEdge = curr->nextEdge; delete curr; } }}inline void AGraph::print(){ for (size_t i = 1; i != G.size(); ++i) { edgeNode *curr = G[i]; cout << i; if (curr == nullptr) cout << " --> null"; else while (curr != nullptr) { cout << " --<" << curr->capacity << ">--> " << curr->adjvertex; curr = curr->nextEdge; } cout << endl; }}AGraph::~AGraph(){ for (size_t i = 1; i != G.size(); ++i) { edgeNode *curr = G[i], *pre; while (curr != nullptr) { pre = curr; curr = curr->nextEdge; delete pre; } }}const int nodenum = 6;/*1 2 16 1 5 132 3 12 2 5 103 5 9 3 6 204 3 7 4 6 45 2 4 5 4 14*/int main(){//测试数据采用的是上面的图,其中所有的顶点按顺时针方向从源点s(1号)开始依次编号 AGraph graph(nodenum); graph.initGraph(); graph.print(); cout << endl; int maximum_flow = graph.Edmonds_Karp(1, nodenum); cout << "The maximum flow is " << maximum_flow << endl; //graph.print(); getchar(); return 0;}
运行截图
输出中<>里面是各边最初的容量,可以理解为权值,只不过这个权值是可以改变的。
最大二分匹配
对于一个无向图G=(V,E),一个匹配时一个边子集M,其包含于E,且对于所有顶点v属于V,M中至多有一条边与v相关联。如果M中某条边与v关联,则说顶点v被匹配,否则是无匹配的。最大匹配就是最大势的匹配,即对于任何一个匹配M’,有|M| > |M’|。
二分图是这样一种图:确定点被分为L和R两部分,L和R不相交,且E中的每条边一个顶点在L,另一个顶点在R。我们假设二分图中的每个顶点至少有一条边与其关联。下图展现了匹配的概念。
那么如何寻找二分图的最大匹配呢?我们知道,对于二分图中的每个顶点v,至多能和M中的一条边关联,这就相当于对于v而言,从它出发的流最多为1,而且该边的容量最大也只能为1。因此,寻找二分图的最大匹配可以转换为在各边容量均为1的多源点多汇点的流网络中寻找最大流。对于上图,我们添加一个超级源点和一个超级汇点,得到单源点单汇点的流网络,如下图:
然后运行Edmonds-Karp算法即可得到最大匹配值,为3。
- 算法导论 26章 最大流(一)
- 算法导论26(最大流)
- 【算法导论】最大流算法
- 【算法导论】第二十六章最大流
- 算法导论 第二十六章 最大流
- 算法导论 ch26 最大流
- 算法导论之最大流
- 算法导论 最大流 FordFulkson
- 算法导论 最大流 Push-Relabel
- 算法导论第三章 最大子数组
- [算法导论]最大堆
- <每日一题>算法导论:最大股票收益
- 算法导论—最大流(Edmonds-Karp算法)
- 算法导论-----最大优先队列
- 【算法导论】最大二分匹配
- 最大优先队列--【算法导论】
- 《算法导论》笔记一
- 《算法导论》学习(一)
- 图片增加水印,图片缩放,图片切割服务
- rime 快捷键设置
- Myeclipse工具自定义Java注释!
- java 数学进制转换
- mysql 常用命令及解释
- 算法导论 26章 最大流(一)
- 一次完整GSM主被叫通话的信令流程
- MDK 使用 ST-Link v2 下载出现 target dll has been cancelled 的错误的解决方法
- windows 下使用gcc
- 程序的编译、链接与装载
- 网络编程基础函数
- 图片相识度比较
- 4.5 注入AspectJ切面
- Same binary weight