07_第七章 迪克斯特拉算法

本章内容:

  • 继续图的讨论,介绍加权图——提高或降低某些边的权重
  • 介绍迪克斯特拉算法,让你能够找出加权图中前往x的最短路径
  • 介绍图中的环,迪克斯特拉算法只适用于有向无环图

在上一章中,我用广度优先算法可以找出段数最少的路径,但是如果要找出最快的路径,要用到另一种算法——迪克斯特拉算法(Dijkstra's algorithm)

 

目录

1  使用迪克斯特拉算法

2  术语

3  算法实现

4  解决上一章的问题(猪猪去珊珊家的最短路径)

5  小结


 

1  使用迪克斯特拉算法

关键理念:找出图中最便宜的节点,并确保没有到该节点的更便宜的路径!

迪克斯特拉算法包含4个步骤

  1. 找出“最便宜”的节点,即可在最短时间内到达的节点
  2. 更新该节点的邻居的开销,检查是否有前往它们更短的路径
  3. 重复这个过程,直到对图中的每个节点都这样做了
  4. 计算最终路径

下面来看看如何对下图使用这种算法(每个数字都代表时间,单位分钟)

第一步:找出最便宜的节点。你站在起点,不知道是前往节点A还是B。

前往A需要6分钟,而前往B需哟啊2分钟,至于其他节点,你还不知道需要多久。由于你还不知道前往终点需要多长的时间,因此需要假设为无穷大。节点B是最近的——2分钟就可以到达。

节点耗时
A6
B

2

终点

第二步:计算经节点B前往各个邻居的所需要的时间。

找到了一条通往A的更加近的路!经B到A的时间只需要5分钟!

节点耗时
A5(=2+3)
B

2

终点7(=2+5)

对于节点B的邻居,如果找到前往它的更短的路径,就更新其开销。在这里你找到了:

  • 前往节点A的更短路径(时间从6分钟缩短为5分钟

  • 前往终点的路径更短(时间从无穷大缩短为7分钟)

第三步:重复!

重复第一步:找出可在最短时间内前往的节点,你对节点B执行了第二步,处节点B外,可在最短时间内前往的节点是节点A。

重复第二步:更新节点A的所有邻居的路径。

对每一个节点都需要运行迪克特斯拉算法,但是对终点无需这样做!

发现前往终点的时间只需要6分钟!

节点耗时
A5
B

2

终点6

第四步:计算路径

先放着...

 

要是有一些一些疑惑,不妨看看这个

视频:迪科斯彻算法分解

 

 

 

2  术语

  1. 迪克斯特拉算法用于每条边都关联数字的图,这些数字称为权重(weight)
  2. 带权重的图称为加权图(weighted graph),不带权重的图称为非加权图(unweighted graph)
  3. 无向图意味着两个节点彼此指向对方,其实就是环。在无向图中,每条边都是一个环
  4. 迪克斯特拉算法只适用于有向无环图(directed acyclic graph    DAG)
  5. 如果有负权边,就不能使用迪克斯特拉算法,因为会导致算法不管用。需要用另外一种算法——贝尔曼-福德算法(Bellman-Ford algorithm)

 

3  算法实现

要编写这个问题的代码,需要三个散列表。

1、需要同时存储邻居和前往邻居的开销,还要使用另外一个散列表来表示边的权重(散列表里面包含散列表)

graph = {}
graph['start'] = {}
graph['start']['a'] = 6
graph['start']['b'] = 2
graph['a'] = {}
graph['a']['fin'] = 1
graph['b'] = {}
graph['b']['a'] = 3
graph['b']['fin'] = 5
graph['fin'] = {}
#获取关系节点图
print(graph)
#获取某一节点的邻居
print(graph['start'].keys())
#获取从一个节点到邻居节点的权重
print(graph['start']['a'])

#输出
{'start': {'a': 6, 'b': 2}, 'a': {'fin': 1}, 'b': {'a': 3, 'fin': 5}, 'fin': {}}
dict_keys(['a', 'b'])
6

2、需要用一个散列表来存储每个节点的开销(即从出发到节点之间需要花多少时间)

当然这个散列表会更新的!

infinity = float('inf')
#这个用于记录python的无穷大
costs = {}
costs['a'] = 6
costs['b'] = 2
costs['fin'] = infinity
print(costs)

#输出
{'a': 6, 'b': 2, 'fin': inf}

3、需要一个散列表存储父节点

parents = {}
parents['a'] = 'start'
parents['b'] = 'start'
parents['fin'] = None
print(parents)

#输出
{'a': 'start', 'b': 'start', 'fin': None}

4、最后需要一个数组记录用过的节点,因为对于同一个节点,你不用多次修改

processed = []

最终的代码如下:

graph = {}
graph['start'] = {}
graph['start']['a'] = 6
graph['start']['b'] = 2
graph['a'] = {}
graph['a']['fin'] = 1
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 = {}
parents['a'] = 'start'
parents['b'] = 'start'
parents['fin'] = None

processed = []

def find_lowest_cost_node(costs):
    #这个找出所有节点的花销里面中最小的那一个
    lowest_cost = float('inf')
    lowest_cost_node = None
    #这个是初始化预设花销为最大,节点为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
    #返回所找的花销最小的那个节点

node = find_lowest_cost_node(costs)
#在未处理的节点中找出开销最小的节点
while node is not None:
    #这个while循环在所有的节点都被处理了之后结束
    cost = costs[node]
    #从起点到node节点所需要的花销
    neighbors = graph[node]
    #这个找到花销最小的节点node的邻居有哪些
    for n in neighbors.keys():
        #对这个节点的邻居进行遍历
        new_costs = cost + neighbors[n]
        #新的总花销就是从起点到node节点所需要的花销以及该节点到他的其中一个邻居的花销的和
        if costs[n] > new_costs:
            #如果起点到这个邻居的花销比新的总花销贵
            costs[n] = new_costs
            #那么就把这个起点到这个邻居的花销换成便宜的这个
            parents[n] = node
            #同时要把这个邻居的父节点换成node这个节点,因为经过node这个节点,起点到这个邻居的节点的花销会少一点
    processed.append(node)
    #同时记录一下这个被处理过的节点
    node = find_lowest_cost_node(costs)
    #然后再从剩下的花销节点里面找出花销最少的那个,重新一轮while循环
print(parents)

#输出
{'a': 'b', 'b': 'start', 'fin': 'a'}

5、对最后的结果进行处理优化

starting_point = 'start'
#起点
destination = 'fin'
#终点
line = [starting_point]
#我们很清楚的看到这个parents中键值对的关系:键代表父节点,值代表子节点
def find_son(father,diction):
    #根据子节点找到父节点
    for key_value in diction:
        #这个里面返回的key_value是键
        if diction[key_value] == father:
            #如果某个键返回的值是这个要找的节点的孩子
            line.append(key_value)
            #那么就把这个父节点加入到这个数组里面去
            return find_son(key_value,diction)
        #进行递归,把这个找到的子节点作为父节点重新开始新一轮的找儿子哈哈哈哈
find_son(starting_point,parents)

spend = costs[destination]
#这个表示最少的花销
print('加权后的最短路径是:',line)
print('最少花销是:',spend)

输出:

加权后的最短路径是: ['start', 'b', 'a', 'fin']
最少花销是: 6

 

4  解决上一章的问题(猪猪去珊珊家的最短路径)

简单的举一反三一下

代码实现如下:

graph = {}
graph['浙江'] = {}
graph['浙江']['安徽'] = 200
graph['浙江']['江西'] = 400
graph['浙江']['福建'] = 350
graph['安徽'] = {}
graph['安徽']['湖北'] = 250
graph['安徽']['江西'] = 300
graph['江西'] = {}
graph['江西']['湖南'] = 350
graph['福建'] = {}
graph['福建']['江西'] = 250
graph['湖北'] = {}
graph['湖北']['湖南'] = 150
graph['湖南'] = {}

infinity = float('inf')
costs = {}
costs['安徽'] = 200
costs['江西'] = 400
costs['福建'] = 350
costs['湖北'] = infinity
costs['湖南'] = infinity

parents = {}
parents['安徽'] = '浙江'
parents['江西'] = '浙江'
parents['福建'] = '浙江'
parents['湖北'] = None
parents['湖南'] = None

processed = []

def find_lowest_cost_node(costs):
    #这个找出所有节点的花销里面中最小的那一个
    lowest_cost = float('inf')
    lowest_cost_node = None
    #这个是初始化预设花销为最大,节点为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
    #返回所找的花销最小的那个节点

node = find_lowest_cost_node(costs)
#在未处理的节点中找出开销最小的节点
while node is not None:
    #这个while循环在所有的节点都被处理了之后结束
    cost = costs[node]
    #从起点到node节点所需要的花销
    neighbors = graph[node]
    #这个找到花销最小的节点node的邻居有哪些
    for n in neighbors.keys():
        #对这个节点的邻居进行遍历
        new_costs = cost + neighbors[n]
        #新的总花销就是从起点到node节点所需要的花销以及该节点到他的其中一个邻居的花销的和
        if costs[n] > new_costs:
            #如果起点到这个邻居的花销比新的总花销贵
            costs[n] = new_costs
            #那么就把这个起点到这个邻居的花销换成便宜的这个
            parents[n] = node
            #同时要把这个邻居的父节点换成node这个节点,因为经过node这个节点,起点到这个邻居的节点的花销会少一点
    processed.append(node)
    #同时记录一下这个被处理过的节点
    node = find_lowest_cost_node(costs)
    #然后再从剩下的花销节点里面找出花销最少的那个,重新一轮while循环

print(parents)

starting_point = '浙江'
#起点
destination = '湖南'
#终点
line = [starting_point]
#我们很清楚的看到这个parents中键值对的关系:键代表父节点,值代表子节点
def find_son(father,diction):
    #根据子节点找到父节点
    for key_value in diction:
        #这个里面返回的key_value是键
        if diction[key_value] == father:
            #如果某个键返回的值是这个要找的节点的孩子
            line.append(key_value)
            #那么就把这个父节点加入到这个数组里面去
            return find_son(key_value,diction)
        #进行递归,把这个找到的子节点作为父节点重新开始新一轮的找儿子哈哈哈哈
find_son(starting_point,parents)

spend = costs[destination]
#这个表示最少的花销
print('猪猪去珊珊家的最短的路线是:',line)
print('猪猪去珊珊家的最少路程是:',spend)

输出:

{'安徽': '浙江', '江西': '浙江', '福建': '浙江', '湖北': '安徽', '湖南': '湖北'}
猪猪去珊珊家的最短的路线是: ['浙江', '安徽', '湖北', '湖南']
猪猪去珊珊家的最少路程是: 600

爱你哦,等我来!

 

5  小结

  • 广度优先搜索用于在非加权图中查找最短路径
  • 迪克斯特拉算法用于在加权图中查找最短路径
  • 仅当权重为正时迪克斯特拉算法才管用
  • 如果图中包含负权边,去使用贝尔曼-福德算法

 

 

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
斯特算法(Dijkstra's algorithm)是一种用于解决单源最短路径问题的算法。它可以找到从一个顶点到其他所有顶点的最短路径。 以下是使用 MATLAB 实现斯特算法的示例代码: ```matlab function [distances, path] = dijkstra(graph, start) nodes = size(graph, 1); distances = inf(1, nodes); visited = false(1, nodes); previous = zeros(1, nodes); distances(start) = 0; for i = 1:nodes current = min_distance(distances, visited); visited(current) = true; neighbors = find(graph(current, :)); for j = 1:length(neighbors) neighbor = neighbors(j); if distances(current) + graph(current, neighbor) < distances(neighbor) distances(neighbor) = distances(current) + graph(current, neighbor); previous(neighbor) = current; end end end path = get_path(previous, start); end function index = min_distance(distances, visited) minDist = inf; index = -1; for i = 1:length(distances) if ~visited(i) && distances(i) < minDist minDist = distances(i); index = i; end end end function path = get_path(previous, start) path = []; current = start; while current ~= 0 path = [current path]; current = previous(current); end end ``` 你可以将这段代码保存为一个名为 `dijkstra.m` 的文件。然后,你可以通过以下方式调用该函数来计算最短路径: ```matlab graph = [0 4 0 0 0 0 0 8 0; 4 0 8 0 0 0 0 11 0; 0 8 0 7 0 4 0 0 2; 0 0 7 0 9 14 0 0 0; 0 0 0 9 0 10 0 0 0; 0 0 4 0 10 0 2 0 0; 0 0 0 14 0 2 0 1 6; 8 11 0 0 0 0 1 0 7; 0 0 2 0 0 0 6 7 0]; [start_node, end_node] = deal(1, 5); [distances, path] = dijkstra(graph, start_node); disp(['最短路径长度为:', num2str(distances(end_node))]); disp('最短路径为:'); disp(path); ``` 这是一个计算给定图形中从节点1到节点5的最短路径的示例。你可以根据自己的需求修改输入图形和起始节点。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值