算法学习笔记——Dijkstra算法

Dijkstra算法

用于计算有权图中一个节点到其他节点的最短路径。

基本原理

从起始点出发,重复寻找所有点中距离源点最近的且未访问过的结点,把该节点设置为已访问,然后利用该结点更新该节点的邻居节点的距离数组,直到访问过全部结点为止,最终的距离数组即为源点到其余各点的最短路径距离。

注:

距离数组:用于记录当前所有点到源点的距离,D[i]表示从源点到第i个点的最短路径距离。初始时数组中的每个值应该为无穷大。这个数组在算法进行中被不断更新,Dijkstra算法结束后,此数组中各个值应该表示源点到其余各点的最短路径距离。

已访问的点:每一次重复中找到的、**距离源点最近(D[i]最小)**且未访问过的节点。

算法正确性

img

如图,0是源点。与0相连的三个点中,0-2的距离是最短的,那么从0到2的最短路径长就是2,即D[2]=2。原因是从0出发,若走中途不经过2的路径到达2,那么一定会经过1或3,从0到1或从0到3的距离就已经比从0直接到2的距离长了。若中途经过2最后又回到2,那么就绕了圈,也是比从0直接到2长的。所以D[2]=2。然后把2设置为已访问,代表已经确定了从源点出发到2的最短路径长,后续不应该再更新D[2]了(体现了dp的思想)。

2是已访问的点,1和3是他的邻居节点。根据算法,还要把点1和点3距离源点的距离D[1]D[3]进行更新。虽然不能确定此时的D[1]D[3]就一定是从源点到点1和点3的最短路径,但是对于这样的点,可以一边遍历所有节点一边不断更新D[i]为更小的值,来找到可能的最短路径。

接下来,按照距离数组中的数据,找到与源点距离最短的点,标记为已访问。目前,距离数组中最小值为D[1]=3,根据算法,可以确定从0到1的最小距离就是3。

我们这样做是否正确,就决定了这个算法是否正确。一般地,设集合S表示已访问点的集合,(对于本例,S的初始成员显然地就是0/0和2)对于每一个已访问点i,其到源点的最短距离已然确定,那么D[i]+点i到其邻居节点的距离就是从源点出发,经过节点i,到达这个邻居节点的最短路径。在算法进行的过程中,在S集合到达这个状态之前,我们已经遍历过所有已访问点,并且按照上述方法更新了所有他们的邻居节点k的D[k]最小值。那么此时所有不在S中的点k,且不为无穷大的D[k]值就表示从源点出发,只能经过集合S中的点,最终到达该点k的最小距离。每一个D[k]值都对应着唯一的一条这样的线路。

现在假设有一条比这条线路还短的线路,起点终点不变。那么他就无法“只能经过集合S中的点”了,设路径中第一个不在S中的点为w。那么“起点—w”长度<“起点—w—k”长度<D[k]。而w是第一个不在S中的点,那么在S中必然能找到一个点,使w是他的邻居节点。说明对于所有不在S中的点,且不为无穷大的D值,是D[w]而不是D[k],这就与前提条件矛盾。因此,说明D中的最小值D[k]对应的线路,就是从源点出发到节点k的最小距离。

走山路与heap优化

题目描述

某同学在一处山地里,地面起伏很大,他想从一个地方走到另一个地方,并且希望能尽量走平路。 现有一个m*n的地形图,图上是数字代表该位置的高度,"#"代表该位置不可以经过。 该同学每一次只能向上下左右移动,每次移动消耗的体力为移动前后该同学所处高度的差的绝对值。现在给出该同学出发的地点和目的地,需要你求出他最少要消耗多少体力。

输入

第一行是整数 m,n,p,m是行数,n是列数,p是测试数据组数。 0 <= m,n,p <= 100 接下来m行是地形图 再接下来n行每行前两个数是出发点坐标(前面是行,后面是列),后面两个数是目的地坐标(前面是行,后面是列)(出发点、目的地可以是任何地方,出发点和目的地如果有一个或两个在"#"处,则将被认为是无法达到目的地)

输出

n行,每一行为对应的所需最小体力,若无法达到,则输出"NO"

样例输入

4 5 3
0 0 0 0 0
0 1 1 2 3
# 1 0 0 0
0 # 0 0 0
0 0 3 4
1 0 1 4
3 4 3 0

样例输出

2
3
NO
Dijkstra做法
m,n,case=map(int,input().split())#行数、列数、测试用例
output=[]#输出
mount_map=[]#地形图,内涵有权图的信息
for _ in range(m):
    mount_map.append(input().split())#str型
direction=[(-1,0),(1,0),(0,1),(0,-1)]#邻居节点的方向

for _ in range(case):#多个测试用例
    sx,sy,ex,ey=map(int,input().split())#起点坐标、终点坐标
    #起点终点不可到达
    if mount_map[sx][sy]=='#' or mount_map[ex][ey]=='#':
        output.append('NO')
        continue

    visited=set()#已访问节点,已知最短路径的点
    D=[[float('inf')]*n for _ in range(m)]#距离数组
    #起点已访问(查找之后标记)
    D[sx][sy]=0
    for j in range((m+1)*(n+1)):#最多循环(m+1)*(n+1)次
        #查找距离源点最近的点
        minimum=float('inf')
        minx=miny=0
        for _x in range(m):
            for _y in range(n):
                if D[_x][_y]<minimum and (_x,_y) not in visited:
                    minimum=D[_x][_y]
                    minx=_x
                    miny=_y
        if minx==ex and miny==ey:
            output.append(minimum)
            visited.add((ex,ey))
            break
        #标记已访问,更新邻居节点
        visited.add((minx,miny))
        for i in range(4):
            dx, dy = direction[i]
            x1,y1=minx+dx,miny+dy
            if 0<=x1<m and 0<=y1<n and mount_map[x1][y1]!='#' and (x1,y1) not in visited:
                D[x1][y1]=min(D[x1][y1],minimum+abs(int(mount_map[minx][miny])-int(mount_map[x1][y1])))
    if (ex,ey) not in visited:
        output.append('NO')

for o in output:
    print(o)

查找D中最小值双重循环会超时,但是答案是正确的。因为heapq每次弹出的是最小值(对于元组来说是元组的第一个元素最小的那个元组),所以可以把距离放在元组的第一个位置,坐标放在元组的第2、3个位置。接着使用heapq进行弹出和推入。这样就不需要初始化和遍历距离数组D了。heapq.heappop()出来的坐标就是当前距离源点最近的点。代码思路和Dijkstra完全一样。

heapq优化
from heapq import *
m,n,case=map(int,input().split())#行数、列数、测试用例
output=[]#输出
mount_map=[]#地形图,内涵有权图的信息
for _ in range(m):
    mount_map.append(input().split())#str型
direction=[(-1,0),(1,0),(0,1),(0,-1)]#邻居节点的方向

for _ in range(case):#多个测试用例
    sx,sy,ex,ey=map(int,input().split())#起点坐标、终点坐标
    #起点终点不可到达
    if mount_map[sx][sy]=='#' or mount_map[ex][ey]=='#':
        output.append('NO')
        continue
    visited=set()#已访问节点,已知最短路径的点
    heap=[]
    heappush(heap,(0,sx,sy))
    while heap:
        mintire,minx,miny=heappop(heap)
        if minx==ex and miny==ey:
            output.append(mintire)
            visited.add((ex,ey))
            break
        #标记已访问,更新邻居节点
        visited.add((minx,miny))
        for i in range(4):
            dx, dy = direction[i]
            x1,y1=minx+dx,miny+dy
            if 0<=x1<m and 0<=y1<n and mount_map[x1][y1]!='#' and (x1,y1) not in visited:
                heappush(heap,(mintire+abs(int(mount_map[minx][miny])-int(mount_map[x1][y1])),x1,y1))
    if (ex,ey) not in visited:
        output.append('NO')

for o in output:
    print(o)
  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值