用图讲解狄克斯特拉(DiskStra)算法,python实现 。

最短路径

在一个带权图中,顶点V0到图中任意一个顶点Vi的一条路径所经过边上的权值之和,定义为该路径的带权路径长度,把带权路径最短的那条路径称为最短路径。
如图所示,求双子峰到金门大桥的最短距离。

此类问题要用到狄克斯特拉(DiskStra)算法

使用狄克斯特拉(DiskStra)算法

图中每个数字都是时间,为此找出从起点到终点用时最短的路径。

如果你使用广度优先搜索,广度优先搜索算法BFS讲解以及python 实现
你将得到下面这条段数最少的路径。

这条路径耗费7分钟,我们接下来找找还有没有更短的路径。
狄克斯特拉(DiskStra)算法 包含4个步骤:

  1. 找出可在最短时间内到达的点
  2. 更新该节点的邻居的开销
  3. 重复计算该过程,直到对所有节点都做了
  4. 计算最终路径

第一步:你站在起点,可以走的点是A,B。到达A需要6分钟,到达B需要2分钟,其他节点不能到达,设为无穷。

第二步:因为起点到B最近,然后计算B前往各邻居的距离
B到A的距离是3,我们找到一条前往A点的更短路径5,从起点直接前往A点距离是6,则更新库中起点到A的距离。

对于节点B的邻居,如果找到前往它的更短路径,就更新其开销,在这里我们找到了:前往A点的更短路径,时间由6分钟缩短到了5分钟;前往终点的更短路径,时间由无穷大缩短到7分钟。

第三步:重复
重复第一步,找出最短时间可以到达的点,前面对节点B执行了第二步,除B点外,还有节点A

重读第二步,更新节点A所有邻居的开销

你发现前往终点的时间为6分钟。

对每个节点都应用狄克斯特拉(DiskStra)算法,现在知道前往B节点2分钟,A分钟,终点6分钟。

狄克斯特拉(DiskStra)算法适用于有向无环图。

算例1实现

以图为例

第一步:解决这个问题,需要准备三个散列表

第一个散列表表示图,依次是父节点,子节点,权重。第一个散列表图可以用散列表嵌套表示。

"""     、、、、、、  第一个散列表  用于表示图   、、、、、、     """
graph={}
#起点 到各邻居的关系
graph["start"]={}
graph["start"]["a"]=6
graph["start"]["b"]=2
#print(graph["start"].keys())#dict_keys(['a', 'b'])
#节点a到各邻居关系
graph["a"]={}
graph["a"]["fin"]=1

#节点b到各邻居关系
graph["b"]={}
graph["b"]["a"]=3
graph["b"]["fin"]=5

#终点与各邻居关系
graph["fin"]={}#终点没有邻居

第二个散列表为初始时起点出发到各节点的距离,随着算法进行需要不断更新。

""" 第二个散列表 节点的开销。节点的开销表示起点出发到该节点需要多少时间"""
infinity=float("inf")#无穷大
costs={}
costs['a']=6
costs['b']=2
costs['fin']=infinity

第三个散列表。左边为子节点,右边为父节点,用来表示路径。随着算法进行需要不断更新,是针对出发点建立的

""" 第三个散列表 存储 路径 . parents['a']="start"表示节点a的上一节点是start,如果后面发现更短路径,可以修改为parents['a']=b"""
parents={}
parents['a']="start"
parents['b']="start"
parents['fin']=None#出发点不能直接到达

第二步:需要一个数组,存储已经处理过的节点

"""                        需要一个数组,存储已经处理过的节点 """
processed=[]

第三步:最小开销节点函数

"""                        开销最小的函数 """
def find_lowest_cost_node(costs):
    lowest_cost=float('inf')
    lowest_cost_node=None
    for node in costs:#遍历每个节点
        cost=costs[node]
        if cost<lowest_cost and node not in processed:#如果当前节点小于最小开销,且没有处理过
            lowest_cost_node=node #则将该节点设为最小开销节点
            lowest_cost=cost#更新最小开销
    return lowest_cost_node

第四步:DiskStra算法

""" DiskStra算法"""
node=find_lowest_cost_node(costs) #在未处理的节点中找到开销最小的节点,即离出发点最近的点
while node  is not None: #这个循环表示所有节点处理过才会结束
    cost=costs[node]
    neighbors=graph[node]
    for n in neighbors.keys():#遍历当前节点的所有邻居
        new_cost=cost+neighbors[n]
        if costs[n]>new_cost:#如果经当前节点离该邻居更近
            costs[n]=new_cost#就更新该节点的开销
            parents[n]=node #同时将该邻居的父节点设置为该节点
    processed.append(node)#将该节点标记为处理过
    node=find_lowest_cost_node(costs) #查找接下来要循环处理的点,并循环

全部代码


"""       第一个散列表  用于表示图        """
graph={}
#起点 到各邻居的关系
graph["start"]={}
graph["start"]["a"]=6
graph["start"]["b"]=2
#print(graph["start"].keys())#dict_keys(['a', 'b'])
#节点a到各邻居关系
graph["a"]={}
graph["a"]["fin"]=1

#节点b到各邻居关系
graph["b"]={}
graph["b"]["a"]=3
graph["b"]["fin"]=5

#终点与各邻居关系
graph["fin"]={}#终点没有邻居

""" 第二个散列表 节点的开销。节点的开销表示起点出发到该节点需要多少时间,每个节点都要列出来"""
infinity=float("inf")#无穷大
costs={}
costs['a']=6
costs['b']=2
costs['fin']=infinity

""" 第三个散列表 存储 路径 . parents['a']="start"表示节点a的上一节点是start,如果后面发现更短路径,可以修改为parents['a']=b,每个节点都要列出来"""
parents={}
parents['a']="start"
parents['b']="start"
parents['fin']=None#出发点不能直接到达


"""                        需要一个数组,存储已经处理过的节点 """
processed=[]

"""                        开销最小的函数 """
def find_lowest_cost_node(costs):
    lowest_cost=float('inf')
    lowest_cost_node=None
    for node in costs:#遍历每个节点
        cost=costs[node]
        if cost<lowest_cost and node not in processed:#如果当前节点小于最小开销,且没有处理过
            lowest_cost_node=node #则将该节点设为最小开销节点
            lowest_cost=cost#更新最小开销
    return lowest_cost_node


""" DiskStra算法"""
node=find_lowest_cost_node(costs) #在未处理的节点中找到开销最小的节点,即离出发点最近的点
while node  is not None: #这个循环表示所有节点处理过才会结束
    cost=costs[node]
    neighbors=graph[node]
    for n in neighbors.keys():#遍历当前节点的所有邻居
        new_cost=cost+neighbors[n]
        if costs[n]>new_cost:#如果经当前节点离该邻居更近
            costs[n]=new_cost#就更新该节点的开销
            parents[n]=node #同时将该邻居的父节点设置为该节点
    processed.append(node)#将该节点标记为处理过
    node=find_lowest_cost_node(costs) #查找接下来要循环处理的点,并循环


#打印结果
print('costs',costs)
print('parents',parents)

流程图解

找到开销最少的点

获取该节点的开销和邻居

遍历邻居

每个节点都要开销,开销指从起点到该节点需要多长时间,在这里,将计算从起点到B再到A的开销,而不是直接到A的开销

和原始开销进行对比

找到一条前往节点A的更短路径,更新节点A的开销 这条路径由B到A,由此更新节点A的父节点

现在回到for循环,下一个邻居是终点

经节点B前往终点需要7分钟,

原始终点节点开销为无穷大,现在7分钟,更新终点节点的开销和父节点

现在将节点B设为被处理过的

接下来要处理的点

获取节点A的开销和邻居

节点A只有一个邻居,终点。当前前往终点需要7分钟,经过节点A需要多久,见图

更短,更新终点的开销和父节点

节点A被添加进处理过的。
接下来要处理的点为终点fin。
终点fin没有邻居,不更新,然后显示终点也被添加进处理过的。
node变为空,退出while循环。结束。

如果要改变起点,如起点改到A,再来探讨该问题。只需修改costs和parents散列表即可。
算例1借鉴:《算法图解》

算例2实现

算例1用到的数据结构是散列表,本例子用矩阵来做。

第一步:定义图和拓扑结构

"""   一.定义图   """
### ====================给一个所有节点的列表
list_nodes_id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
### ==================== 给一个拓扑的常数矩阵.
fin= float('inf') # 无穷大。这意味着没有联系。
### fin_topo是表示拓扑的二维邻接矩阵
fin_topo = [
    [fin, 2.9, 2.5, fin, 2, fin, 1.8, 3, 1.8, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 0
    [2.9, fin, 1.3, fin, fin, 1.7, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 1
    [1, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 2
    [fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 3
    [1, fin, fin, 1, fin, fin, fin, fin, fin, 1, 1, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 4
    [fin, 1, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 5
    [1, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 6
    [1, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 7
    [1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin],  # 8
    [fin, fin, fin, fin, 1, fin, fin, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 9
    [fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin],  # 10
    [fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, 1, fin, fin, 1, 1, fin, fin, fin, fin, fin, fin],  # 11
    [fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin],  # 12
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, 1, fin, fin, 1, 1, fin, fin],  # 13
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, 1, fin, 1, 1, fin, fin, fin, fin],  # 14
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, 1, fin],  # 15
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, fin, fin, 1],  # 16
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, fin, fin],  # 17
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, fin, 1, fin],  # 18
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin, 1],  # 19
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin],  # 20
]

### --- 读取拓扑,并生成给定拓扑中的所有边。
edges = []
for i in range(len(fin_topo)):
    for j in range(len(fin_topo[0])):
        if i != j and fin_topo[i][j] != fin:
            edges.append((i, j, fin_topo[i][j]))  ### (i,j) 是一个链接;fin_topo[i][j]这里是1,链接的长度(i,j).
#print(edges)
#[(0, 1, 2.9), (0, 2, 2.5), (0, 4, 2), (0, 6, 1.8), (

edges

第二步 算法

"""算法实现"""
from collections import defaultdict
from heapq import *

#heapq 堆,一般指最小堆
"""
heap = [1,3,4,2,6,8,9]
heapq.heapify(heap)
# heap = [1,2,4,3,6,8,9]
"""
"""
g
{0: [(2.9, 1), (2.5, 2), (2, 4), (1.8, 6), (3, 7), (1.8, 8)], 1: [(2.9, 0), (1.3, 2), (1.7, 5)], 2: [(1, 0), (1, 1), (1, 3)], 3: [(1, 2), (1, 4)], 4: [(1, 0), (1, 3), (1, 9), (1, 10), (1, 11)], 5: [(1, 1), (1, 6)], 6: [(1, 0), (1, 5), (1, 7)], 7: [(1, 0), (1, 6), (1, 8)], 8: [(1, 0), (1, 7), (1, 9), (1, 12)], 9: [(1, 4), (1, 8), (1, 11)], 10: [(1, 4), (1, 11), (1, 13)], 11: [(1, 4), (1, 9), (1, 10), (1, 13), (1, 14)], 12: [(1, 8), (1, 14)], 13: [(1, 10), (1, 11), (1, 14), (1, 17), (1, 18)], 14: [(1, 11), (1, 12), (1, 13), (1, 15), (1, 16)], 15: [(1, 14), (1, 16), (1, 18), (1, 19)], 16: [(1, 14), (1, 15), (1, 20)], 17: [(1, 13), (1, 18)], 18: [(1, 13), (1, 15), (1, 17), (1, 19)], 19: [(1, 15), (1, 18), (1, 20)], 20: [(1, 16), (1, 19)]})


"""

def dijkstra_raw(edges, from_node, to_node):
    g = defaultdict(list)#使用list作第一个参数,可以很容易将键-值对序列转换为列表字典。
    for l, r, c in edges:
        g[l].append((c, r))
    q, seen = [(0, from_node, ())], set()#q=[(0, from_node, ())],q为当前节点创建一个堆
    while q:
        (cost, v1, path) = heappop(q)#删除q中的顶
        if v1 not in seen:#如果V1没有被查看,V1为出发点
            seen.add(v1)#将V1添加进已查看
            path = (v1, path)#(点,路径)
            if v1 == to_node:#如果当前点是目标点
                return cost, path#返回距离和路径
            for c, v2 in g.get(v1, ()):#遍历V1的所有邻居 c为到邻居的距离,V2为邻居
                if v2 not in seen:
                    heappush(q, (cost + c, v2, path))#修改起点到V2的距离
    return float("inf"), []

全部代码

"""   一.定义图   """
# ====================给一个所有节点的列表
list_nodes_id = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
### ==================== 给一个拓扑的常数矩阵.
fin= float('inf') # 无穷大。这意味着没有联系。
#fin_topo是表示拓扑的二维邻接矩阵
fin_topo = [
    [fin, 2.9, 2.5, fin, 2, fin, 1.8, 3, 1.8, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 0
    [2.9, fin, 1.3, fin, fin, 1.7, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 1
    [1, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 2
    [fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 3
    [1, fin, fin, 1, fin, fin, fin, fin, fin, 1, 1, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 4
    [fin, 1, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 5
    [1, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 6
    [1, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 7
    [1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin],  # 8
    [fin, fin, fin, fin, 1, fin, fin, fin, 1, fin, fin, 1, fin, fin, fin, fin, fin, fin, fin, fin, fin],  # 9
    [fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, fin, fin, fin, fin, fin, fin],  # 10
    [fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, 1, fin, fin, 1, 1, fin, fin, fin, fin, fin, fin],  # 11
    [fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, fin, fin],  # 12
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, 1, fin, fin, 1, 1, fin, fin],  # 13
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, 1, fin, 1, 1, fin, fin, fin, fin],  # 14
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, 1, fin],  # 15
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, 1, fin, fin, fin, fin, 1],  # 16
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, fin, fin, 1, fin, fin],  # 17
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, 1, fin, 1, fin, 1, fin],  # 18
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin, 1],  # 19
    [fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, fin, 1, fin, fin, 1, fin],  # 20
]

#--- 读取拓扑,并生成给定拓扑中的所有边。
edges = []
for i in range(len(fin_topo)):
    for j in range(len(fin_topo[0])):
        if i != j and fin_topo[i][j] != fin:
            edges.append((i, j, fin_topo[i][j]))  ### (i,j) 是一个链接;fin_topo[i][j]这里是1,链接的长度(i,j).
#print(edges)
#[(0, 1, 2.9), (0, 2, 2.5), (0, 4, 2), (0, 6, 1.8), (

#算法实现
from collections import defaultdict
from heapq import *




def dijkstra_raw(edges, from_node, to_node):
    g = defaultdict(list)#使用list作第一个参数,可以很容易将键-值对序列转换为列表字典。
    for l, r, c in edges:
        g[l].append((c, r))
    q, seen = [(0, from_node, ())], set()#q=[(0, from_node, ())],q为当前节点创建一个堆
    while q:
        (cost, v1, path) = heappop(q)#删除q中的顶
        if v1 not in seen:#如果V1没有被查看,V1为出发点
            seen.add(v1)#将V1添加进已查看
            path = (v1, path)#(点,路径)
            if v1 == to_node:#如果当前点是目标点
                return cost, path#返回距离和路径
            for c, v2 in g.get(v1, ()):#遍历V1的所有邻居 c为到邻居的距离,V2为邻居
                if v2 not in seen:
                    heappush(q, (cost + c, v2, path))
    return float("inf"), []

start = int(input("请输入起始点:"))
end = int(input("请输入终点:"))
print("=== Dijkstra算法 ===")
print("找从 %s 到 %s的最短路径:" % (start, end))
length, path_queue = dijkstra_raw(edges,start, end)#path_queue路径
print('距离',length)
print('路径',path_queue)

0到15的距离为4.8,路径0-8-12-14-15

工具包讲解

参考我以前的博文
最短路径Dijkstra讲解,工具包使用 python

在这里插入图片描述

作者:电气-余登武

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

总裁余(余登武)

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

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

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

打赏作者

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

抵扣说明:

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

余额充值