上世纪 50 年代中期,美国空军研究员 Theodore E. Harris 和退伍少将 Frank S. Ross 联合写了一份研究苏联到其东欧卫星国铁路网的报告,该报告于 1999 年解密
铁路网可以看作加权有向图
两个问题
Ford 和 Fulkerson 将最大流问题的形式化归功于 Harris 和 Ross,但 Harris 和 Ross 又致谢了 George Dantzig 协助形式化了该问题
给定流网络$\Gcal = (\Vcal, \Ecal)$,源点$s$,汇点$t$
流$f: \Ecal \mapsto \Rbb$是实值函数且满足流量守恒:对$\forall u \in \Vcal \setminus \{ s,t \}$有
$$ \begin{align*} \quad \sum_{v \in \Vcal} f(v,u) = \sum_{v \in \Vcal} f(u,v) \end{align*} $$
流值$|f| \triangleq \sum_{v \in \Vcal} f(s,v) - \sum_{v \in \Vcal} f(v,s)$,即源点$s$的净流出流量
容量$c: \Ecal \mapsto \Rbb^+$是非负实值函数
容量限制:对$\forall (u,v) \in \Ecal$有$0 \le f(u,v) \le c(u,v)$
满足容量限制的流称为可行的 (feasible)
$$ \begin{align*} \quad & \begin{cases} f(s,v_1) = 11 \\ f(s,v_2) = 8 \\ f(v_1,v_3) = 12 \\ f(v_2,v_1) = 1 \\ f(v_2,v_4) = 11 \\ f(v_3,v_2) = 4 \\ f(v_4,v_3) = 7 \\ f(v_3,t) = 15 \\ f(v_4,t) = 4 \end{cases} \\[10px] & |f| = \sum_{v \in \Vcal} f(s,v) - \sum_{v \in \Vcal} f(v,s) = 11 + 8 = 19 \end{align*} $$
最大流问题:在给定的流网络中找一个流值最大的流
流网络中无反向平行边
对反向平行边$(v_1,v_2)$、$(v_2,v_1)$,选择其中一条,比如$(v_1,v_2)$,加入新结点$v$,将其分为两条边$(v_1,v)$和$(v,v_2)$,并将两条边的容量设为被替代掉的边的容量,即$c(v_1,v) = c(v,v_2) = c(v_1,v_2)$
可以证明,转换后的网络与原网络等价
流网络中只有单一的源点和汇点
加入超级源点$s$和超级汇点$t$,$c(s,s_i) = \infty$,$c(t_j,t) = \infty$
可以证明,转换后的网络与原网络等价
给定流网络$\Gcal = (\Vcal, \Ecal)$,源点$s$,汇点$t$
切割$(\Scal, \Tcal)$将$\Vcal$分成$\Scal$和$\Tcal = \Vcal \setminus \Scal$使得$s \in \Scal$、$t \in \Tcal$
切割$(\Scal,\Tcal)$的容量为$c(\Scal, \Tcal) = \sum_{u \in \Scal} \sum_{v \in \Tcal} c(u,v)$
最小切割问题:在给定的流网络中找一个容量最小的切割
切割$(\Scal, \Tcal)$:$\Scal = \{ s, v_1, v_2 \}$,$\Tcal = \{ t, v_3, v_4 \}$
切割$(\Scal,\Tcal)$的容量$c(\Scal, \Tcal) = 12 + 14 = 26$
该切割不是最小切割,$(\{s,v_1,v_2,v_4\},\{v_3,t\})$是更小的切割
引理:任意流值不超过任意切割的容量
$$ \begin{align*} \quad |f| & = \sum_{v \in \Vcal} f(s,v) - \sum_{v \in \Vcal} f(v,s) = \sum_{u \in \Scal} \left( \sum_{v \in \Vcal} f(u,v) - \sum_{v \in \Vcal} f(v,u) \right) \\ & = \sum_{u \in \Scal} \left( \sum_{v \in \Scal} f(u,v) + \sum_{v \in \Tcal} f(u,v) - \sum_{v \in \Scal} f(v,u) - \sum_{v \in \Tcal} f(v,u) \right) \\ & = \sum_{u \in \Scal} \sum_{v \in \Tcal} f(u,v) - \sum_{u \in \Scal} \sum_{v \in \Tcal} f(v,u) \\ & \le \sum_{u \in \Scal} \sum_{v \in \Tcal} f(u,v) \le \sum_{u \in \Scal} \sum_{v \in \Tcal} c(u,v) = c(\Scal, \Tcal) \end{align*} $$
对任意流网络,存在最大流等于最小切割
最大流最小切割定理是线性规划中强对偶成立的一个特例
给定流网络$\Gcal = (\Vcal, \Ecal)$和流$f$,定义残存网络$\Gcal_f = (\Vcal, \Ecal_f)$来记录每条边上的流量可以修改的极限
若$0 < f(u,v) < c(u, v)$
若$f(u,v) = c(u, v)$,则正向边$(u,v)$不加入,没有可增加的余地
若$f(u,v) = 0$,则反向边$(v,u)$不加入,没有可减少的余地
残存网络$\Gcal_f = (\Vcal, \Ecal_f)$中边$(u,v)$的残存容量$c_f(u,v)$定义为
$$ \begin{align*} \quad c_f(u,v) = \begin{cases} c(u,v) - f(u,v), & (u,v) \in \Ecal \\ f(v,u), & (v,u) \in \Ecal \\ 0, & \text{其它} \end{cases} \end{align*} $$
残存容量定义中的前两种情形是互斥的,因为我们约定了给定的流网络中不存在反向平行边
给定流网络$\Gcal = (\Vcal, \Ecal)$和流$f$,设$p$是残存网络$\Gcal_f$中一条从源点$s$到汇点$t$的路径,称为增广路径 (augmenting path)
增广路径的残存容量是所有边上残存容量的最小值
$$ \begin{align*} \quad c_f (p) = \min \{ c_f (u,v): (u,v) \in p \} \end{align*} $$
下图蓝色边构成一条增广路径,$c_f (p) = \min \{ 5,4,5 \} = 4$
若残存网络$\Gcal_f$包含增广路径,则可根据残存容量增加流
$$ \begin{align*} \quad f'(u,v) = \begin{cases} f(u,v) + c_f(p), & (u,v) \in p \\ f(u,v) - c_f(p), & (v,u) \in p \\ f(u,v), & \text{其它} \end{cases} \end{align*} $$
增广路径的残存容量为$4$,原流网络的流值可以增大$4$
可以证明$f'$是原网络中的一个流,流值$|f'| = |f| + c_f (p) > |f|$
流量守恒:对任意点$v \in \Vcal \setminus \{ s,t \}$
若$v \not \in p$,进入、离开点$v$的流量不变,显然满足流量守恒
若$v \in p$,设$p = \langle s, \ldots, u, v, w, \ldots, t \rangle$,分四种情况:
流值为源点$s$的净流出流量,源点$s \in p$,故流值增加$c_f(p)$
容量限制:对任意边$(u,v) \in \Ecal$
若$(u,v), (v,u) \not \in p$,则$(u,v)$的流量不变
若$(u,v) \in p$,则$(u,v)$的流量增加
$$ \begin{align*} \quad f' (u,v) & = f(u,v) + c_f(p) \\ & \le f(u,v) + c_f(u,v) = f(u,v) + c(u,v) - f(u,v) = c(u,v) \end{align*} $$
若$(v,u) \in p$,则$(u,v)$的流量减少
$$ \begin{align*} \quad f' (u,v) & = f(u,v) - c_f(p) \\ & \ge f(u,v) - c_f(v,u) = f(u,v) - f(u,v) = 0 \end{align*} $$
若残存网络$\Gcal_f$不包含任何增广路径,即$s$、$t$不连通,构造切割$(\Scal, \Tcal)$,其中$\Scal$为与$s$连通的所有结点,$\Tcal = \Vcal \setminus \Scal$
对$\forall u \in \Scal$、$\forall v \in \Tcal$,边$(u,v) \not \in \Gcal_f$,否则$v$也与$s$连通
由引理知此时$f$为最大流、$(\Scal, \Tcal)$为最小切割
最大流最小切割定理直接给出了 Ford-Fulkerson 算法:
算法没有明确指定如何寻找增广路径
def initialize_flow(c): # 零初始化 f = {} for u in c: f[u] = {} for u in c: for v in c[u]: f[u][v] = 0 return f def res_net(c, f): # 计算残存网络 c_f = {} for u in c: c_f[u] = {} for u in c: for v in c[u]: if f[u][v] == c[u][v]: # 满流量 c_f[v][u] = c[u][v] elif f[u][v] == 0: # 无流量 c_f[u][v] = c[u][v] else: c_f[u][v] = c[u][v] - f[u][v] # 正向边容量等于剩余容量 c_f[v][u] = f[u][v] # 反向边容量等于当前流量 return c_f def dfs_rec(c_f, pre, visited, u): # 深度优先 递归 if u == "t": return for v in c_f[u].keys(): # 残存网络中 u -> v if v not in visited: visited.append(v) pre[v] = u dfs_rec(c_f, pre, visited, v) def xxfs_iter(c_f, n): pre, visited, l = {}, ["s"], ["s"] # 前驱表 访问标记 用于做搜索的数据结构 while l: u = l.pop(n) # 弹出第n个元素 for v in c_f[u].keys(): # 残存网络中 u -> v if v not in visited: visited.append(v) l.append(v) pre[v] = u if v == "t": return pre return pre def longest_path(c_f): # 穷举检查 带环有向图中寻找最长简单路径是NP难的 nodes = [] # 存储全部中间结点 for u in c_f: if u != "s" and u != "t": nodes.append(u) pre = {} for i in range(len(nodes), 0, -1): combination = list(itertools.combinations(nodes, i)) # 按结点数从多到少遍历幂集 for comb in combination: permutation = list(itertools.permutations(comb)) # 对于某个结点子集 生成全排列 for perm in permutation: find_path = True if perm[0] not in c_f["s"].keys(): # 如果没有 s -> 第一个点 find_path = False if "t" not in c_f[perm[-1]].keys(): # 如果没有 最后一个点 -> t find_path = False for j in range(1, len(perm)): if perm[j] not in c_f[perm[j - 1]].keys(): find_path = False break if find_path: pre["t"] = perm[-1] for j in range(len(perm) - 1, 0, -1): pre[perm[j]] = perm[j - 1] pre[perm[0]] = "s" return pre return pre def max_res_cap(c_f): # 类似于最小生成树的Prim算法 pre, weight, in_tree, pq = {}, {}, [], PriorityQueue() for v in c_f: weight[v] = -1 # 记录还没加入树中的点与树中结点的边的最大容量 pq.put([None, "s"]) while not pq.empty(): _, u = pq.get() # 获取队首元素点u if u not in in_tree: in_tree.append(u) for v in c_f[u].keys(): # 残存网络中 u -> v if v not in in_tree and weight[v] < c_f[u][v]: weight[v] = c_f[u][v] pq.put([-weight[v], v]) # 最小优先队列找最大容量 故取反加入 pre[v] = u return pre def increase_flow(c_f, f, pre): v, path = "t", [] while True: # 根据前驱表生成增广路径 path.append((pre[v], v)) v = pre[v] if v == "s": break path.reverse() print("增广路径:", path) res_cap_edge = [] # 存储增广路径上边的容量 for u, v in path: res_cap_edge.append(c_f[u][v]) res_cap = min(res_cap_edge) # 确定残存容量 print("残存容量:", res_cap) for u, v in path: if v in f[u].keys(): f[u][v] += res_cap # 正向边流量增大 else: f[v][u] -= res_cap # 反向边流量减小 def calculate_flow(f): value = 0 for v in f["s"].keys(): value += f["s"][v] return value def ford_fulkerson(c, mode): f, i = initialize_flow(c), 1 # 初始化流值为零 while True: print("第%d轮" % i) print("当前的流:", f) c_f = res_net(c, f) # 计算残存网络 print("残存网络:", c_f) match mode: case 1: # 深度优先 递归 pre = {} dfs_rec(c_f, pre, ["s"], "s") case 2: # 深度优先 循环 栈 pre = xxfs_iter(c_f, -1) case 3: # 广度优先 循环 队列 or 最少边数 pre = xxfs_iter(c_f, 0) case 4: # 最多边数 pre = longest_path(c_f) case 5: # 最大残存容量 pre = max_res_cap(c_f) case _: print("mode取值必须是{1,2,3,4,5}!") return if "t" not in pre.keys(): # 若汇点、源点已不再连通 print("最大流值为%d" % calculate_flow(f)) return else: increase_flow(c_f, f, pre) i += 1 c = { # 容量 "s": {"v1": 16, "v2": 13}, # c(s,v1) = 16, c(s,v2) = 13 v1 -- v3 "v1": {"v3": 12}, # c(v1,v3) = 12 / | / | \ "v2": {"v1": 4, "v4": 14}, # c(v2,v1) = 4, c(v2,v4) = 14 s | / | t "v3": {"v2": 9, "t": 20}, # c(v3,v2) = 9, c(v3,t) = 20 \ | / | / "v4": {"v3": 7, "t": 4}, # c(v4,v3) = 7, c(v4,t) = 4 v2 -- v4 "t": {} } ford_fulkerson(c, mode=5) # ----------------------------------------------------- # 第1轮 # 当前的流: {'s': {'v1': 0, 'v2': 0}, 'v1': {'v3': 0}, 'v2': {'v1': 0, 'v4': 0}, 'v3': {'v2': 0, 't': 0}, 'v4': {'v3': 0, 't': 0}, 't': {}} # 残存网络: {'s': {'v1': 16, 'v2': 13}, 'v1': {'v3': 12}, 'v2': {'v1': 4, 'v4': 14}, 'v3': {'v2': 9, 't': 20}, 'v4': {'v3': 7, 't': 4}, 't': {}} # 增广路径: [('s', 'v1'), ('v1', 'v3'), ('v3', 't')] # 残存容量: 12 # 第2轮 # 当前的流: {'s': {'v1': 12, 'v2': 0}, 'v1': {'v3': 12}, 'v2': {'v1': 0, 'v4': 0}, 'v3': {'v2': 0, 't': 12}, 'v4': {'v3': 0, 't': 0}, 't': {}} # 残存网络: {'s': {'v1': 4, 'v2': 13}, 'v1': {'s': 12}, 'v2': {'v1': 4, 'v4': 14}, 'v3': {'v1': 12, 'v2': 9, 't': 8}, 'v4': {'v3': 7, 't': 4}, 't': {'v3': 12}} # 增广路径: [('s', 'v2'), ('v2', 'v4'), ('v4', 'v3'), ('v3', 't')] # 残存容量: 7 # 第3轮 # 当前的流: {'s': {'v1': 12, 'v2': 7}, 'v1': {'v3': 12}, 'v2': {'v1': 0, 'v4': 7}, 'v3': {'v2': 0, 't': 19}, 'v4': {'v3': 7, 't': 0}, 't': {}} # 残存网络: {'s': {'v1': 4, 'v2': 6}, 'v1': {'s': 12}, 'v2': {'s': 7, 'v1': 4, 'v4': 7}, 'v3': {'v1': 12, 'v2': 9, 't': 1, 'v4': 7}, 'v4': {'v2': 7, 't': 4}, 't': {'v3': 19}} # 增广路径: [('s', 'v2'), ('v2', 'v4'), ('v4', 't')] # 残存容量: 4 # 第4轮 # 当前的流: {'s': {'v1': 12, 'v2': 11}, 'v1': {'v3': 12}, 'v2': {'v1': 0, 'v4': 11}, 'v3': {'v2': 0, 't': 19}, 'v4': {'v3': 7, 't': 4}, 't': {}} # 残存网络: {'s': {'v1': 4, 'v2': 2}, 'v1': {'s': 12}, 'v2': {'s': 11, 'v1': 4, 'v4': 3}, 'v3': {'v1': 12, 'v2': 9, 't': 1, 'v4': 7}, 'v4': {'v2': 11}, 't': {'v3': 19, 'v4': 4}} # 最大流值为23
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+8}{\longrightarrow} 12 \overset{+7}{\longrightarrow} 19 \overset{+4}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+8}{\longrightarrow} 12 \overset{+7}{\longrightarrow} 19 \overset{+4}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+7}{\longrightarrow} 11 \overset{+12}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+7}{\longrightarrow} 11 \overset{+12}{\longrightarrow} 23$
流值变化$0 \overset{+12}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+7}{\longrightarrow} 23$
流值变化$0 \overset{+12}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+7}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+4}{\longrightarrow} 8 \overset{+4}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+3}{\longrightarrow} 19 \overset{+1}{\longrightarrow} 20 \overset{+3}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+4}{\longrightarrow} 8 \overset{+4}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+3}{\longrightarrow} 19 \overset{+1}{\longrightarrow} 20 \overset{+3}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+4}{\longrightarrow} 8 \overset{+4}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+3}{\longrightarrow} 19 \overset{+1}{\longrightarrow} 20 \overset{+3}{\longrightarrow} 23$
流值变化$0 \overset{+4}{\longrightarrow} 4 \overset{+4}{\longrightarrow} 8 \overset{+4}{\longrightarrow} 12 \overset{+4}{\longrightarrow} 16 \overset{+3}{\longrightarrow} 19 \overset{+1}{\longrightarrow} 20 \overset{+3}{\longrightarrow} 23$
流值变化$0 \overset{+12}{\longrightarrow} 12 \overset{+7}{\longrightarrow} 19 \overset{+4}{\longrightarrow} 23$
流值变化$0 \overset{+12}{\longrightarrow} 12 \overset{+7}{\longrightarrow} 19 \overset{+4}{\longrightarrow} 23$
若边的容量均为整数,则最大流$f^\star$在各个边上的流量也是整数
每轮迭代流值至少增加$1$,故最多迭代$O(|f^\star|)$轮
Ford-Fulkerson 算法的总时间复杂度为$O(|\Ecal| |f^\star|)$
Jack Edmonds、Richard Karp 发现时间复杂度$O(|\Ecal| |f^\star|)$是紧的
c = { # 容量 v "s": {"u": 100, "v": 100}, # c(s,u) = 100, c(s,v) = 100 / | \ "u": {"v": 1, "t": 100}, # c(u,v) = 1, c(u,t) = 100 s | t "v": {"t": 100}, # c(v,t) = 100 \ | / "t": {} # # u } ford_fulkerson(c, mode=4) # ------------------------------------ # 第1轮 # 当前的流: {'s': {'u': 0, 'v': 0}, 'u': {'v': 0, 't': 0}, 'v': {'t': 0}, 't': {}} # 残存网络: {'s': {'u': 100, 'v': 100}, 'u': {'v': 1, 't': 100}, 'v': {'t': 100}, 't': {}} # 增广路径: [('s', 'u'), ('u', 'v'), ('v', 't')] # 残存容量: 1 # 第2轮 # 当前的流: {'s': {'u': 1, 'v': 0}, 'u': {'v': 1, 't': 0}, 'v': {'t': 1}, 't': {}} # 残存网络: {'s': {'u': 99, 'v': 100}, 'u': {'s': 1, 't': 100}, 'v': {'u': 1, 't': 99}, 't': {'v': 1}} # 增广路径: [('s', 'v'), ('v', 'u'), ('u', 't')] # 残存容量: 1 # 第3轮 # 当前的流: {'s': {'u': 1, 'v': 1}, 'u': {'v': 0, 't': 1}, 'v': {'t': 1}, 't': {}} # 残存网络: {'s': {'u': 99, 'v': 99}, 'u': {'s': 1, 'v': 1, 't': 99}, 'v': {'s': 1, 't': 99}, 't': {'u': 1, 'v': 1}} # 增广路径: [('s', 'u'), ('u', 'v'), ('v', 't')] # 残存容量: 1 # 第4轮 # 当前的流: {'s': {'u': 2, 'v': 1}, 'u': {'v': 1, 't': 1}, 'v': {'t': 2}, 't': {}} # 残存网络: {'s': {'u': 98, 'v': 99}, 'u': {'s': 2, 't': 99}, 'v': {'s': 1, 'u': 1, 't': 98}, 't': {'u': 1, 'v': 2}} # 增广路径: [('s', 'v'), ('v', 'u'), ('u', 't')] # 残存容量: 1 # 第5轮 # 当前的流: {'s': {'u': 2, 'v': 2}, 'u': {'v': 0, 't': 2}, 'v': {'t': 2}, 't': {}} # 残存网络: {'s': {'u': 98, 'v': 98}, 'u': {'s': 2, 'v': 1, 't': 98}, 'v': {'s': 2, 't': 98}, 't': {'u': 2, 'v': 2}} # 增广路径: [('s', 'u'), ('u', 'v'), ('v', 't')] # 残存容量: 1 # ... # 第201轮 # 当前的流: {'s': {'u': 100, 'v': 100}, 'u': {'v': 0, 't': 100}, 'v': {'t': 100}, 't': {}} # 残存网络: {'s': {}, 'u': {'s': 100, 'v': 1}, 'v': {'s': 100}, 't': {'u': 100, 'v': 100}} # 最大流值为200
若边的容量均为有理数,可将有理数表示成既约分数的形式,设所有分母的最小公倍数为$d$,将所有边的容量乘以$d$可全部变成整数,根据 Ford-Fulkerson 算法求得最大流后再除以$d$就是原流网络的最大流
若存在边的容量为无理数,则 Ford-Fulkerson 算法可能无法停止,甚至极限流值不等于正确流值
1993 年,Uri Zwick 提出如下 6 个点的流网络,其中$\phi$为黄金分割比,满足$\phi^2 + \phi = 1$
假设第$1$轮的增广路径为$s \rightarrow b \rightarrow c \rightarrow t$,之后中间三条边的残存容量分别为$1$、$0$、$\phi$
假设某轮后中间三条边的残存容量分别为$\phi^{k-1}$、$0$、$\phi^k$
轮次 | 增广路径 | 流值增量 | 残存容量 |
---|---|---|---|
$1$ | $(B)$ | $\phi^k$ | $\phi^{k-1} - \phi^k = \phi^{k+1}$、$\phi^k$、$0$ |
$2$ | $(C)$ | $\phi^k$ | $\phi^{k+1}$、$0$、$\phi^k$ |
$3$ | $(B)$ | $\phi^{k+1}$ | $0$、$\phi^{k+1}$、$\phi^k - \phi^{k+1} = \phi^{k+2}$ |
$4$ | $(A)$ | $\phi^{k+1}$ | $\phi^{k+1}$、$0$、$\phi^{k+2}$ |
每$4$轮一个周期,残存容量从$\phi^{k-1}$、$0$、$\phi^k$变为$\phi^{k+1}$、$0$、$\phi^{k+2}$
每$4$轮一个周期,残存容量从$\phi^{k-1}$、$0$、$\phi^k$变为$\phi^{k+1}$、$0$、$\phi^{k+2}$
经过$4n+1$轮后
$$ \begin{align*} \quad |f| & = 1 + 2 \sum_{i=1}^{2n} \phi^i = 1 + 2 \frac{\phi(1 - \phi^{2n})}{1 - \phi} \\ & \longrightarrow 1 + 2 \frac{\phi}{1 - \phi} = 2 + \sqrt{5} \ll 2 X +1 \end{align*} $$
计算机内部无法精确表示无理数,研究无理数容量有何意义?
Ford-Fulkerson 算法的时间复杂度上界为$O(|\Ecal| |f^\star|)$
Ford-Fulkerson 算法的时间复杂度下界?
流分解定理:对任意流$f$,可将其分解为$s$到$t$的路径和有向环的正线性组合,$f(u,v) > 0$当且仅当$(u,v)$在路径或环中至少出现一次,路径和环的总数不超过$|\Ecal|$
在流网络中添加边$(t,s)$并令$f(t,s) = |f|$,于是流网络流值为零且所有点流量守恒,这样的流称为环流 (circulation)
引理:任意环流$f$可分解为不超过$\max\{ 0, \# f - 1 \}$个有向环的正线性组合,其中$\# f = \{ |(u,v)| \mid f(u,v) > 0 \}$为有非零流量的边数
分三种情况:
若流值为零,则$\# f = 0$,引理显然成立
若流值非零,且$f$只在单个有向环上有非零流量,则$\# f \ge 2$,引理显然成立
若流值非零,且$f$不只在单个有向环上有非零流量,对$\# f$的大小进行归纳
假设引理对非零流量边数$< \# f$的环流都成立
考虑从$(u,v)$开始、所有边都有非零流量的路径$u \rightarrow v \rightarrow \cdots$,由于所有点流量守恒,因此该路径可不断延长直至某个点第二次出现,于是出现环$C$
设$F = \min_{(u,v) \in C} f(u,v)$,考虑新的流
$$ \begin{align*} \quad f'(u,v) = \begin{cases} f(u,v) - F, & (u,v) \in C \\ f(u,v), & \text{其它} \end{cases} \end{align*} $$
设$e \in C$、$f(e) = F$,于是$f'(e) = 0$、$\# f' \le \# f - 1$,由归纳假设,$f'$可分解为不超过$\max\{ 0, \# f' - 1 \}$个有向环的正线性组合,于是$f$可分解为不超过$\max\{ 0, \# f' \} \le \max\{ 0, \# f - 1 \}$个有向环的正线性组合
对任意流$f$,添加边$(t,s)$构造环流$f'$,则$\# f' = \# f + 1$
由引理,$f'$可分解为不超过$\max\{ 0, \# f' - 1 = \# f \}$个有向环的正线性组合,对包含$(t,s)$的环,删掉$(t,s)$变成$s$到$t$的路径,于是$f$分解成不超过$\max\{ 0, \# f \} \le |\Ecal|$个$s$到$t$的路径或有向环的正线性组合
流分解定理可导出流分解算法
寻找$s$到$t$的路径或有向环需$O(|\Vcal|)$时间
流分解算法每轮至少可以让一条边的流量变为零,因此最多迭代$O(|\Ecal|)$轮,总的时间复杂度为$O(|\Vcal||\Ecal|)$
流分解算法每轮至少可以让一条边的流量变为零,因此最多迭代$O(|\Ecal|)$轮,总的时间复杂度为$O(|\Vcal||\Ecal|)$
存在流网络其中的环流可被分解成$|\Ecal| - |\Vcal| + 1 = O(|\Ecal|)$个有向环,且无法被分解成更少个有向环
类 Ford-Fulkerson 算法:每轮迭代寻找一条路径或是有向环,增加其上流值的算法,时间复杂度下界为$\Omega(|\Vcal| |\Ecal|)$
Ford-Fulkerson 算法的低效源自于不合适的增广路径寻找方法
70 年代初期,Jack Edmonds、Richard Karp 提出两个准则
Ford、Fulkerson 原始的最大流论文中也提到了准则二,作为寻找增广路径的启发式方法;1970年,苏联数学家 Yefim Dinitz 提出了准则二的一个变种,当时他还是 Georgy Adelson-Velsky 算法课上的学生,他的算法多保存了一些中间结果,将时间复杂度从$O(|\Vcal| |\Ecal|^2)$降到$O(|\Vcal|^2 |\Ecal|)$
准则 Ⅰ:寻找残存容量最大的增广路径
每轮迭代:以$s$为根结点生长树$\Tcal$,不断向$\Tcal$中加入残存容量最大的边 (类似最小生成树的 Prim 算法),直至$\Tcal$中包含从$s$到$t$的路径,时间复杂度为$O(|\Ecal|\lg|\Ecal|) = O(|\Ecal|\lg|\Vcal|)$
迭代轮数:设$\Gcal_f$中的最大流为$f'$,根据流分解定理,$f'$最多可分解到$|\Ecal|$条路径上,因此至少有一条路径的流量为$|f'|/|\Ecal|$,选其为增广路径,剩余流量$\le f' - |f'|/|\Ecal| = |f'| (1 - 1/|\Ecal|)$
若存在边的容量为无理数,Edmonds-Karp算法Ⅰ可能无法停止
初始流网络即为残存网络,最大流为$f^\star$,每轮迭代后至少衰减到原来$1 - 1/|\Ecal|$,经过$|\Ecal| \ln |f^\star|$轮迭代后,流值小于$1$
$$ \begin{align*} \quad |f^\star| (1 - 1/|\Ecal|)^{|\Ecal| \ln |f^\star|} < |f^\star| \exp (- \ln |f^\star|) = 1 \end{align*} $$
若边的容量均为整数,则流值亦为整数,小于$1$就意味着等于零,即$f$是最大流,总时间复杂度为$O(|\Ecal|^2 \lg|\Vcal| \ln |f^\star|)$
相较之前的$O(|\Ecal| |f^\star|)$,$|f^\star|$移到了$\ln()$里面,因此时间复杂度是多项式级
这里用到了不等式$1 - x < \exp(-x)$
准则 Ⅱ:寻找边数最少的增广路径
每轮迭代:残存网络中边的数目不超过$2 |\Ecal|$,采用 BFS 找一条从$s$到$t$的最短路径的时间是$O(|\Ecal|)$
迭代轮数:依赖下面两个引理
每轮选增广路径至少产生一条关键边,总迭代轮数$O(|\Vcal||\Ecal|)$,总时间复杂度$O(|\Vcal| |\Ecal|^2)$,与最大流值无关
引理:对$\forall v \in \Vcal \setminus \{ s, t \}$,$\delta_f (v)$随着迭代单调递增
直觉上,随着迭代进行,残存网络中的正向边越来越少,反向边越来越多,从$s$到其它点的路径越来越曲折
证明:假设存在结点$v$在某轮迭代后,$\delta_f (s,v)$减少
设$f \rightarrow f'$是$\delta_f (s, v)$首次变小的轮次,$\delta_f (s, v) > \delta_{f'} (s, v)$,且$v$是最短路径上离$s$最近的点
设$\Gcal_{f'}$中$s$到$v$的最短路径为$s \rightsquigarrow u \rightarrow v$,$\delta_{f'} (s, u) = \delta_{f'} (s, v) - 1$
$\delta_f (s, v) > \delta_{f'} (s, v)$,$\delta_{f'} (s, u) = \delta_{f'} (s, v) - 1$
根据$v$的选取,$\delta_f (s, u) \le \delta_{f'} (s, u)$
易知$(u,v) \not \in \Ecal_f$,否则
$$ \begin{align*} \quad \delta_{f'} (s, v) < \delta_f (s, v) \le \delta_f (s, u) + 1 \le \delta_{f'} (s, u) + 1 = \delta_{f'} (s, v) \end{align*} $$
$\Gcal_f$中得到的增广路径在边$(v,u)$上有流量,抵消了$(u,v)$上的部分流量,故$(v,u)$是$\Gcal_f$中$s$到$u$的最短路径上的最后一条边
$$ \begin{align*} \quad \delta_f (s, v) = \delta_f (s, u) - 1 \le \delta_{f'} (s, u) - 1 = \delta_{f'} (s, v) - 2 \end{align*} $$
引理:每条边成为关键边的次数不超过$|\Vcal|/2$次
$(u,v)$第一次成为关键边时,$\delta_f (s, v) = \delta_f (s, u) + 1$
根据前一个引理,$\delta_f (s, v) \le \delta_{f'} (s, v)$,于是
$$ \begin{align*} \quad \delta_{f'} (s, u) = \delta_{f'} (s, v) + 1 \ge \delta_f (s, v) + 1 = \delta_f (s, u) + 2 \end{align*} $$
$(u,v)$成为关键边后到再次成为关键边前$\delta_f (s, u)$至少增加$2$
最短路径长度不超过$|\Vcal|$,每条边成为关键边的次数不超过$|\Vcal|/2$
最大流问题至今是一个活跃的研究领域,2012 年,James Orlin 首次提出$O(|\Vcal| |\Ecal|)$复杂度的算法,达到理论最优
技术 | 复杂度 | 复杂度 (用数据结构) |
---|---|---|
blocking flow | $O(\shu \Vcal \shu^2 \shu \Ecal \shu)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log \shu \Vcal \shu)$ |
network simplex | $O(\shu \Vcal \shu^2 \shu \Ecal \shu)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log \shu \Vcal \shu)$ |
push-relabel (generic) | $O(\shu \Vcal \shu^2 \shu \Ecal \shu)$ | - |
push-relabel (FIFO) | $O(\shu \Vcal \shu^3)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log (\shu \Vcal \shu^2 / \shu \Ecal \shu))$ |
push-relabel (highest label) | $O(\shu \Vcal \shu^2 \sqrt{\shu \Ecal \shu})$ | - |
push-relabel-add games | - | $O(\shu \Vcal \shu \shu \Ecal \shu \log_{\shu \Ecal \shu / (\shu \Vcal \shu \log \shu \Vcal \shu)} \shu \Vcal \shu)$ |
pseudoflow | $O(\shu \Vcal \shu^2 \shu \Ecal \shu)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log \shu \Vcal \shu)$ |
pseudoflow (highest label) | $O(\shu \Vcal \shu^3)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log (\shu \Vcal \shu^2 / \shu \Ecal \shu))$ |
incremental BFS | $O(\shu \Vcal \shu^2 \shu \Ecal \shu)$ | $O(\shu \Vcal \shu \shu \Ecal \shu \log (\shu \Vcal \shu^2 / \shu \Ecal \shu))$ |
compact networks | - | $O(\shu \Vcal \shu \shu \Ecal \shu )$ |
对于单位容量的流网络,即每条边容量为$1$的流网络
1973 年,Alexander Karzanov 证明 Dinitz 的 blocking flow 算法的时间复杂度为$O(\min\{ |\Vcal|^{2/3}, |\Ecal|^{1/2} \} |\Ecal|)$
Alexander Karzanov 同时证明了单位容量流网络中的流分解时间复杂度是$O(\min\{ |\Vcal|^{2/3}, |\Ecal|^{1/2} \} |\Ecal|)$,因此并没有和前面的下界$\Omega(|\Vcal| |\Ecal|)$矛盾
2013 年,Aleksander Mądry 提出了一个$O(|\Ecal|^{10/7} \mathrm{poly} (\log |\Ecal|))$时间复杂度的算法,在$|\Ecal| = o(|\Vcal|^{14/9})$的情况下更优
任给有向图,求任意两点间的最大边不相交路径数
下图$s \rightsquigarrow t$有 3 条边不相交的路径,是否为最大?
归约为单位容量的流网络的最大流问题
一方面,对任意$k$条边不相交的路径,每条上面可运输单位流量,得到流值为$k$的流,因此最大边不相交路径数$\le$最大流值
另一方面,由于边的容量均为整数,因此最大流在各条边上的流值也为整数,非$0$即$1$,每条边要么满流量、要么零流量;根据流分解定理,最大流可分解为若干条$s \rightsquigarrow t$的路径,每条路径上的流量为$1$,因此这些路径不会有公共边,故最大流值$\le$最大边不相交路径数
利用 James Orlin 算法,时间复杂度为$O(|\Vcal| |\Ecal|)$
事实上,切割$(\{s\}, \Vcal \setminus \{s\})$的容量最多为$|\Vcal| - 1$,因此最大流值不超过$|\Vcal| - 1$,朴素的 Ford - Fulkerson 算法也有$O(|\Vcal| |\Ecal|)$的时间复杂度
如果流网络$\Gcal$对任意点$v \in \Vcal \setminus \{ s, t \}$也有容量限制
$$ \begin{align*} \quad \sum_u f(u,v) \le c(v) \end{align*} $$
1962年,Ford、Fulkerson提出如下$\Gcal \rightarrow \Gcal'$的归约
对$\Gcal'$求$s_o$到$t_i$的最大流即可
归约遍历所有点和边,时间复杂度为$O(|\Vcal| + |\Ecal|) = O(|\Ecal|)$
给每个点的容量设为$1$,求最大流即可
匹配:无向图$\Gcal = (\Vcal, \Ecal)$的匹配是边的一个子集$\Mcal \subseteq \Ecal$,使得对$\forall v$,子集$\Mcal$中最多有一条边与$v$相连
最大匹配:若$\Mcal$是最大匹配,则对任意匹配$\Mcal'$有$|\Mcal| \ge |\Mcal'|$
二分图:结点集合$\Vcal = \Lcal \uplus \Rcal$,边集$\Ecal$中所有边横跨$\Lcal$和$\Rcal$
问题:在二分图中寻找最大匹配
应用
归约为流网络$\Gcal' = \{ \Vcal', \Ecal' \}$中的最大流问题
一方面,任意匹配$\Mcal$可以转化为一个可行的流$f_{\Mcal}$:对任意边$(u,v) \in \Mcal$,在路径$s \rightarrow u \rightarrow v \rightarrow t$上运输单位流,因此最大匹配$\le$最大流
另一方面,由于边的容量均为整数,因此最大流在各条边上的流值也为整数,非$0$即$1$:对于$\Lcal$中的点$u$,只有一条入边$(s,u)$,若其有流量流入,则必然有一条边流出;对于$\Rcal$中的点$v$,只有一条出边$(v,t)$,若其有流量流出,则必然有一条边流入,故从$\Lcal$到$\Rcal$的有流量的边是一个匹配,于是最大流$\le$最大匹配
综上,最大流等于最大匹配
对于二分图,假设每个结点至少与一条边相连,即$|\Ecal| \ge |\Vcal| / 2$
$|\Ecal'| = |\Ecal| + |\Vcal| \le 3 |\Ecal| = \Theta(|\Ecal|)$,最大流值不超过$|\Vcal|$
Ford-Fulkerson 算法的时间复杂度为$O(|\Vcal| |\Ecal|)$,与 James Orlin 算法相当
交替路径:从$\Lcal$中某个不属于匹配的点到$\Rcal$中某个不属于匹配的点的路径,其中的边交替属于或不属于匹配
Ford-Fulkerson 算法迭代寻找增广路径的过程就是在原二分图中寻找交替路径的过程,每找到一条交替路径,匹配数加$1$
1957年,Claude Berge研究了通过交替路径来寻找最大二分匹配,独立于最大流最小切割的研究;1973年,John Hopcroft、Richard Karp通过每轮寻找多个交替路径,将时间复杂度降到$O(\sqrt{|\Vcal|} |\Ecal|)$
有$n$个班级、$r$个教室、$t$个时间段、$p$位监考员,输入
要求
输出:是否能安排全部班级的考试,若能,给出方案
构建流网络,若最大流值为$n$,则可安排全部班级的考试
假设有$n$个有限集$\Xcal_1, \ldots, \Xcal_n$,代表$n$种资源
从每个集合选一个元素构成$n$元组
这是个一般性的问题
给定有向图$\Gcal$,寻找若干条不相交的路径包含每个结点恰好一次
平凡解:每个结点单独一个路径
最少的不相交路径覆盖数?
最少的不相交路径覆盖数为 1 等价于图中存在哈密顿路径
这是一个 NP 难的问题
但当输入是无环图 (DAG) 时有基于最大流的高效解法
根据$\Gcal$,构建二分图$\Gcal'$
$\Gcal$可被$k$条不相交路径覆盖当且仅当$\Gcal'$中存在$|\Vcal| - k$的匹配
$\Gcal$可被$k$条不相交路径覆盖当且仅当$\Gcal'$中存在$|\Vcal| - k$的匹配
若$\Gcal$可被$k$条不相交路径覆盖,记路径集合构成的子图为$\Pcal$,由于每条路径的点数比边数多$1$,因此$\Pcal$共有$|\Vcal| - k$条边,定义$\Gcal'$中的边集$\Mcal' = \{ (u_r, v_l) \mid (u, v) \in \Pcal \}$,显然$|\Mcal| = |\Vcal| - k$,$\Mcal'$中每个点最多只与一条边相连,反设$(u_r, v_l), (u_r, w_l) \in \Mcal'$,则$(u, v), (u, w) \in \Pcal$,这是不可能的,因此$\Mcal'$是一个匹配
若$\Gcal'$中存在$|\Vcal| - k$的匹配$\Mcal'$,定义$\Gcal$中的子图$\Pcal = (\Vcal, \Mcal)$,其中$\Mcal = \{ (u,v) \in \Ecal \mid (u_r, v_l) \in \Mcal' \}$,由于$\Mcal'$是一个匹配,因此$\Pcal$中每个点最多只有一条入边、一条出边,故$\Pcal$是一系列不相交的路径,又其包含所有结点,因此路径数为$k$
有$n$个课程、$m$个教室,输入
假设
输出:最少需雇佣多少名老师才能完成教学任务
构建图$\Gcal$
之后在图中求最少的不相交路径覆盖数
算法导论 3rd
26.1-1、26.2-3、26.3-1