最短路与动态规划(二)

1.一对多的无负权环路最短路问题可使用贝尔曼-福特算法求解

2.多对多的无负权环路最短路问题可使用弗洛伊德-瓦尔肖算法求解

3.迪杰斯特拉算法适用于一对多且路为非负的最短路问题

4.一对多的无环有向图也有最高效的算法

继上一篇最短路问题,我们介绍几种利用动态规划思想开发的算法。

传送门:最短路与动态规划(一)

1

一对多最短路算法

先介绍第一个无负权环路的最短路算法,由贝尔曼(R.E.Bellman)和福特(L.R.Ford)从函数方程启发而得的算法——贝尔曼-福特算法。为此,我们先引入记号:

83df56d8c6c3222492c3d19bb6a42e03.png

我们先给出算法的详细流程:

最短路与动态规划(二)_第1张图片

我们以“双环”马戏团为案例,来感受一下该算法的全流程,起点为节点1,对于t=0,很显然有:

f689f62a003990759738e226e6c77076.png

对于t=1,我们有:

最短路与动态规划(二)_第2张图片

最终我们得到下面这张从t=0到6的表格,对应的d[k]进行更新:

最短路与动态规划(二)_第3张图片

可以看到,从起点1到终点9的最短路长度是5.5(这里指成本),为了方面,我们做了简化,并且把最短路用绿色底色标注出来:

最短路与动态规划(二)_第4张图片

可以看到,从终点开始,t=5时刻的最短路上一个节点是8,t=4时节点8的上一个节点是7,以此类推,得到了最短路径:1-3-5-7-8-9,下图的红色路径就是最短路:

最短路与动态规划(二)_第5张图片

上面的算法能终止条件是图中不包含负权环路,如果遇到了负权环路,它就会不断变化,我们也可以用来判断一个图中是否包含负权环路。有原理:

如果贝尔曼-福特算法遇到了有负权环路的情况,那么当t=节点数后,v[k]仍有可能在不断变化,而仍在变化的节点处于那个负权环路之中

我们以上一篇的负权环路为例子,看看贝尔曼-福特算法的更新过程:

最短路与动态规划(二)_第6张图片  最短路与动态规划(二)_第7张图片

t=4时刻,达到了节点数,但v[k]没有停止变化,我们进行回溯有: 

最短路与动态规划(二)_第8张图片

这就找到了负权环路。

2

多对多最短路算法

对贝尔曼-福特算法进行改进,就可以得到多对多的最短路算法,弗洛伊德-瓦尔肖算法——由弗洛伊德(R.W.Floyd)和瓦尔肖(S.Warshall)设计的算法。

最短路与动态规划(二)_第9张图片

这里唯一要注意的是,红色部分的t可能不是中介节点标号t,下面会举例加以说明。我们以利特尔维尔为例,展示弗洛伊德-瓦尔肖算法的全流程(代码附到最后)。

我们列出t=0到1和t=9到10的信息:

最短路与动态规划(二)_第10张图片

最短路与动态规划(二)_第11张图片

highlight的字体是相对上一次有变化的地方,更新原理按照算法的计算,这里要说明一下,对于t=10的v[4,1],我们计算得到:

9b3db7901df3c0c59203d23600d908dd.png

但这里需要确定d[4,1],这里应该设为5而不是10.因为d[4,1]表示更新后的路的倒数第二个节点,应该从上一次循环中的d[10,1]中得到,即t=9时d[10,1]=5.

接下来的问题是,得到了最后的v和d,如何找到任意两个节点的最优路径,有原理:

求出节点k到节点l的最短路,可以通过从l出发,回溯其邻居节点d[k,l],以此类推直到到达起点k

比如要找到节点3到节点8的最短路,我们从v[3,8]=79知道最短路长度为79,其路径按照下面进行回溯(红色部分):d[3,8]=10,于是d[3,10]=4,进而d[3,4]=3,回到了起点3,因此路径为:3-4-10-8

最短路与动态规划(二)_第12张图片

同样的,该算法也可以用于检验图中是否含有负权环路,仍以上一节负权环路的图为例子:

最短路与动态规划(二)_第13张图片

算法会在t=3停止迭代,其结果如下:

最短路与动态规划(二)_第14张图片

v[4,4]=-10,这告诉我们节点4在一个负权环路上,

我们进行回溯:d[4,4]=3,d[4,3]=2,d[4,2]=4,得到负权环路4-2-3-4

3

根据图特性定制高效算法

贝尔曼-福特算法和弗洛伊德-瓦尔肖算法可以解决没有负权环路的所有最短路问题。但当图满足更多条件的时候,这两个算法就不再是效率最高的算法了。对于一对多最短路问题,如果每条路的成本都是非负的,那么迪杰斯特拉算法(E.W.Dijkstra)具有更高的效率:

最短路与动态规划(二)_第15张图片

可以证明,迪杰斯特拉算法是计算一对多无负权的图最有效的方法。我们以德克萨斯运输公司为例,展现该算法的详细过程。

最短路与动态规划(二)_第16张图片

最短路与动态规划(二)_第17张图片

起点v[3]=0,因此p=3,步骤1更新过程为:

最短路与动态规划(二)_第18张图片

步骤3下一次p=5,再跳转回步骤1进行循环。

最后可以从表中直接得到从起点3到达其他节点的最短路长度。按照回溯方法可以得到最短路径,比如3到10,最短路径为3-8-7-10

迪杰斯特拉算法适用路是非负的一对多的最短路问题,包含有环路(注意,无负权环路不代表没有环路)的情况,如果图中的条件发生改变,对于一个无环图(必然仅含有弧,因为如果存在边,那么一条边的两个节点就可以构成一个有环子图),有更高效的的一对多无环有向图的最短路算法:

最短路与动态规划(二)_第19张图片

对于下图,起点是节点1,利用一对多无环有向图的最短路算法展示其全过程。

最短路与动态规划(二)_第20张图片

最短路与动态规划(二)_第21张图片

根据算法,起点s=0,然后p=2,值会被更新v[2]=0+5=5,d[2]=1.接下来节点p=3,v[3]=min{v[2]-10,v[1]+8}=-5,d[3]=2,以此类推,最后更新到节点9.最后利用回溯原理得到起点到任意节点的最短路径及长度。(该算法也开发了代码,已放在附录)

最后这里额外说明,对于最长路问题,只需要将这些算法的min替换为max,正无穷替换为负无穷即可。

附录:

#弗洛伊德瓦尔肖算法
import sys
import numpy as np
import pandas as pd


def Floyd_warshall(V):


    '''


    :param V: t=0时刻的v[k,l]矩阵,numpy格式
    :return:
    '''


    t = 0
    m,n = V.shape
    try:
        assert m == n
    except AssertionError as e:
        print('v[k,l]构成的原始矩阵不对, 请检查!')
        sys.exit()
    if not np.all(np.diagonal(V) == 0):
        print('矩阵V对角元素不全为0,不符合图原则,请检查!')
        sys.exit()


    #更新d[k,l]矩阵,numpy格式
    d = V.copy()
    d[(d == 0) | (d == float('inf'))] = 0
    for i in range(len(d)):
        d[i, :][(d[i, :] != 0)] = i + 1
    print('t=0的最短路程矩阵V和节点矩阵d:\n{},\n{}'.format(V,d))
    print('***********************************************')
    Ve,De = pd.DataFrame(),pd.DataFrame()
    Ve = pd.concat([Ve, pd.DataFrame(V)], axis=0)
    De = pd.concat([De, pd.DataFrame(d)], axis=0)
    while t < n:
        t += 1
        for k in range(n):
            for l in range(n):
                if t-1 != l:
                    per_v = V[k,l]
                    V[k,l]=min(V[k,l],V[k,t-1]+V[t-1,l])
                    if V[k,l] < per_v:
                        d[k,l] = d[t-1,l]
        print('t={}的最短路程矩阵V和节点矩阵d:\n{},\n{}'.format(t,V,d))
        print('***********************************************')
        Ve = pd.concat([Ve, pd.DataFrame(V)], axis=0)
        De = pd.concat([De, pd.DataFrame(d)], axis=0)
        if np.any(np.diagonal(V) < 0):
            print('迭代提前终止,迭代次数t={}'.format(t))
            break


    return Ve,De


if __name__ == '__main__':
    V = np.array(
        [[0,            12,         float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),float('inf')],
         [float('inf'),  0,          18,          float('inf'),float('inf'),32,          float('inf'),float('inf'),float('inf'),float('inf')],
         [float('inf'),float('inf'),0,           13,          float('inf'),float('inf'),30,          float('inf'),float('inf'),float('inf')],
         [float('inf'),float('inf'),float('inf'),0,            float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),38],
         [20,          float('inf'),float('inf'),float('inf'),0,           18,          float('inf'),float('inf'),float('inf'),float('inf')],
         [float('inf'),32,          float('inf'),float('inf'),18,          0,           28,          float('inf'),25,          float('inf')],
         [float('inf'),float('inf'),30,          float('inf'),float('inf'),28,          0,           float('inf'),21,          49          ],
         [float('inf'),float('inf'),float('inf'),float('inf'),18,          float('inf'),float('inf'),0,           36,          float('inf')],
         [float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),25,          21,          36,          0,           40          ],
         [float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),float('inf'),49,          28,          40,          0]])
    V,d = Floyd_warshall(V)
    V.to_csv('V_matrix.csv',index=False)
    d.to_csv('d_matrix.csv',index=False)
import sys
import numpy as np
import pandas as pd
import networkx as nx




def Acyclic_Directed_Graph(df,s=1):


    '''
    一对多无环图最短路算法
    :param df: 有向图dataframe,三列:source,target,weight
    :param s: 一对多最短路算法的起点s,必须是节点中最小值,默认为1
    :return:
    '''


    df['label'] = df['target'] - df['source']
    df['label'] = df['label'].apply(lambda x:1 if x > 0 else 0)
    a = list(set(df['target'])|set(df['source']))
    v = dict.fromkeys(a,np.nan)
    d = dict.fromkeys(a,'-')
    if 0 in set(df['label']):
        print('有向弧的节点命名不符合规则,source编码必须小于target,请检查')
        sys.exit()
    if s != min(a):
        print('一对多算法的起点必须为所有节点最小值,请检查')
        sys.exit()
    v[s] = 0 #起点s最短路长度为0
    deal_p = [s]
    G = nx.from_pandas_edgelist(df,'source','target',edge_attr=["weight"],create_using=nx.DiGraph())
    for p in sorted(v.keys()):
        if p in deal_p:
            continue
        di = {i:v[i]+k['weight'] for (i,j,k) in G.in_edges(p,data=True)} #入弧
        try:
            i = min(di,key=di.get)  #权重最小的入弧节点
            v[p] = di[i]
            d[p] = i
        except ValueError as e:
            v[p] = float('inf')
        deal_p.append(p)
    Re = pd.DataFrame([v.keys(),v.values(),d.values()]).T
    Re = Re.rename(columns={0:'p',1:'v[p]',2:'d[p]'})
    return Re

你可能感兴趣的:(最短路与动态规划(二))