《算法图解》学习笔记(第七章)

《算法图解》学习笔记-第七章-狄克斯特拉算法



前言

本文为自己的算法学习笔记,用来将所学知识及时输出
所用书籍:《算法图解》


7.1 加权图

定义:设G为图,对图的每一条边e来说,都对应于一个实数W(e)(可以通俗的理解为边的“长度”,只是在数学定义中图的权可以为负数),我们把W(e)称为e的“权”。把这样的图G称为“加权图”——百度百科
个人理解:类似带宽,同样的物理长度,千兆带宽(加权图)比百兆带宽(图),传输的数据量多。
狄克斯特拉算法就是用来在加权图中查找最短路径的办法。


7.2 使用狄克斯特拉(Dijkstra)算法

在下面这幅图中数字代表着时间,单位为分钟,如何找出从起点到终点的耗时最短路径?
请添加图片描述
如果用上一章讲的广度优先搜索,你可能会得到:起点 ——> A ——> 终点 这样的路径,但它是耗时最短的路径吗?显然不是,他需要耗时7分钟,而我们仔细观察一下就会发现:由起点 ——> B ——> A ———> 终点 只需6分钟。

狄克斯特拉算法包含四步:

  1. 找出可在最短时间内到达的节点。
  2. 更新其邻居节点开销。
  3. 重复上述过程,直到对所有节点都进行过此操作。
  4. 计算出到终点的最优路径。

第一步:由起点出发,到达A点需要6分钟,到达B点需要2分钟,到达终点所需时间暂时不知,暂设为∞(无穷大)。具体表格如下:

节点时长父节点
A6起点
B2起点
终点未知

第二步:我们发现由起点到达B节点耗时最短,因此来到B节点。由B节点出发到达A节点需要3分钟,到达终点需要5分钟。现在我们了解了到达由起点到达终点所需的时长:2+5=7(分钟),并且发现了到达A节点的更短路径:起点 ——> B ——> A 只需用时5分钟,更新表格 :

节点时长父节点
A~~6~~ 5~~起点~~ B
B2起点
终点5B

第三步:对B节点重复上述操作。
重复第一步:发现到A节点所用时间最少,来到A节点。
重复第二步:更新A节点的邻居节点开销,发现了到达终点的更短时间为6分钟,更新表格如下:

节点时长父节点
A5B
B2起点
终点6A

最后一步:由终点开始回溯,找出最短路径。查找终点的父节点发现其为A,而A节点的父节点为B,B节点的父节点为起点,至此我们发现了由起点到终点耗时最短的路径:起点 ——> B ——> A ——> 终点 耗时6分钟。


7.3 术语

  1. 权重:狄克斯特拉算法用于每条边带有关联数字的图,这些数字被称为权重(weight)。
  2. 加权图&非加权图:带有权重的图称为加权图(weighted graph),反之称为非加权图(unweighted graph)。
  3. 环:如下图一样,从一个节点出发走一圈又回到当前节点。请添加图片描述
    上一章讲过有向图和无向图,无向图的关系双向的,也可以看做是一个环,而若一直沿着环循环前进则永远达不到终点,所以狄克斯特拉算法只适用于有向无环图(directed acyclic)

7.4 负权边

假设你从A节点到B节点的时候遇到了时空乱流,唰一下你回到了30分钟前,因为时间倒退,这时你从A节点到B节点的时间应为负值,即权重为负值(当然这并不严谨,只是用来举个例子),如下图所示:
请添加图片描述

第一步:从start开始,查找用时最短路径,得到下表:

节点时长父节点
A5start
B2start
endunknown

第二步:发现到B节点用时最短,来到B节点并更新其邻居节点开销,如下表:

节点时长父节点
A5start
B2start
end12B

将B节点标记为已处理

第三步:重复以上操作,当来到A节点发现,由A节点到B节点所用时间远远小于从起点到A节点
因此准备更新B节点,这时要注意了,因为B节点已经被标记过,这意味着已经没有前往该节点的更短路径,可你明白这并不正确。end节点没有邻居,算法结束。
如下表:

节点时长父节点
A5start
~~B~~ ~~-25~~ ~~A~~
end12B

因此狄克斯特拉算法不能解决带负权的加权图。


7.5 狄克斯特拉算法的Python实现

代码如下:

'''
    Dijkastra Algorithm 迪克斯特拉算法(仅当权值为正时使用)
'''
# Graph 权重关系表

graph = {}
graph['start'] = {}         # 由起点到其邻近节点的权值的信息
graph['start']['a'] = 6
graph['start']['b'] = 2

graph['a'] = {}             # 由a节点到其邻近节点的信息
graph['a']['fin'] = 1

graph['b'] = {}             # 由b节点到其邻近节点的信息
graph['b']['a'] = 3
graph['b']['fin'] = 5

graph['fin'] = {}

# cost 开销表
infinity = float('inf')		# 定义无穷大量
costs = {}
costs['a'] = 6
costs['b'] = 2
costs['fin'] = infinity		# 由于不清楚start距fin有多远,暂设为无穷大

# parents 父节点表
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 = cost
            lowest_cost_node = node
    
    return lowest_cost_node


# 算法主要部分
def dijkastra():
    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) # 获取下一个最小开销节点


# test
if __name__ == '__main__':
    dijkastra()
    print(processed)						# ['b', 'a', 'fin']
    print(parents)							# {'a': 'b', 'b': 'start', 'fin': 'a'}
    print(costs)							# {'a': 5, 'b': 2, 'fin': 6}


end:总结

  • 图的边均带有相应权值的图名为加权图
  • 两个节点互相指向称为环,无向图可看做环。
  • 狄克斯特拉算法可以解决查找加权图最优路径的问题。
  • 狄克斯特拉算法只可用于解决有向无环图。
  • 狄克斯特拉算法不能解决负权值图的最优路径问题。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值