图的最短路径

图的最短路径


图最短路径的实现一般用 狄杰斯特拉算法。它能在一个加权图中计算从 A A A 点运动到 B B B 点的 最短路径。加权图如下所示:

在这里插入图片描述

传统的狄杰斯特拉算法并不支持负权值,这里把狄杰斯特拉算法做了一点点改进,还是称之为 狄克斯特拉算法


狄克斯特拉算法原理


第一步:建立起点( A A A)到各个点的空表

起始节点权重代价最优路径是否访问
AC ∞ \infty -不可达
-D ∞ \infty -不可达
-E ∞ \infty -不可达
-F ∞ \infty -不可达
-G ∞ \infty -不可达
-B ∞ \infty -不可达

其中 是否访问 表示是从起点 A A A 开始是否访问过该节点(例如 D D D)的每一个邻居节点。其取值状态如下:

  1. 正在遍历: 表示计算了从 A A A 到目标节点的路径,但是还没有计算从起始节点 A A A 到目标节点的邻居节点的路径;
  2. 不可达: 表示起始点 A A A 没有路径到达目标节点;
  3. 已遍历: 表示计算了从起始节点 A A A 到目标节点、目标节点的所有邻居节点的路径;

第二步:从起点初始化表格
起点 A A A 的邻居位 [ D , C ] [D, C] [D,C],则有:

  1. P a t h < A , D > = A → D Path_{<A, D>} = A \rightarrow D Path<A,D>=AD L o s s < A , D > = 10 Loss_{<A, D>} = 10 Loss<A,D>=10,设置为未遍历
  2. P a t h < A , C > = A → C Path_{<A, C>} = A \rightarrow C Path<A,C>=AC L o s s < A , C > = 4 Loss_{<A, C>} = 4 Loss<A,C>=4,设置为未遍历

更新图表数据如下:

起始节点权重代价最优路径是否访问
AD 10 10 10 A → D A \rightarrow D AD正在遍历
-C 4 4 4 A → C A \rightarrow C AC正在遍历
-E ∞ \infty -不可达
-F ∞ \infty -不可达
-G ∞ \infty -不可达
-B ∞ \infty -不可达

第三步:取出未遍历节点中
例如:提取状态为 正在遍历 节点 ( D D D),节点 D D D 的邻居为 [ E , G ] [E,G] [E,G]

  1. 计算 P a t h < A , E > = A → D → E Path_{<A, E>} = A \rightarrow D \rightarrow E Path<A,E>=ADE L o s s < A , E > = 15 Loss_{<A, E>} = 15 Loss<A,E>=15
  2. 如果 L o s s < A , E > Loss_{<A, E>} Loss<A,E> 小于目前路径权重代价,则更新当前路径,并设置节点 E E E 正在遍历
  3. 计算 P a t h < A , G > = A → D → G Path_{<A, G>} = A \rightarrow D \rightarrow G Path<A,G>=ADG L o s s < A , G > = 18 Loss_{<A, G>} = 18 Loss<A,G>=18
  4. 如果 L o s s < A , G > Loss_{<A, G>} Loss<A,G> 小于目前路径权重代价,则更新当前路径,并设置节点 G G G 正在遍历
  5. 设置节点 D D D已遍历
起始节点权重代价最优路径是否访问
AD 10 10 10 A → D A \rightarrow D AD已遍历
-C 4 4 4 A → C A \rightarrow C AC正在遍历
-E 15 15 15 A → D → E A \rightarrow D \rightarrow E ADE正在遍历
-F ∞ \infty -不可达
-G 18 18 18 A → D → G A \rightarrow D \rightarrow G ADG正在遍历
-B ∞ \infty -不可达

第四步: 重复第三步直至遍历完所有节点
最终的结果如下:

起始节点权重代价最优路径是否访问
AD 10 10 10 A → D A \rightarrow D AD已遍历
-C 4 4 4 A → C A \rightarrow C AC已遍历
-E 15 15 15 A → D → E A \rightarrow D \rightarrow E ADE已遍历
-F 20 20 20 A → D → E → F A \rightarrow D \rightarrow E \rightarrow F ADEF已遍历
-G 18 18 18 A → D → G A \rightarrow D \rightarrow G ADG已遍历
-B 24 24 24 A → D → E → F → B A \rightarrow D \rightarrow E \rightarrow F \rightarrow B ADEFB终点

总结: 以上就得出了从起始点 A 依次到节点 D、C、E、F、G、B 的最短路径。
以上就是狄克斯特拉算法原理


交换商品


假设你有一个乐谱,想和朋友交换钢琴,一群朋友之间的商品存在一个交换图(如下图所示),权重是需要额外支付的费用。那么,你的交换路径是怎么样的,才能使费用最少呢?注意其中存在一条负权边

在这里插入图片描述

我们按照 狄克斯特拉算法原理 求解结果如下:

起始节点权重代价最优路径是否访问
AB 5 5 5 A → B A \rightarrow B AB已遍历
-C − 2 -2 2 A → B → C A \rightarrow B \rightarrow C ABC已遍历
-D 20 20 20 A → B → D A \rightarrow B \rightarrow D ABD已遍历
-E 25 25 25 A → B → E A \rightarrow B \rightarrow E ABE已遍历
-F 35 35 35 A → B → E → F A \rightarrow B \rightarrow E \rightarrow F ABEF终点

最终的结果如下:

在这里插入图片描述


代码实现


建立网络

# 建立网络
Graph = {}
Graph['A'] = {"B":5, "C":0}
Graph['B'] = {"D":15, "E":20, "C":-7}
Graph['C'] = {"D":30, "E":35}
Graph['D'] = {"F":20}
Graph['E'] = {"F":10}
Graph['F'] = {}

建立表格


Infty = 100000
# 建立图表
# flag == -1:表示不可达
# flag == 0:表示未遍历
# flag == 1:表示已遍历
Table = {}
Table['A'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到A的路径信息
Table['B'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到B的路径信息
Table['C'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到C的路径信息
Table['D'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到D的路径信息
Table['E'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到E的路径信息
Table['F'] = {'Loss': Infty, 'Path':[], 'flag':-1} # 起点到F的路径信息

# 未遍历节点列表
NoTraverseNodes = set()

初始化表格


# 初始化图表
def InitialTable(table, start):
    Table.pop(start) # 删除起始节点
    global NoTraverseNodes
    for node in Graph[start]:
        NoTraverseNodes.add(node) # 添加进未遍历节点
        Table[node]['Loss'] = Graph[start][node]
        Table[node]['Path'] = [start, node]
        Table[node]['flag'] = 0

    return table

# 打印Table
for node in InitialTable(Table, 'A'):
    print(node, ":", Table[node])
"""
B : {'Loss': 5, 'Path': ['A', 'B'], 'flag': 0}
C : {'Loss': 0, 'Path': ['A', 'C'], 'flag': 0}
D : {'Loss': 100000, 'Path': [], 'flag': -1}
E : {'Loss': 100000, 'Path': [], 'flag': -1}
F : {'Loss': 100000, 'Path': [], 'flag': -1}
"""

搜索最短路径


# 进行最短路径搜索
def SearchShortestPath(table, start):
    global NoTraverseNodes
    while len(NoTraverseNodes): # 遍历未遍历节点列表
        node1 = NoTraverseNodes.pop()
        for node2 in Graph[node1]: # 访问节点node1的每一个邻居
            # 计算start到node2的损失值
            loss = Table[node1]['Loss'] + Graph[node1][node2]
            if loss < Table[node2]['Loss']: # 更新start到node2的路径
                Table[node2]['Loss'] = loss
                Table[node2]['Path'] = Table[node1]['Path'] + [node2]
                Table[node2]['flag'] = 0
                NoTraverseNodes.add(node2)
        table[node1]['flag'] = 1 # 表示此节点已遍历过

    return table
print()
for node in SearchShortestPath(Table, 'A'):
    print(node, ":", Table[node])
"""
B : {'Loss': 5, 'Path': ['A', 'B'], 'flag': 1}
C : {'Loss': -2, 'Path': ['A', 'B', 'C'], 'flag': 1}
D : {'Loss': 20, 'Path': ['A', 'B', 'D'], 'flag': 1}
E : {'Loss': 25, 'Path': ['A', 'B', 'E'], 'flag': 1}
F : {'Loss': 35, 'Path': ['A', 'B', 'E', 'F'], 'flag': 1}
"""

最终的结果显示了,从节点 A A A 到各个节点的最短路径。

传统的 狄克斯特拉算法 无法解决负权边的最短路径搜索问题,此时可以使用 贝尔曼-福德 算法。


参考资料


  1. 《算法图解》
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值