最大流EK算法和Dinic算法 生成随机网络对比——实验报告

实验报告-比较分析最大流算法

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大小的网络求出最大流也只需要二十秒左右
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

章小雯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值