一.实验目的
- 掌握最大流算法思想。
- 学会用最大流算法求解应用问题。
二.实验步骤与结果
1.上述问题可按最大流问题求解:
假设有医生n个,现要安排元旦(共D’天)和五一(共D’’天)的排班,医生1的可安排日期为{元旦第1、2天;五一第1、D’’天},医生2的可安排日期为{元旦第2、D’天;五一第1、2天},医生n的可安排日期为{元旦第2天;五一第2、D’’天}。
图构建如下:
且该图满足实验的三个限制:
①每一个假日都有医生值班
②每人每个假期最多值班一天
③每个医生值班不得超过c日
则将排班问题转化为了最大流问题:如果求解最大流的结果等于总的假日天数,那么存在分配方案,否则无解。查看增广图每条边的权值,可以确定最终选择的是那些路径(即分配方案)。
求解最大流的方法:
(1)Ford-Fulkerson方法
①算法概述
在Ford-Fulkerson方法的每次迭代中,找出任意增广路径p,沿p每条边的流f加上其残留容量。
求最大流即找到一种符合个点流量限制的、多条不交叉的从s到t的路径,使最终获得的流量最大。可以通过dfs深度优先搜索获得从s到t的路径,但如果要获得符合条件的最优路径组合,用Ford-Fulkerson算法来判断每次获得路径是否是组合里的。
对于每次获得的路径(增广路径),我们可以将该路径反向(由原流网络变为残留网络),看是否能与剩余量形成连通路径(根据当前获得的残留容量)。如果有需要减去改变,重复操作,模拟纠错过程。最后完全没有连通路径时即计算完毕。
②伪代码
Ford-Fulkerson(G,s,t) |
for each(u,v)∈E(G) do f[u,v]←0 f[v,u]←0 while there exists a path p from s to t in the residual network Gf do cf(p) ←min{cf(u,v):(u,v)is in p} for each (u,v) in p do f[u,v]←f[u,v]+cf(p) f[v,u]← -f[u,v] |
(2)Edmonds-karp算法
①伪代码
Edmonds-karp算法伪代码: |
while true 通过bfs()找到增广路径p if p不存在 break; for 边e in p 更新e的权值 |
②算法步骤:
(1)从源点开始,用BFS找一条最短的增广路径,计算该路径上的残量最小值,累加到最大流值;
(2)沿着该路径修改流量值,实际是修改是残量网络的边权;
(3)重复上述步骤,直到找不到增广路时,此时得到的流就是最大流。
③正确性测试:
输入(2,2 , 2 , 1)
(即:医生数目=2、假期数目=2、每个假期的假日数目=2、每个医生的最多值班天数c=1)
④算法效率:
(3)Dinic算法
①算法概述:
首先对图进行BFS分层,搜索增广路径使用DFS搜索,使得一次搜索可以更新多条增广路上的权值,从而提高速度。
②伪代码
Dinic_func(): |
dfs(x,minval): //x为点序号,minval为到达点x的(最小)流 if x==t: //到达汇聚节点t返回 Return paths for y in x.adj: //遍历x的下一邻点,进行整一层的更新 renew(minval) //更新邻点边加入后(最小)流 res=dfs(y,minval): if res: reweight(edge(x,y)) return res return 0 while bfs(): while dfs() |
③算法效率:时间复杂度O(VE2)
(4)引入当前弧优化:
由于dfs一旦找到就返回,而一次bfs分层可能存在很多条相同长度的增广路,那么会在同一张图上跑多次dfs,而图不断更新,可用的边越来越少,我们试图跳过前面已经走过的“死路”,直接从未访问的路开始走。
在Dinic+弧优化算法下,因为一共e条边,而不管做多少次dfs,因为减少了对死路的判断,保证每次邻接表第一个邻居就是能走的活路,所以每次dfs走n步一定能够走到目标而不必回退,dfs的开销由O(e)变为O(n),而因为最多有e条路径,dfs需要最多做e次,所以每一轮的代价是O(ne),而因为外循环的bfs分层最多进行n次,所以总的代价是O(n2e)。
DFS_hu(x,flow): |
if x==s //汇点 return flow for i=cur[x] to x的最后一条弧 y=i指向的点 fxy=边xy的权值 r=dfs(y,min(flow,fxy)) if r!=0 更新边xy的权值 return r return 0 |
(5)引入多路增广策略:
如下图所示,普通dinic算法找到之后,直接全部退出,而多路增广优化的Dinuc算法找到之后,回溯到父节点,然后继续向下搜索,最后退栈的时候再更新边的权值,这样一次dfs可以进行多次更新。
伪代码:
DFS_add2(x,flow): |
if x==s //汇点 return flow temp =flow for y in 邻接[x] fxy=边xy的权值 r=dfs(y,min(flow,fxy)) if r!=0 更新边xy的权值 flow-= r if flow=0 return temp return temp-flow |
(6)ISAP
ISAP在dfs的同时就动态地更新节点的层次关系,减少bfs的次数,将dfs利用到极致。ISAP算法只需要一次bfs建立最初的层次关系。除此之外,ISAP算法还引入了“gap”优化,即如果某一高度的节点数目为0,直接判断没有路径并且结束搜索。因为按层次bfs保证层次是严格连续下降的,如果某一高度节点数目为0表示出现断层,无法到达。
DFS_ISAP(x,flow): |
if x==s //汇点 return flow temp =flow for y in 邻接[x] fxy=边xy的权值 r=dfs(y,min(flow,fxy)) if r!=0 更新边xy的权值 flow-= r if flow=0 return temp gap[level[x]]-- level[x]++ //调整高度 gap[level[x]]++ return temp-flow |
时间复杂度分析:
该算法不带访问控制数组,一个节点会被多次访问,最多有e条增广路径,而每次需要走过n个点到达汇点,所以复杂度O(ne),因为外循环需要判断源点的高度,而源点最多增高n次,总体复杂度为O(n2e)
2.算法对比:
由数据及绘图对比可明显看出,Dinic算法具有很好的优化效果,且适用在各种规模的数据集上。
3.控制变量分析:
由实验数据结果可以得出以下结论:
改变医生数目和假期数目和节假日数目算法测试运行时间随着参数数值规模增大呈现明显增加变化,但是最大值班天数对运行时间影响不大。