Skip to content

最大流,最小费用最大流问题 python

徐少华 算法设计与分析 P145

代码实现

import itertools


class Node:
    def __init__(self, name):
        """
        :param name:节点的名字
        """
        self.name = name
        self.next_nodes = set()
        self.prev_nodes = set()
        self.tag = None

    def add_next_node(self, next_node):
        """
        添加后向节点
        :param next_node:
        :return:
        """
        self.next_nodes.add(next_node)

    def add_prev_node(self, prev_node):
        """
        添加前向节点
        :param prev_node:
        :return:
        """
        self.prev_nodes.add(prev_node)

    def update_tag(self, tag):
        self.tag = tag

    def del_tag(self):
        self.tag = None

    def __str__(self):
        str_node = f"{self.name} " \
                   f"\t tag:{self.tag} " \
                   f"\t next_node:{[i.name for i in self.next_nodes]} " \
                   f"\t prev_node:{[j.name for j in self.prev_nodes]} "
        return str_node


class Graph:
    def __init__(self, nodes, edges):
        self.nodes = dict()
        for i in nodes:
            self.nodes[i] = Node(i)
        self.edges = dict()
        for i in edges:
            start = i[0]
            end = i[1]
            weight = i[2]
            self.edges[(i[0], i[1])] = Edge(start, end, weight)
            self.nodes[start].add_next_node(self.nodes[end])
            self.nodes[end].add_prev_node(self.nodes[start])

        self.head_node = self.nodes['vs']
        self.tail_node = self.nodes['vt']

    def get_node_names(self):
        return list(self.nodes.keys())

    def get_edge_names(self):
        return list(self.edges.keys())

    def get_nodes(self):
        return list(self.nodes.values())

    def get_costs(self):
        return sum(i.weight[1] * i.weight[2] for i in self.edges.values())

    def __str__(self):

        nodes_str = "Nodes:" + str([i.name for i in self.nodes.values()])

        edges_str = "Edges:\n\t" + "\n\t".join([str(i) for i in self.edges.values()])

        return nodes_str + "\n" + edges_str

    def clean_node_tag(self):
        # 清除所有节点的标号
        for i in self.nodes.keys():
            self.nodes[i].update_tag(None)

    def update_flow(self, path, theta, by):

        path = list(path)
        if by == "by_flow":
            # 在增流圈中,需要对圈进行调整,使路径变成环
            path.insert(0, path[-1])

        for i in range(1, len(path)):
            start = path[i - 1].name
            end = path[i].name
            key = (start, end)
            if key in self.edges:
                # 正向弧
                weight = list(self.edges[key].weight)
                weight[1] += theta
            else:
                key = tuple(reversed(key))
                weight = list(self.edges[key].weight)
                weight[1] -= theta
            self.edges[key].weight = tuple(weight)

    def is_add_loop(self, neg_loop):

        # 判断负回路是否是增流圈

        for i in range(len(neg_loop)):
            start = neg_loop[i - 1].name
            end = neg_loop[i].name
            key = (start, end)
            if key in self.edges:
                # 正向弧
                weight = list(self.edges[key].weight)
                if not weight[0] > weight[1]:
                    return False
            else:
                key = tuple(reversed(key))
                weight = list(self.edges[key].weight)
                if weight[1] == 0:
                    return False
        else:
            return True

    def find_loop(self):
        # 寻找回路
        # 构成(负)回路至少两3个点
        for i in range(3, len(self.nodes) + 1):
            for j in itertools.permutations(self.nodes.values(), i):
                if is_path(j, isloop=True):
                    yield j

    def find_neg_loop(self):
        """
        寻找负回路
        :return: 【迭代器】 一个负回路
        """
        mat = []
        for i in self.find_loop():
            val = 0
            min_theta = inf
            for j in range(len(i)):
                weight = self.edges[(i[j - 1].name, i[j].name)].weight
                val += weight[1]
                min_theta = min(min_theta, weight[0])
            if val < 0:
                l = set([j.name for j in i])
                if l not in mat:
                    mat.append(l)
                    yield i, min_theta

    def find_chain(self):
        """
        查找所有链(链的起始点为起点,结束点为收点,路径但忽略方向)
        :param self:
        :return:
        """

        head_node = self.head_node
        tail_node = self.tail_node

        def gen_full_chain(u, u_tag_path):
            full_chain = [self.head_node]
            full_chain.extend(u)
            full_chain.append(self.tail_node)
            u_tag_path.append(True)
            return full_chain, u_tag_path

        nodes_ex = self.get_nodes()

        nodes_ex.remove(head_node)
        nodes_ex.remove(tail_node)

        all_path_u = []
        for i in range(1, len(nodes_ex) + 1):
            all_path_u.extend(list(itertools.permutations(nodes_ex, i)))

        all_chain_u = all_path_u
        for i in all_chain_u:
            if i[0] not in head_node.next_nodes or i[-1] not in tail_node.prev_nodes:
                # 构不成一条链
                continue
            tag_path = [True]
            # 标注在图上的方向,是正向还是反向
            if len(i) == 1:
                # 只有一个中间节点的情况
                yield gen_full_chain(i, tag_path)
                continue
            for j in range(1, len(i)):
                if i[j] in i[j - 1].next_nodes:
                    # 前项弧
                    tag_path.append(True)
                elif i[j] in i[j - 1].prev_nodes:
                    tag_path.append(False)
                    # 后向弧
                else:
                    break
            else:
                yield gen_full_chain(i, tag_path)

    def find_expanded_chain(self):
        """
        查找一条增广链
        :param self: 图
        :return:
        """
        chains = self.find_chain()  # 返回迭代器
        for chain, chain_direction in chains:
            for j in range(len(chain_direction)):
                k = (chain[j].name, chain[j + 1].name)
                if chain_direction[j]:
                    # 前项弧,容量小于容量
                    weight = self.edges[k].weight
                    if not weight[0] > weight[1]:
                        break
                else:
                    # 后项弧,流量大于0
                    k = tuple(reversed(k))
                    weight = self.edges[k].weight
                    if not weight[1] > 0:
                        break
            else:
                return chain, chain_direction
        else:
            return False, False

    def max_flow_by_tag(self, show_detail=False):
        """
        标号法寻找最大流
        :param show_detail:
        :param self:
        :return: 无返回值,引用传递g
        """
        while True:
            # 寻找增广链
            chain, chain_direction = self.find_expanded_chain()
            if chain:
                # 对增广链进行标号
                chain[0].update_tag(tag=("0", inf, True))
                min_theta = inf
                for j in range(len(chain_direction)):
                    k = (chain[j].name, chain[j + 1].name)
                    if chain_direction[j]:
                        # 前项弧标号 v_prev,容量-流量,true
                        weight = self.edges[k].weight
                        dig = weight[0] - weight[1]
                    else:
                        # 后项弧标号 v_prev,流量,False
                        k = tuple(reversed(k))
                        weight = self.edges[k].weight
                        dig = weight[1]
                    min_theta = min(dig, min_theta)
                    tag = (chain[j].name, dig, False)
                    chain[j + 1].update_tag(tag)
                # 调整流量
                for j in range(len(chain_direction)):
                    k = (chain[j].name, chain[j + 1].name)
                    if chain_direction[j]:
                        # 前项弧增大流量,加上 Θ
                        weight = self.edges[k].weight
                        new_weight = (weight[0], weight[1] + min_theta, weight[2])
                    else:
                        # 后项弧减小流量,减去 Θ
                        k = tuple(reversed(k))
                        weight = self.edges[k].weight
                        new_weight = (weight[0], weight[1] - min_theta, weight[2])
                    self.edges[k].update_weight(new_weight)
                self.clean_node_tag()

                if show_detail:
                    print("调整流量后的图:")
                    print(self)
            else:
                break

    def augment_net(self, by):

        """
        构建增流网络
        :return:
        """
        nodes = self.get_node_names()
        # 先把点复制出来

        edges = []
        # 检查边是否是零流弧、饱和弧、非饱和弧

        if by == "by_flow":
            for i in self.get_edge_names():
                weight = self.edges[i].weight
                if weight[1] == 0:
                    # 零流弧,构建同向弧
                    new_weight = (weight[0], weight[2])
                    edges.append((i[0], i[1], new_weight))
                elif weight[0] == weight[1]:
                    # 饱和弧,构建反向弧
                    new_weight = (weight[1], -weight[2])
                    edges.append((i[1], i[0], new_weight))
                else:
                    # 不饱和弧,同时构建同向弧和反向弧
                    # 同向弧
                    new_weight1 = (weight[0] - weight[1], weight[2])
                    edges.append((i[0], i[1], new_weight1))
                    # 反向弧
                    new_weight2 = (weight[1], -weight[2])
                    edges.append((i[1], i[0], new_weight2))
        if by == 'by_cost':
            for i in self.get_edge_names():
                weight = self.edges[i].weight
                if weight[1] == 0:
                    # 零流弧,构建同向弧
                    new_weight = (weight[2],)
                    edges.append((i[0], i[1], new_weight))
                elif weight[0] == weight[1]:
                    # 饱和弧,构建反向弧
                    new_weight = (-weight[2],)
                    edges.append((i[1], i[0], new_weight))
                else:
                    # 不饱和弧,同时构建同向弧和反向弧
                    # 同向弧
                    new_weight1 = (weight[2],)
                    edges.append((i[0], i[1], new_weight1))
                    # 反向弧
                    new_weight2 = (-weight[2],)
                    edges.append((i[1], i[0], new_weight2))

        return Graph(nodes=nodes, edges=edges)

    def get_theta(self, path):
        # 先获取调整量
        theta = inf
        for i in range(1, len(path)):
            key = (path[i - 1].name, path[i].name)

            if key in self.edges:
                # 同向弧
                weight = self.edges[key].weight
                vals = weight[0] - weight[1]
            else:
                key = tuple(reversed(key))
                weight = self.edges[key].weight
                vals = weight[1]

            theta = min(theta, vals)

        return theta

    def find_all_path(self):

        chains = self.find_chain()
        for i, _ in chains:
            if is_path(i):
                yield i

    def find_min_cost_path(self):
        """

        返回最小费用路径和最小费用
        :return:
        """

        all_paths = self.find_all_path()

        min_path = None
        costs = set()
        all_paths = [i for i in all_paths]
        if len(all_paths) == 0:
            return False
        else:
            for path in all_paths:
                now_cost = 0
                now_path = path
                for j in range(1, len(now_path)):
                    edge_cost = self.edges[(now_path[j - 1].name, now_path[j].name)].weight[0]
                    now_cost += edge_cost
                if len(costs) > 0:
                    if now_cost < min(costs):
                        # 初始状态 最小值为inf ,则第一个路径初始化为最小花费路径
                        min_path = path
                else:
                    min_path = path
                costs.add(now_cost)
            return min_path


inf = 999999


class Edge:
    def __init__(self, start, end, weight):
        """
        :param start: 起始点
        :param end: 终点
        :param weight: 权重
        """
        self.start = start
        self.end = end
        self.weight = weight

    def update_weight(self, weight):
        """
        更新权重
        """
        self.weight = weight

    def __str__(self):
        return str(self.start) + " -" + str(list(self.weight)) + "-> " + str(self.end)


def is_path(lis, isloop=False):
    if isloop:
        if lis[0] not in lis[-1].next_nodes:
            return False
    for i in range(1, len(lis)):
        if lis[i] not in lis[i - 1].next_nodes:
            return False
    else:
        return True


def graph_to_max(g, by):
    k = 0
    if by == 'by_flow':
        # 调整到最大流
        g.max_flow_by_tag()
        print(f"最大流网络\n{g}")
    while True:
        k += 1
        print(f"第{k}轮调整")
        # 构建增流网络
        df = g.augment_net(by=by)
        print(f"增流网络{df}")
        if by == 'by_flow':
            neg_loops = [(neg_loop, theta) for neg_loop, theta in df.find_neg_loop()]
            if len(neg_loops) > 0:
                neg_loop, theta = neg_loops[0]
                if g.is_add_loop(neg_loop):
                    # 是增流圈
                    g.update_flow(path=neg_loop, theta=theta, by=by)
            else:
                break
        if by == 'by_cost':
            ch_path = df.find_min_cost_path()
            if ch_path:
                theta = g.get_theta(ch_path)
                g.update_flow(path=ch_path, theta=theta, by=by)
            else:
                break
        print('调整后的g\n', g)
    return g, g.get_costs()

from p62.graph import Graph, graph_to_max

g2 = Graph(nodes=['vs', 'v1', 'v2', 'v3', 'vt'],
           edges=[('vs', 'v1', (10, 0, 4)),
                  ('vs', 'v2', (8, 0, 1)),
                  ('v2', 'v1', (5, 0, 2)),
                  ('v1', 'vt', (7, 0, 1)),
                  ('v2', 'v3', (10, 0, 3)),
                  ('v1', 'v3', (2, 0, 6)),
                  ('v3', 'vt', (4, 0, 2))])

g2, g2_cost = graph_to_max(g2,by='by_cost')

print(f"调整到最后的网络:\n{g2}", f"\n最小花费:{g2_cost}")


g1 = Graph(nodes=['vs', 'v1', 'v2', 'v3', 'vt'],
          edges=[('vs', 'v1', (10, 0, 4)),
                 ('vs', 'v2', (8, 0, 1)),
                 ('v2', 'v1', (5, 0, 2)),
                 ('v1', 'vt', (7, 0, 1)),
                 ('v2', 'v3', (10, 0, 3)),
                 ('v1', 'v3', (2, 0, 6)),
                 ('v3', 'vt', (4, 0, 2))])

g1, g1_cost = graph_to_max(g1,by='by_flow')

print(f"调整到最后的网络:\n{g1}", f"\n最小花费:{g1_cost}")

输出

第1轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[4]-> v1
    vs -[1]-> v2
    v2 -[2]-> v1
    v1 -[1]-> vt
    v2 -[3]-> v3
    v1 -[6]-> v3
    v3 -[2]-> vt
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 0, 4]-> v1
    vs -[8, 5, 1]-> v2
    v2 -[5, 5, 2]-> v1
    v1 -[7, 5, 1]-> vt
    v2 -[10, 0, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 0, 2]-> vt
第2轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[4]-> v1
    vs -[1]-> v2
    v2 -[-1]-> vs
    v1 -[-2]-> v2
    v1 -[1]-> vt
    vt -[-1]-> v1
    v2 -[3]-> v3
    v1 -[6]-> v3
    v3 -[2]-> vt
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 2, 4]-> v1
    vs -[8, 5, 1]-> v2
    v2 -[5, 5, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 0, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 0, 2]-> vt
第3轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[4]-> v1
    v1 -[-4]-> vs
    vs -[1]-> v2
    v2 -[-1]-> vs
    v1 -[-2]-> v2
    vt -[-1]-> v1
    v2 -[3]-> v3
    v1 -[6]-> v3
    v3 -[2]-> vt
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 2, 4]-> v1
    vs -[8, 8, 1]-> v2
    v2 -[5, 5, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 3, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 3, 2]-> vt
第4轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[4]-> v1
    v1 -[-4]-> vs
    v2 -[-1]-> vs
    v1 -[-2]-> v2
    vt -[-1]-> v1
    v2 -[3]-> v3
    v3 -[-3]-> v2
    v1 -[6]-> v3
    v3 -[2]-> vt
    vt -[-2]-> v3
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 3, 4]-> v1
    vs -[8, 8, 1]-> v2
    v2 -[5, 4, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 4, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 4, 2]-> vt
第5轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[4]-> v1
    v1 -[-4]-> vs
    v2 -[-1]-> vs
    v2 -[2]-> v1
    v1 -[-2]-> v2
    vt -[-1]-> v1
    v2 -[3]-> v3
    v3 -[-3]-> v2
    v1 -[6]-> v3
    vt -[-2]-> v3
调整到最后的网络:
Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 3, 4]-> v1
    vs -[8, 8, 1]-> v2
    v2 -[5, 4, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 4, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 4, 2]-> vt 
最小花费:55
最大流网络
Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 9, 4]-> v1
    vs -[8, 2, 1]-> v2
    v2 -[5, 0, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 2, 3]-> v3
    v1 -[2, 2, 6]-> v3
    v3 -[4, 4, 2]-> vt
第1轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[1, 4]-> v1
    v1 -[9, -4]-> vs
    vs -[6, 1]-> v2
    v2 -[2, -1]-> vs
    v2 -[5, 2]-> v1
    vt -[7, -1]-> v1
    v2 -[8, 3]-> v3
    v3 -[2, -3]-> v2
    v3 -[2, -6]-> v1
    vt -[4, -2]-> v3
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 4, 4]-> v1
    vs -[8, 7, 1]-> v2
    v2 -[5, 5, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 2, 3]-> v3
    v1 -[2, 2, 6]-> v3
    v3 -[4, 4, 2]-> vt
第2轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[6, 4]-> v1
    v1 -[4, -4]-> vs
    vs -[1, 1]-> v2
    v2 -[7, -1]-> vs
    v1 -[5, -2]-> v2
    vt -[7, -1]-> v1
    v2 -[8, 3]-> v3
    v3 -[2, -3]-> v2
    v3 -[2, -6]-> v1
    vt -[4, -2]-> v3
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 4, 4]-> v1
    vs -[8, 7, 1]-> v2
    v2 -[5, 3, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 4, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 4, 2]-> vt
第3轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[6, 4]-> v1
    v1 -[4, -4]-> vs
    vs -[1, 1]-> v2
    v2 -[7, -1]-> vs
    v2 -[2, 2]-> v1
    v1 -[3, -2]-> v2
    vt -[7, -1]-> v1
    v2 -[6, 3]-> v3
    v3 -[4, -3]-> v2
    v1 -[2, 6]-> v3
    vt -[4, -2]-> v3
调整后的g
 Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 3, 4]-> v1
    vs -[8, 8, 1]-> v2
    v2 -[5, 4, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 4, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 4, 2]-> vt
第4轮调整
增流网络Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[7, 4]-> v1
    v1 -[3, -4]-> vs
    v2 -[8, -1]-> vs
    v2 -[1, 2]-> v1
    v1 -[4, -2]-> v2
    vt -[7, -1]-> v1
    v2 -[6, 3]-> v3
    v3 -[4, -3]-> v2
    v1 -[2, 6]-> v3
    vt -[4, -2]-> v3
调整到最后的网络:
Nodes:['vs', 'v1', 'v2', 'v3', 'vt']
Edges:
    vs -[10, 3, 4]-> v1
    vs -[8, 8, 1]-> v2
    v2 -[5, 4, 2]-> v1
    v1 -[7, 7, 1]-> vt
    v2 -[10, 4, 3]-> v3
    v1 -[2, 0, 6]-> v3
    v3 -[4, 4, 2]-> vt 
最小花费:55