实验报告-比较分析最大流算法
1. 实验题目
- 算法1:
- 算法2:Dinic算法
2. 需求分析
随机生成最大流网络和边容量,(三种类型网络:1000大小,5000大小,1万大小),分别使用算法1和算法2解决上述问题,使用系统时钟测量各算法所需的实际时间,并进行比较
3. 概要设计
-
1、对于EK算法,我优先选用了熟悉的邻接矩阵来存储
-
2、该程序包含 3个函数——具体思路在题目中已经给出
-
① 主函数 main();
-
②广度优先遍历函数bfs(int s,int t);
-
——一次找到一条最大可行流,立刻构建剩余网络
-
构建剩余网络的关键步骤:
- while(进行迭代) - 1.从起点到终点BFS找增广路 - 2.更新残留网络: (1)正向边减去k (2)反向边增加k
-
-
③最大流函数MaxFlow(int s,int t);
-
-
3、对于Dinic算法,考虑到最后网络的复杂性和便捷性,我使用了链式向前星来存储网络;
- 关键思路:在每次寻找可行流之前,先用bfs构造层次网络,以一次获得最短路径(所有最大流)
- 之后再构建剩余网络Gf,由此减少了剩余网络的构建,降低时间复杂度
-
4、该程序包含了结构体以及5个函数:
-
①链式向前星结构体
struct ed { int v; int w; int next; } edge[N * N]; int n, cnt, head[N], dis[N], iter[N]; ll m; //edge结构体里面存放的是: //w:该条边指向的下一个顶点 //w:即该条边的权值 //next:表示与此条边的from顶点连接的其他边 //用head[from]来记录每一个顶点连接的其他边的最大编号,再edge[j].to就可以索引到下一个邻接边
-
②主函数 main();
-
③加边函数add(int u,int v,int w);
-
④广度优先遍历函数bfs(int s);
- 实现了层次网络的构建
-
⑤深度优先遍历函数dfs(int s,int t,int f);
- 调用一次:找到目前Gf的全部可行流,且一共只重构了一个剩余网络
-
⑥Dinic(int s,int t);
- 调用dfs直到不能找到新的流,此时的流则是我们的目标最大流
-
-
另外,用系统时钟进行计时,对不同大小的网络进行所耗时间的比较
-
5、 详细设计
//EK #include <stdio.h> #include <queue> #include <string.h> #include <algorithm> #include<iostream> #include<time.h> #define N 10030 #define inf 0x7f7f7f using namespace std; int n, m, e[N][N]; //邻接矩阵 int pre[N], flow[N]; //FF算法是邻接表+DFS,EK是邻接矩阵+BFS, 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++) {//n个点 if (e[u][v] > 0 && v != s && pre[v] == -1) { //注意不可以流回源点 pre[v] = u;//标记为已访问 q.push(v); flow[v] = min(flow[u], e[u][v]); //现在的流量是流过来的流量和可以流走的量的最小值 } } } if (pre[t] == -1) //如果没有到达终点,判错 return -1; return flow[t]; //返回流量 } int MaxFlow(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", &n) != EOF) { memset(e,0,sizeof(e)); e[1][n]=50; m = n * n / 5; memset(pre, -1, sizeof(pre)); while (m--) { u = rand() % (n + 1); v = rand() % (n + 1); w = 100 + rand() % 10000; //生成网络不难,但是我们需要可行的网络 。 e[u][v] += w; } clock_t startTime, endTime; startTime = clock(); printf("%d\n", MaxFlow(1, n)); endTime = clock();//计时结束 std::cout << "The run time is: " << (double)(endTime - startTime) << "ms" << std::endl; } return 0; }
//Dinic
#include <stdio.h>
#include <queue>
#include<time.h>
#include <string.h>
#include <algorithm>
#include<iostream>
#define N 10030
#define inf 0x7f7f7f
#define ll long long
using namespace std;
struct ed {
int v;
int w;
int next;
} edge[N * N];
int n, cnt, head[N], dis[N], iter[N];
ll m;
//edge结构体里面存放的是:
//w:该条边指向的下一个顶点
//w:即该条边的权值
//next:表示与此条边的from顶点连接的其他边
//用head[from]来记录每一个顶点连接的其他边的最大编号,再edge[j].to就可以索引到下一个邻接边
void add(int u, int v, int w) {
edge[cnt].v = v;//弧尾
edge[cnt].w = w;//流量
edge[cnt].next = head[u];
head[u] = cnt++;//标号//注意到从cnt0号位开始赋边,偶数为正向边,奇数为反相边
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();//u节点出队
for (int v = head[u]; v != -1; v = edge[v].next) {
ed G = edge[v];
if (dis[G.v] == -1 && G.w && G.v != s) {
//节点还未遍历 且 边容量>0且不是汇点
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) {//s连接的全部邻接边
//当前弧优化,&改变iter的值,如果又遍历到该点则直接跳过返回0
ed 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;//和1按位或,通过此种手段变成对应奇数
return d;
}
}
}
return 0;
}//找到目前Gf的全部可行流,且一共只重构了一个剩余网络
int Dinic(int s, int t) {
int ans = 0;
while (1) {
bfs(s);
if (dis[t] == -1)return ans;
//dist[t]为-1,已经毫无可行流的可能
for (int i = 1; i <= n; i++)iter[i] = head[i];
//初始化iter数组
int d;
while ((d = dfs(s, t, inf)) > 0)
ans += d;
}
}
int main() {
int u, v, w;
while (scanf("%d", &n) != EOF) {
memset(edge,0,sizeof(edge));
add(1, n, 50);
cnt = 0;
m = n * n / 5;
memset(head, -1, sizeof(head));
while (m--) {
u = rand() % (n + 1);
v = rand() % (n + 1);
w = 100 + rand() % 10000;
//生成网络不难,但是我们需要可行的网络 。
add(u, v, w);
}
clock_t startTime, endTime;
startTime = clock();
printf("%d\n", Dinic(1, n));
endTime = clock();//计时结束
std::cout << "The run time is: " << (double)(endTime - startTime) << "ms" << std::endl;
}
}
-
6、 调试分析
- 本次实验中最主要的调试在于如何生成不同大小的随机网络
- 我采取的第一种方法是让点数、边数都随机生成,起点、终点和边容量也随机生成,但是结果就是计算出来的最大流往往是零,因为在这种情况下,商城从起点和会点的可行网络实在是太少了;
- 之后我把网络设置成完全图,边数是n*(n-1)/2,在这个情况下,是一定能够形成我们所需要的可行网络的,但是此时出现的新问题是算法所耗的时间太大;
- 最后我把生成的边数进行适当减少,改为n*n/5,已达成最后折中的效果
- 本次实验中最主要的调试在于如何生成不同大小的随机网络
-
7. 使用说明以及测试结果
- 下面进行两个结果的运行时间对比——第一行输入节点数目n,第二行输出计算出的最大流,第三行输出运行耗费时间
- 对于EK算法的测试结果如下:(介于5000节点的网络等待时间已经过长,接近两分钟,便不再测试10000节点大小的网络)
-
-
对于Dinic的测试结果如下:
- 可以看出两种算法的时间复杂度对比显著,10000大小的网络求出最大流也只需要二十秒左右
-