网络与流

作者: pdnbplus | 发布时间: 2024/07/13 | 阅读量: 169

网络与流 -- 潘登同学的图论笔记

  • 网络与流 > 假设$G=(V,E)$是一个连通无重边且不包含自环的有向图,如果G中 > > (1)只有一个入度为0的顶点,记之作为s,称为源 > > (2)只有一个出度为0的顶点,记之作为t,称为汇 > > (3)每条有向边$e=(u,v)$都存在一个非负权值$C_{uv}$,称作边的容量 > > 则称G是一个网络或流网络,也记作$G=(V,E,s,t,C)$

注:(问题转化)

①若存在重边,对重边求和作为一条边总容量

②自环的存在与否不影响问题的分析

③在实际应用中,还要考虑中转站的容量上限

④如果有多个源或者汇,那么新增两个顶点,S,T作为 源的源 和 汇的汇 ,并且将足够大的容量$C_0$赋予这些新加的有向边(指的是S到多个源s的边,T到多个汇t的边)

  • 前驱、后继

    假设顶点$v∈V$,定义v的前驱为$pred(v) = {u|(u,v)∈E}$

    定义v的后继为$secc(v)={u|(u,v)∈E}$

  • 流(流量)

    若实值函数$f: E→\mathcal{R}$满足

    (1)容量限制:对所有$e=(u,v)∈E$,有$f_{uv} = f(e) ≤C_{uv}$

    (2)流量守恒:对所有顶点$v∈V-{s,t}, ∑_{(u∈pred(v))}f_{uv} = ∑_{(u∈succ(v))}f_{uv}$

    则称他是网络的一个容许流分布,或简称一个流,$f_uv$称作边(u,v)上的流量,若$e=(u,v)$满足$f_{uv} = C_{uv}$,则称e为饱和边

  • 流入(出)量

    对所有顶点$v∈V-{s}$,定义$f( * ,v) = ∑_{(u∈pred(v))}f_{uv}$,称作顶点v的总流入量

    对所有顶点$u∈V-{t}$,定义$f(u ,*) = ∑_{(v∈pred(u))}f_{uv}$,称作顶点v的总流出量

    (为完整性考虑,补充定义$f(* ,s)= 0, f(t,*)= 0$

流量守恒表明:流在除了元和汇以外的各个顶点的总流入等于总流出量

  • 最大流

    设f是网络图G的一个流,$f(s,*)$称作流f的流量,即源s的总流出量,记作|f|。若G的任意一个流f'都满足$|f|≥|f’|$,则称f是G的一个最大流

  • S-T割(非常重要)

    在网络$G = (V,E,s,t,c)$中,任何一个满足$s∈S,t∈T = V-S$的顶点V的划分(S,T)称作一个S-T割,简称割

    一个S-T割的容量定义为 $∑_{(u∈S,v∈T,uv∈E)}C_{uv}$,记作$C_{uv}(S,T)$

    如果图G的S-T割使得任意一个G的S-T割(S',T')都有$Cap(S,T)≤Cap(S',T')$则称(S,T)是图G的一个最小S-T割,简称最小割

  • 符号说明

    在网络$G = (V,E,s,t,c)$中,假设A、B都是V的非空子集,定义$f(A,B) = ∑_{(u∈S,v∈T,uv∈E)}f_{uv}$,即从A穿出进入B的边的总流量,定义$C(A,B)= ∑_{(u∈A,v∈B,uv∈E)}C_{uv}$,即从A穿出进入B的边的总容量

  • (定理)割流量 = 总流量

    假设$G =(V,E,s,t,c)$是一个网络,令f是一个流,(S,T)是一个S-T割,则通过该割的流量 = 由源s发出的流量;即$f(S,T)-f(T,S) = |f|$

    特别地,有$f(*, t) = |f|$

证明:(对|S|进行归纳证明)

①当$S={s}$的时候显然成立

②假设定理对于$|S|<k$的时候成立;

当$|S| = k$时, 任取$v∈S-{s}$,令$S’= S-{v}$, $T' = T∪{v}$(这一步是把S中k个对象将为k-1个,然后可以用假设)

则由$|S'| = k-1$ 可知$f(S’,T')-f(T’,S’)=|f|$

将顶点v添加到S'后,有 $$

\begin{aligned} f(S,T)-F(S,T) &= (f(S’,T')-f(S’,{v})+f({v},T))-(f(T’,S')-f(T,{v})+f(S',{v}))\ &= f(S’,T') - f(T’,S') + (f({v},T) + f({v},S’)- f(T,{v}) - f(T,{v}))\ &= f(S’,T') - f(T’,S') + f(v,)- f(,v)\ &= f(S’,T') - f(T’,S') = |f|\ \end{aligned} $$

  • 最大流的上限由最小割决定 > 设G是一个网络,令f是G的一个流,(S,T)是G的一个S-T割,则$|f|≤Cap(S,T)$

证明: 由上面的定理 $$ \begin{aligned} |f| &= f(S,T) - f(T,S)≤f(S,T)\ &= ∑_{(u∈S,v∈T,uv∈E)}f_{uv} \ &≤ ∑_{(u∈S,v∈T,uv∈E)}C_{uv} = Cap(S,T)\ \end{aligned} $$

  • 推论

设G是一个网络,f是一个流,(S,T)是一个S-T割,则若$|f| = Cap(S,T)$,则f是一个最大流且(S,T)是一个最小割

证明:假设$f_1$是任意一个流,则由上面定理可得,$|f_1|≤|f|$,则$f$是一个最大流

假设$(S_1,T_1)$是任意一个S-T割,则由上面定理,可得$Cap(S,T)=|f| ≤Cap(S_1,T_1)$,因此(S,T)是一个最小S-T割

(因此只要找到最小S-T割就能找到最大流)

  • 剩余图 > 假设网络$G =(V,E,s,t,c)$中有流f,则可如下定义G关于f的剩余图为$G_f =(V,E,s,t,c’)$ > > (1)$G_f$的顶点集与G的顶点相同 > > (2)$G_f$的边集合$E_f$有两类:${(u,v)|f_{uv}

(说白了就是把边反过来)

如图一个由流构造剩余图的例子:

一个剩余图

  • 可增广道路 > 假设网络$G =(V,E,s,t,c)$中由$流f,G关于f的剩余图$中的简单s-t道路(就是由源s到汇t的道路)P称作可增广道路,定义bottleneck(P,f)为P所经过各边的最小流量(bottleneck意思是瓶颈) > > 可以由增广道路P构造G的一个新流f'; > > 当(u,v)是P中的前向边, $f_{uv}’ = f_{uv} + bottleneck(P,f)$ > > 当(u,v)是P中的后向边,$f_{uv}’ = f_{uv} - bottleneck(P,f)$ > > 其他情况时, $fuv$

可以验证函数f'满足容量条件和守恒条件:

(1)满足容量条件。对于不在道路P上的边而言,不产生任何变化

如果(u,v)是P中的前向边, $f_{uv}’ = f_{uv} + bottleneck(P,f)≤f_{uv} + C_{uv}’ = f_{uv} + C_{uv} - f_{uv} = C_{uv}$;

如果(u,v)是P中的后向边, $f_{uv}’ = f_{uv} - bottleneck(P,f)≤f_{uv}≤ C_{uv}$;

(2)满足守恒条件,对于不在道路P上的顶点而言,不产生任何变化

对于在P上的顶点$v∈V-{s,t}$,假设在道路P上与v关联的边是(u,v)和(v,w)

①如果(u,v)和(v,w)都是P中的前向边,则

$$ \begin{aligned} f’(,v) - f’(v,) &= (f(,v) + bottleneck(P,f)) - (f(v,) + bottleneck(P,f))\ &=f(,v) - f(v,) = 0 \end{aligned} $$

②如果(u,v)和(v,w)都是P中的后向边,则 $$ \begin{aligned} f’(,v) - f’(v,) &= (f(,v) - bottleneck(P,f)) - (f(v,) - bottleneck(P,f))\ &=f(,v) - f(v,) = 0\ \end{aligned} $$ ③如果(u,v)是P中的前向边, (v,w)是P中的后向边,则 $$ \begin{aligned} f’(,v) - f’(v,) &= (f(,v) - bottleneck(P,f)+ bottleneck(P,f)) - f(v,)\ =f(,v) - f(v,) = 0\ \end{aligned} $$ ④如果(u,v)是P中的后向边, (v,w)是P中的前向边,则 $$ \begin{aligned} f’(,v) - f’(v,) &= f(,v) - (f(v,)- bottleneck(P,f)+ bottleneck(P,f))\ &=f(,v) - f(v,) = 0\ \end{aligned} $$

而且$|f’| = f’(s,) = f(s,) + bottleneck(P,f) > f(s,*) = |f|$,即流的流量得以提升

  • 最大流,最小割定理 > 假设f是网络$G =(V,E,s,t,c)$的一个流,则一下陈述等价 > > (a)f是一个最大流 > > (b)当且f的剩余图中不存在增广道路 > > (c)存在G的一个S-t割(S,T)使得$|f| = Cap(S,T)$

证明:(a)-->(b)

如果当前关于f的剩余图中存在可增广道路,则可以通过这条道路扩大流,与f是最大流矛盾

(b)-->(c)

假设f是不存在可增广道路的流,设S是在当前剩余图由s可达的顶点之集合,显然s∈S,且t∉S,否则存在增广道路;

令$T = V - S,假设u∈S,v∈T,若(u,v)∈E,则必有f_{uv} = C_{uv},否则(u,v)∈E_f$,v也由s可达,与S的定义矛盾;

若$(v,u)∈E,则必然有f_{vu}=0,否则(u,v)∈E_f$,v也由s可达,与S的定义矛盾

(c)-->(a) :由上面的推论可得

(算法)Ford-Fulkerson(G) (最大流最小割算法)

输入: 网络G =(V,E,s,t,c)

输出: G的一个最大流f

①初始流量选为0流量,即对所有边$uv,f_{uv}←0$

②构造G关于f的剩余图$G_f$

③若$G_f$中存在增广道路P,则按照前述方法构造由增广道路P,构造G的一个新流$f'$,$f←f’$,转到②

否则输出f

(通俗来说,就是在图中随便找一条路,再把路反过来,再找一条路,再把路反过来,往复这个过程直达找不到路就是最大流了)

实现算法过程注意几点:(是我在写代码中的出来的关键)

因为f永远是正的,跟初始边的方向是同向的,所以f不需要再建一个图,直接把f并入边的属性即可cost=[f,c],所以图的数据结构要修改

增广道路的搜寻方法多种多样,可以用前面的DFS(固定起始点s,当path的最后一个元素是t时输出path即可),我这里采用BFS(广度优先搜素,详细的可以继续往后看,会说BFS)

剩余图也用同一个数据结构,但是边属性只有一个C,cost=c

接下来,还是看代码!!!冲!!!

#%%Ford-Fulkerson算法求解最大流问题
from Vertex import Vertex # 导入Vertex
from Graph import Graph  # 导入之前实现的Graph
import sys

class New_Vertex(Vertex):  # 某一个具体问题的数据结构需要继承原有数据结构
    def __init__(self, key):
        super().__init__(key)
        self.color = 'white'  # 新增类属性(用于记录节点是否被走过)
        self.dist = sys.maxsize  # 新增类属性(用于记录strat到这个顶点的距离)初始化为无穷大
        self.pred = None  # 顶点的前驱 BFS需要

    # 新增类方法, 设置节点颜色
    def setColor(self, color):
        self.color = color

    # 新增类方法, 查看节点颜色
    def getColor(self):
        return self.color

    # 新增类方法, 设置节点前驱
    def setPred(self, p):
        self.pred = p

    # 新增类方法, 查看节点前驱
    def getPred(self):  # 这个前驱节点主要用于追溯,是记录离起始节点最短路径上
        return self.pred    # 该节点的前一个节点是谁

    # 新增类方法, 设置节点距离
    def setDistance(self, d):
        self.dist = d

    # 新增类方法, 查看节点距离
    def getDistance(self):
        return self.dist

class New_Graph(Graph):  # 继承Graph对象
    def __init__(self):
        super().__init__()

    # 重载方法  因为原先Graph中新增节点用的是Vertex节点,但现在是用New_Vertex
    def addVertex(self, key):   # 增加节点
        '''
        input: Vertex key (str)
        return: Vertex object
        '''
        if key in self.vertList:
            return
        self.numVertices = self.numVertices + 1
        newVertex = New_Vertex(key)   # 创建新节点
        self.vertList[key] = newVertex
        return newVertex

# %广度优先算法(先从距离为1开始搜索节点,搜索完所有距离为k才搜索距离为k+1)
'''
为了跟踪顶点的加入过程,并避免重复顶点,要为顶点增加三个属性
    距离distance:从起始顶点到此顶点路径长度
    前驱顶点predecessor:可反向追随到起点
    颜色color:标识了此顶点是尚未发现(白色),已经发现(灰色),还是已经完成探索(黑色)
还需用一个队列Queue来对已发现的顶点进行排列
    决定下一个要探索的顶点(队首顶点)

BFS算法过程
    从起始顶点s开始,作为刚发现的顶点,标注为灰色,距离为0,前驱为None,
    加入队列,接下来是个循环迭代过程:
        从队首取出一个顶点作为当前顶点;遍历当前顶点的邻接顶点,如果是尚未发现的白
        色顶点,则将其颜色改为灰色(已发现),距离增加1,前驱顶点为当前顶点,加入到队列中
    遍历完成后,将当前顶点设置为黑色(已探索过),循环回到步骤1的队首取当前顶点
'''

# 队列 -- 也是BFS需要的
class Queue:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def enqueue(self, items):   # 往队列加入数据
        self.items.insert(0, items)

    def dequeue(self):
        return self.items.pop()

    def size(self):
        return len(self.items)

class max_flow_graph():
    # 创建图和初始化流f
    # 注意图中的cost由[flow, cost] 组成 前者表示流量 后者表示容量
    # 既可以传入字典也能传入邻接矩阵
    def createGraph_f(self, g_dict=None, g_matrix=None):
        '''
        input: g_dict(邻接表) or g_matrix(邻接矩阵)
        output: directgraph(有向图)
        '''
        graph = New_Graph()
        # f = Graph()
        if g_dict:
            for from_key in g_dict:
                for to_key in g_dict[from_key]:
                    # 这里就是注意提到的f跟着c走所以原图的c是[f,c],这个很重要
                    graph.addEdge(from_key, to_key[0], [0, to_key[1]])

        elif g_matrix:
            # 先给顶点起个名字
            name = [str(i) for i in range(1, len(g_matrix)+1)]
            for i, from_key in enumerate(name):
                for j, to_key in enumerate(name):
                    if g_matrix[i][j] != float('inf'):
                        graph.addEdge(from_key, to_key, [0,g_matrix[i][j]])
        return graph

    # 得到简单的s-t道路P
    def BFS(self, gf, start, end):   # g是图,start是起始的节点
        '''
        g: Gf 剩余图
        start: key
        end: key
        return: p 以节点为元素的道路列表
        '''
        start.setDistance(0)  # 距离
        start.setPred(None)  # 前驱节点
        vertQueue = Queue()   # 队列
        vertQueue.enqueue(start)  # 把起始节点加入图中
        while vertQueue.size() > 0:   # 当搜索完所有节点时,队列会变成空的
            currentVert = vertQueue.dequeue()  # 取队首作为当前顶点
            for nbr in currentVert.getConnections():  # 遍历临接顶点
                if (nbr.getColor() == 'white'):   # 当邻接顶点是灰色的时候
                    nbr.setColor('gray')
                    nbr.setDistance(currentVert.getDistance() + 1)
                    nbr.setPred(currentVert)
                    vertQueue.enqueue(nbr)
            currentVert.setColor('balck')
        P = []
        P.append(end)
        while end.getPred():
            end = end.getPred()
            P.append(end)
        return P[::-1]

    # 由原图构造剩余图
    def create_remain_graph(self, g):
        '''
        input: g有向图(包含flow流量和cost容量)
        output: gf剩余图(就是把flow的方法反过来的一个图)
        '''
        # 剩余图gf的边只有一个属性就是cost
        gf = New_Graph()
        # 获得顶点集
        verlist = g.vertList
        for i in verlist:
            from_key = verlist[i]
            gf.addVertex(from_key.getId())
            for to_key in from_key.getConnections():
                gf.addVertex(to_key.getId())
                f = from_key.getWeight(to_key)[0]  # f表示流
                c = from_key.getWeight(to_key)[1]  # c表示容量
                # 前向边
                if f <  c:
                    c_new = c - f
                    gf.addEdge(from_key.getId(), to_key.getId(), c_new)
                # 后向边
                if f > 0:
                    gf.addEdge(to_key.getId(), from_key.getId(), f)
        return gf

    def print_flow(self, g):
        '''
        input: g 带流的图
        output: flow_matrix
        '''
        result = [[0]*len(g) for _ in range(len(g))]
        for i in range(1, len(g)):
            from_key = str(i)
            for j in range(i+1, len(g)+1):
                to_key = str(j)
                from_Vertex = g.getVertex(from_key)
                to_Vertex = g.getVertex(to_key)
                try:
                    result[i-1][j-1] = from_Vertex.getWeight(to_Vertex)[0]
                except:
                    pass
        return result

    def Ford_Fulkerson(self, g):
        '''
        算法主流程:
            1.初始化流为0 (f<--0)(我把这步放到createGraph中了)
            2.构造G关于f的剩余图gf
            3.若gf中存在增广道路P, 则由增广道路P构造G的一个新流f’
              f <-- f' 转步骤2
              否则输出f

        Parameters
        ----------
        g : graph
            由createGraph_f创建的图.

        Returns
        -------
        choice matrix
            流量方案的选择(每一行表示一个流出点, 每一列表示一个流入点).

        '''
        # 构造g关于f的剩余图Gf
        gf = self.create_remain_graph(g)
        # 求出Gf的增广道路
        P = self.BFS(gf, gf.getVertex('1'), gf.getVertex(str(len(g))))
        # 若存在增广道路 则由增广道路构造G的一个新流f
        while len(P)>1:
            # 计算增广路中的bottleneck
            bottleneck = 1e5
            for i in range(len(P)-1):
                if P[i].getWeight(P[i+1]) < bottleneck:
                    bottleneck = P[i].getWeight(P[i+1])
            # 更新f
            for i in range(len(P)-1):
                u_name = P[i].getId()
                v_name = P[i+1].getId()
                u = g.vertList[u_name]
                v = g.vertList[v_name]
                c = u.getWeight(v)
                # 如果(u,v)是g中的正向边
                if v in u.getConnections():
                    c[0] += bottleneck
                elif u in v.getConnections():
                    c[0] -= bottleneck
                # 流一定是正向的,只有剩余图的边有可能是反向的
                g.addEdge(u_name, v_name, c)
            # 更新gf
            gf = self.create_remain_graph(g)
            # 求出Gf的增广道路
            P = self.BFS(gf, gf.getVertex('1'), gf.getVertex(str(len(g))))
        return self.print_flow(g)

if __name__ == '__main__':
    g_dict ={'1':[['2', 10], ['3', 10]],
             '2':[['3', 2], ['4', 4], ['5', 8]],
             '3':[['5', 9]],
             '4':[['6', 10]],
             '5':[['4', 6], ['6', 10]]}
    s = max_flow_graph()
    g = s.createGraph_f(g_dict)
    print(s.Ford_Fulkerson(g))

(注:如果各边的容量都是整数,则每次f←f‘的更新都使得流的值至少增加1,因此算法至多再$∑_{(v∈suss(s)}C_{sv}$次结束,如果各边的容量是一般实数,那么该算法永远运行下去)

  • 网络最大流的应用 > 可以将二部图的匹配问题转化为网络流图 > > 步骤:(1)将原图所有无向边改为有向边,由X中顶点指向Y中顶点 > > (2)添加一个超源顶点S和一个超汇顶点T > > (3)添加S到X中每个顶点的有向边,添加Y中每个顶点到T的有向边 > > (4)所有有向边的容量都设置为1

所得的图称作匹配网络,显然二部图中的最大匹配对应匹配网络的最大流。(看到这里是不是豁然开朗了,原来网上说的二部图的最大匹配算法的原理是这样)

我的离散数学的图论笔记就到此为止了!!,如果你也是一个证明一个证明看下来,一行代码一行代码地敲下来的话,你已经很强啦!!!