11.1 Ford-Fulkerson算法及Edmonds-Karp算法

基础理论

  剩余容量residual capicity的概念比较简单,就是容量减去流量,设起点为 u u u,终点为 v v v,公式为:
c f ( u , v ) = c ( u , v ) − f ( u , v ) c_f(u,v)=c(u,v)-f(u,v) cf(u,v)=c(u,v)f(u,v)
  不饱和边集unsaturated edges,是在特定的流量模型 f f f下所有剩余容量大于0的边的集合,数学符号为 E f E_f Ef
  剩余网络redisual network,是在特定的流量模型f下,所有的点和不饱和边组成的网络。剩余网络,肯定是原网络的子图,也因为点集相等,所以也是原网络的伸展子图。
  增广路径augmenting path,是特定流量模型 f f f下剩余网络中从起点s到终点f的路径path
  增广路径定理Augmenting Path Theorem,说的是一个流 f f f如果是最大流当且仅当剩余网络中不存在增广路径。

算法及例子

  这个算法超级简单啊,就是利用增广路径定理。首先是初始化流为0,这是句废话了,初始化都应该是0。
  然后是从剩余网络里,找到一条增广路径,再取出增广路径里最小的剩余容量。然后把增广路径里的流全部加上最小剩余容量,再去找增广路径,直到找不出增广路径。根据增广路径定理,找不出的时候,就是最大流了。
  如果每次都找的是任意路径,那就是Ford-Fulkerson算法。如果每次找到的增广路径都是最短路径,那就是Edmonds-Karp算法。
  我以那个被无数大学和结构使用的流量图为例子,使用Edmonds-Karp算法:
在这里插入图片描述
  第一次循环,找到了一条路径:
在这里插入图片描述
  第二次循环,先把流量加上,再找增广路径:
在这里插入图片描述
  第三次循环:
在这里插入图片描述
  第四次循环:
在这里插入图片描述
  第五次循环:
在这里插入图片描述
  最终找不到增广路径,跳出循环,达到最大流:
在这里插入图片描述

Python代码

# _*_ coding:utf-8 _*_
class Edge:

    def __init__(self, to, weight):
        self.__to = to
        self.__weight = weight

    @property
    def to(self):
        return self.__to

    @to.setter
    def to(self, to):
        self.__to = to

    @property
    def weight(self):
        return self.__weight

    @weight.setter
    def weight(self, weight):
        self.__weight = weight


# 加权图
class FlowNetwork:

    def __init__(self, vertices, edges, s, t):
        self.__vertices = vertices
        self.__edges = edges
        self.__s = s
        self.__t = t

    def ford_fulkerson(self):
        # init
        flow = [[Edge(x.to, 0) for x in e] for e in self.__edges]

        while (p := self.augmenting_path(flow)) is not None:
            # 找出最小的剩余容量
            cf_min = p[1]
            path = p[0]

            # path上所有流量加上最小剩余流量
            for i in range(len(path) - 1):
                flows = flow[path[i]]
                for x in flows:
                    if x.to == path[i + 1]:
                        x.weight += cf_min
        return flow

    WHITE = 0
    GRAY = 1
    BLACK = 2

    def augmenting_path(self, flow):
        # 使用单源bfs找到路径
        parent_map = [None for _ in self.__vertices]
        # 因为python 没有hashset,所以以字典代替
        colors = [FlowNetwork.WHITE for _ in self.__vertices]
        colors[self.__s] = FlowNetwork.GRAY
        queue = [self.__s]
        e = self.__s

        while e != self.__t and len(queue) != 0:
            e = queue.pop(0)
            # 把点连接的点全部放入队列中
            for neighbor in self.__edges[e]:
                f = FlowNetwork.get_flow(flow, e, neighbor.to)
                cf = neighbor.weight - f
                if cf > 0:
                    if colors[neighbor.to] == FlowNetwork.WHITE:
                        # 必须是不饱和边
                        parent_map[neighbor.to] = e
                        queue.append(neighbor.to)
                        colors[neighbor.to] = FlowNetwork.GRAY
            colors[e] = FlowNetwork.BLACK
        # 没有找到
        if e != self.__t:
            return None

        # 找到之后组装路径
        path = [self.__t]
        end = self.__t
        min_cf = None
        while end != self.__s:
            # 不停地找parent
            c = FlowNetwork.get_flow(self.__edges, parent_map[end], end)
            f = FlowNetwork.get_flow(flow, parent_map[end], end)
            cf = c - f
            min_cf = cf if min_cf is None else min(min_cf, cf)
            end = parent_map[end]
            path.append(end)
        path.reverse()
        return path, min_cf

    @staticmethod
    def get_flow(flow, source, target):

        flows = flow[source]
        f = None
        for edge_flow in flows:
            if edge_flow.to == target:
                f = edge_flow.weight
        return f

    def to_dot(self, pos):
        dot_s = 'digraph s {\n\tlayout=fdp\n'
        for i, v in enumerate(self.__vertices):
            dot_s += f'\t"{v}"[pos="{pos[i]}"];\n'
        for i, e in enumerate(self.__edges):
            for t in e:
                dot_s += f'\t\"{self.__vertices[i]}\"->"{self.__vertices[t.to]}"[label="{t.weight}"];\n'
        dot_s += '}\n'
        return dot_s

    def to_flow_dot(self, pos, flow):
        dot_s = 'digraph s {\n\tlayout=fdp\nnode [shape = circle]\n'
        for i, v in enumerate(self.__vertices):
            dot_s += f'\t"{v}"[pos="{pos[i]}"];\n'
        for i, e in enumerate(self.__edges):
            for t in e:
                edge = f'\t\"{self.__vertices[i]}\"->"{self.__vertices[t.to]}"'
                dot_s += edge
                f = FlowNetwork.get_flow(flow, i, t.to)
                dot_s += f'[label="{f}/{t.weight}"];\n'
        dot_s += '}\n'
        return dot_s

    def to_flow_path_dot(self, pos, flow, path):
        path_map = {}
        for i in range(len(path) - 1):
            path_map[path[i]] = path[i + 1]
        dot_s = 'digraph s {\n\tlayout=fdp\nnode [shape = circle]\n'
        for i, v in enumerate(self.__vertices):
            dot_s += f'\t"{v}"[pos="{pos[i]}"];\n'
        for i, e in enumerate(self.__edges):
            for t in e:
                edge = f'\t\"{self.__vertices[i]}\"->"{self.__vertices[t.to]}"'
                dot_s += edge
                f = FlowNetwork.get_flow(flow, i, t.to)
                # 遇到路径就加粗
                dot_s += f'[label="{f}/{t.weight}";'
                if i in path_map and path_map[i] == t.to:
                    dot_s += 'color="red"'
                dot_s += '];\n'
        dot_s += '}\n'
        return dot_s

    @property
    def vertices(self):
        return self.__vertices

    @vertices.setter
    def vertices(self, value):
        self.__vertices = value

    @property
    def edges(self):
        return self.__edges

    @edges.setter
    def edges(self, value):
        self.__edges = value

    @property
    def s(self):
        return self.__s

    @s.setter
    def s(self, s):
        self.__s = s

    @property
    def t(self):
        return self.__t

    @t.setter
    def t(self, value):
        self.__t = value

Python测试数据

import unittest

from com.youngthing.graph.ford_fulkerson import Edge
from com.youngthing.graph.ford_fulkerson import FlowNetwork


class ForkFulkersonTestCase(unittest.TestCase):
    def test_fork_fulkerson(self):
        # 做一张图
        vertices = ['S', '2', '3', '4', '5', '6', '7', 'T']
        # 设计容量
        edges = [
            [Edge(1, 10), Edge(2, 5), Edge(3, 15)],
            [Edge(2, 4), Edge(4, 9), Edge(5, 15)],
            [Edge(3, 4), Edge(5, 8)],
            [Edge(6, 30)],
            [Edge(5, 15), Edge(7, 10)],
            [Edge(6, 15), Edge(7, 10)],
            [Edge(7, 10), Edge(2, 6)],
            []
        ]
        fn = FlowNetwork(vertices, edges, 0, 7)
        pos = ["0,0!", "2,1!", "2,0!", "2,-1!", "4,1!", "4,0!", "4,-1!", "6,0!"]
        print(fn.to_dot(pos))

        flow = fn.ford_fulkerson()
        print(fn.to_flow_dot(pos, flow))


if __name__ == '__main__':
    unittest.main()

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

醒过来摸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值