问题描述:求一个有向图从起点(s)到终点(t)流量的最大值:
如图所示:
解题算法:Ford-Fulkerson算法:
1.从零流开始
2.当剩余网络中有增广路径时(BFS),
3.找一条增广路径(DFS),增大流,
4.输出流量
算法步骤:
1.输入数据:
这个题目数据输入是一个很关键的点,我们用结构体来存储数据,但这里应该怎么存储图是一个值得思考的问题。经过尝试,我们发现,用结构体来存储边且用next来表示该始点的下一条边是一个很好的做法:
int h[MAX];//用于存储每一个顶点最后连接的是哪一个顶点;
int num = 0;//用于记录边的数量,记住,每一对平行边的序号一定相邻;
int s,t;
int p[105][105];//用于记录两点的相邻与否;
struct node{
int from;//该边的始点
int to;//该边的终点
int weight;//该边的权值
int next;//该边始点下一个连接的边
}Edge[MAX];
void AddEdge(int f, int t, int w)
{
Edge[++num].from = f;
Edge[num].to = t;
Edge[num].weight = w;
Edge[num].next = h[f];//第一个存储的next值就取0,以便于后面判定是否已经把这个点相邻的点找完(行45,行66)
h[f] = num;//用于下一次再有以f为始点的边记录
}
int main()
{
int N,i;
scanf("%d", &N);//点的总数;
s = 1;
t = N;
int a, b, c;
memset(h, 0, sizeof(h));
memset(p, 0, sizeof(p));
while(scanf("%d%d%d", &a, &b, &c) != EOF)
{
if(p[a][b] == 0)
{
AddEdge(a,b,c);
p[a][b] = num;
AddEdge(b,a,0);
p[b][a] = num;
}
else
{
Edge[p[a][b]].weight += c;
}
}
// for(i = 1; i <= num; i++)
// printf("from = %d to = %d weight = %d next = %d\n", Edge[i].from, Edge[i].to, Edge[i].weight, Edge[i].next);
int ans = 0;
while(BFS())
{
ans += DFS(s, INF);
printf("%d\n", ans);
}
printf("%d\n", ans);
system("pause");
return 0;
}
2.在剩余网络中找增广路径:
这部过程尤其容易弄混淆,因为我们只需要找出一条增广路径即可,如果有的话就直接返回1,如果没有就返回0。但是!!!这里有一个重要的东西需要我们去解决,那就是找到每一个点接下来应该接哪一个点,虽然在原图中这个点可能可以接很多很多其他的点,但是,在我们要找的增广路径中,这个点的后面就只能跟某个点(或某些点),这里我们引入一个数组d来记录这个点沿着可能的增广路径到s相隔几个点(因为一些bug,我们定义s到s为1,s后面一个点到s为2…)
int d[MAX];//用于记录到起点的距离,也可以当做在遍历时判定上一个点是否已经走过了的条件
bool BFS()
{
memset(d,0,sizeof(d));//每一次重新找剩余网络都需要更新d数组;
d[s] = 1;//其实在一般情况下,这里的起始点的d值应该为0才说得过去,但由于后面判定这个点是否已经走过是由d[i]是否为0来判定的,所以我们起始点用1
std::queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int q = Q.front();
Q.pop();
for(int i = h[q]; i != 0; i = Edge[i].next)
{
int to = Edge[i].to;
if(d[to] == 0 && Edge[i].weight != 0)
{
d[to] = d[q] + 1;
if(to == t)
{
for(int j = s; j <= t; j ++)
printf("%d ", d[j]);
printf("\n");
return true;
}
Q.push(to);
}
}
}
return d[t] != 0;
}
那第一轮BFS之后,我们的各个点的d值就出来了(如下图红色部分),这个d值就决定了待会进行DFS的时候,哪些点可以沿哪些塔顶的轨迹行走:
3.找到那条增广路径并增大流:
int DFS(int from, int flow)//from是当前进行遍历的点的编号,flow是当前流进from点的流量;
{
if(from == t)
return flow;
int sum = 0;//sum用来记录这一点from到底能够流多少流量;
int r = flow;//r在接下来用于记录这一点from流了一定的流量之后,还剩下多少流量可流出;
for(int i = h[from]; i && r; i = Edge[i].next)
{
int to = Edge[i].to;
if(d[to] == d[from] + 1 && Edge[i].weight != 0)
{
int key = DFS(to, Min(r, Edge[i].weight));//递归找到以这个点为起点能流过多少流量,流进的流量为r和权值的最小值;
if(key == 0)
d[to] = 0;//如果以这个点起不能再流进,那么这个点作废;
Edge[i].weight -= key;
Edge[i + 1].weight += key;//[i + 1]就是边[i]的平行边;
r -= key;
sum += key;
}
}
return sum;
}
每找到一条增光路径并增大流以后,原有路径会发生变化,原路径减去该增广路径的流量,原路径的反路径加上该增广路径的流量(至于正确性,那就是另外一个话题了,详情可以见《算法导论》),那么上面的图经过第一轮增大流之后变成了什么样子呢?
将0边删除后就是这个样子:
这一轮过后我们找到了总流量为35,接下来我们继续进行BFS和DFS,这个图接下来还有两部循环,分别如下:
第二次DFS后流量加了1
最后一次DFS后还是加了1,因此最后结果为37.
最后附上完整代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <queue>
#define MAX 100010
#define INF 0x3f3f3f3f
int h[MAX];//用于存储每一个顶点最后连接的是哪一个顶点;
int num = 0;//用于记录边的数量,记住,每一对平行边的序号一定相邻;
int s,t;
int p[105][105];//用于记录两点的相邻与否;
struct node{
int from;//该边的始点
int to;//该边的终点
int weight;//该边的权值
int next;//该边始点下一个连接的边
}Edge[MAX];
int Min(int a, int b)
{
return a < b ? a : b;
}
void AddEdge(int f, int t, int w)
{
Edge[++num].from = f;
Edge[num].to = t;
Edge[num].weight = w;
Edge[num].next = h[f];//第一个存储的next值就取0,以便于后面判定是否已经把这个点相邻的点找完(行45,行66)
h[f] = num;//用于下一次再有以f为始点的边记录
}
int d[MAX];//用于记录到起点的距离,也可以当做在遍历时判定上一个点是否已经走过了的条件
bool BFS()
{
memset(d,0,sizeof(d));//每一次重新找剩余网络都需要更新d数组;
d[s] = 1;//其实在一般情况下,这里的起始点的d值应该为0才说得过去,但由于后面判定这个点是否已经走过是由d[i]是否为0来判定的,所以我们起始点用1
std::queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int q = Q.front();
Q.pop();
for(int i = h[q]; i != 0; i = Edge[i].next)
{
int to = Edge[i].to;
if(d[to] == 0 && Edge[i].weight != 0)
{
d[to] = d[q] + 1;
if(to == t)
{
for(int j = s; j <= t; j ++)
printf("%d ", d[j]);
printf("\n");
return true;
}
Q.push(to);
}
}
}
return d[t] != 0;
}
int DFS(int from, int flow)//from是当前进行遍历的点的编号,flow是当前流进from点的流量;
{
if(from == t)
return flow;
int sum = 0;//sum用来记录这一点from到底能够流多少流量;
int r = flow;//r在接下来用于记录这一点from流了一定的流量之后,还剩下多少流量可流出;
for(int i = h[from]; i && r; i = Edge[i].next)
{
int to = Edge[i].to;
if(d[to] == d[from] + 1 && Edge[i].weight != 0)
{
int key = DFS(to, Min(r, Edge[i].weight));//递归找到以这个点为起点能流过多少流量,流进的流量为r和权值的最小值;
if(key == 0)
d[to] = 0;//如果以这个点起不能再流进,那么这个点作废;
Edge[i].weight -= key;
Edge[i + 1].weight += key;//[i + 1]就是边[i]的平行边;
r -= key;
sum += key;
}
}
return sum;
}
int main()
{
int N,i;
scanf("%d", &N);//点的总数;
s = 1;
t = N;
int a, b, c;
memset(h, 0, sizeof(h));
memset(p, 0, sizeof(p));
while(scanf("%d%d%d", &a, &b, &c) != EOF)
{
if(p[a][b] == 0)
{
AddEdge(a,b,c);
p[a][b] = num;
AddEdge(b,a,0);
p[b][a] = num;
}
else
{
Edge[p[a][b]].weight += c;
}
}
// for(i = 1; i <= num; i++)
// printf("from = %d to = %d weight = %d next = %d\n", Edge[i].from, Edge[i].to, Edge[i].weight, Edge[i].next);
int ans = 0;
while(BFS())
{
ans += DFS(s, INF);
printf("%d\n", ans);
}
printf("%d\n", ans);
system("pause");
return 0;
}