网络流(network flow)算法是一个又实用又有趣又重要还老少皆宜的算法。而且还可以用在实际生活中。
比如说,现在有105个血包(50个O型,36个A型,11个B型,8个AB型),分给100个伤员(45个O型,42个A型,10个B型,3个AB型)可能吗?
注:O型是万能输血者,AB型是万能接收者。除此之外,A型能输A型,B型能输B型。
转化为网络流问题:在上图中能不能有100单位的水从s流到t?(上图所有箭头从左往右)其中除了s和t之外的节点都不能提供、储藏、吸收水!
答案是不能。这张图最多只能流过99单位。有一个伤员得不到血袋了。
从蓝圈内到蓝圈外最多只能流99个单位!所以从s到t最多只能流99个单位!
那么怎么才能找到这个圈呢?
先随便在s注入一些水,形成了一个流。上图中已经放了45+36+10+3=94个单位的水了。
当一个流出现以后,同时会出现这个流的Residual Graph。所谓Residual Graph,就是把流从原图中线的容量减去,再添加一条和流反向的路径。注:没标数字的线容量是正无穷,正无穷减掉了流在这条线上的流量后,还是正无穷。
一个流的Residual Graph可以指导我们怎么扩大这个流的流量。
照着这条道路,给流再注入一些水。
得到了一个新流。当流产生的时候,流的Residual Graph也同时产生了。
随便弄个流-流诞生后,流的Residual Graph同时诞生-在Residual Graph中寻找s-t通路-找到了就利用该通路扩大流,产生新流,产生新流的Residual Graph......找不到就结束算法,大功告成。
这样,我们不仅找到了这幅图里的最大流量,还找到了开头的那个圈!
以上就是网络流的Ford-Fulkerson方法。时间复杂度O(mC)。m为边数,C为流量的最大可能值。
有一些技巧似乎可以加速该方法,比如给每个节点赋一个值,创造一个等价问题,使得Residual Graph中所有边非负,然后内嵌Dijkstra最短路算法。这样可以加速到O(mlogn)n为点数量。然而我看了三遍也没想清楚一些具体细节,以后用到了再说吧了。
还有一个挺有意思的问题。就是如果你在画Residual Graph的时候忘记画反向箭头了会怎样?显然会得到一个错误答案。但这个错误答案会不会总是大于等于正确答案乘以一个常数呢?我觉得答案是0.5,毕竟你没有利用到的流的Residual Graph中的反向边最多和这个流的流量相等。又可以找到例子使它恰好是0.5。
不仅是用在献血上,这个小小的算法还能用在飞机规划、图像前景背景分离、调节供需上。
本文参考了Jon Kleinberg和Eva Tardos的Algorithm Design。图片均由本文作者绘制。