Dijkstra最短路算法笔记

最短路算法算是基础算法, 我还是总是忘。。维基有个动图很好,比较直观,可是还不够友好,于是自己做了点笔记,仅供参考。网上关于Dijkstra的文章也不少,适合的才是最好的。

动态图 - 来自wiki

Dijkstra_Animation.gif - 无向图,正权值路径
  • 顶点为城市,顶点间连线表示城市相连通,线上数字为城市间距离。


    城市间距离
  • 目标为找出从序号为1的城市到序号为5的城市的最短路径。

  • 该路径可以由 已知最短路径 迭代求出。

  • 每次从已知最短路径中选出没探索过最短的,从该路径终点出发,探索新的最短路径。

  • 一开始,从出发点到达它之外的所有顶点的已知最短路径为无穷大,到出发点自己的最短路径为0。

  • 现在,出发点的已知最短路径最小,则从出发点出发,探索与其直连的所有顶点,如果路径长度比到该顶点的已知最短路径小,则刷新该顶点的已知最短路径

  • 接着,出发点已经探索过了,从未出发探索过的已知最短路径中选出最小的一个,即从城市2出发,探索与其直连的城市,如果到达该城市的路径长度比已知最短路径小,则刷新最短路径。可以看到,从城市2到3的路径总长17>城市3目前的最短路径9,不满足条件,不刷新城市3的最短路径,而到城市4的已知最短路径刷新为7+15=21。(已知最短路径的计算都是从出发点开始)

  • 依次类推,直到我们遇到目的地是已知最短路径里未探索的所有顶点中最短的一个时,终止探索。

  • 值得注意的是,我们每一步探索过程中的当前出发点的最短路径是确定的了不会再变了,因为它是所有未探索过的已知最短路径中的最小的了,所以不存在从其它地方再加一段路程到达它还会比它更小的情况。

  • 刚看动态图可能跟不上,我们用工具将gif转为png一帧帧过一遍就好理解了, 图在后面,先看代码。

简单代码

  • 维基百科里的伪代码,可以比较简单地转换为python代码。
V = [ # 顶点列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市间距离, -1表示无穷大,与图中一致
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    d = [-1 if v != s else 0 for v in V] # 出发点到各点的已知最短路径的初始值, 到自身为0
    N = len(V) # 顶点数
    p = [None] * N # 出发点到各点的已知最短路径的前一个点
    S = set() # 这个集合暂时没什么用处
    Q = set(range(N)) # 生成所有顶点的虚拟序号的集合, 以0起始
    
    def Extract_Min(Q): # 将顶点集合Q中有最小d[u]值的顶点u从Q中删除并返回u,可先不管
        u_min = None
        for u in Q:
            if d[u] != -1: # 无穷大则跳过
                u_min = u
        for u in Q:
            if d[u] == -1: # 无穷大则跳过
                continue
            if d[u_min] > d[u]:
                u_min = u
        Q.remove(u_min)
        return u_min
        
    v_d = V.index(dest) # 目的地的虚拟序号
    
    while Q : # 当Q非空
        u = Extract_Min(Q) # 将顶点集合Q中有最小d[u]值的顶点u从Q中删除并返回u
        if u == v_d : break # 可以在找到目的地之后立即终止,也可继续查找完所有最短路径
        S.add(u)
        
        v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到达的顶点, 这里没有去除在S中的顶点
        for v in v_reached_from_u:
            if d[v] > d[u] + w[u][v] or d[v] == -1: # 如果经过u再到v的距离更短
                d[v] = d[u] + w[u][v] # 用该值更新已知最短路径
                p[v] = u # 则已知到v的最短路径上的倒数第二个点为u
        print('d=', d)
    
    v = v_d  # 由p列表可以回溯出最短路径
    final_route = []
    while v != V.index(s):
        final_route.insert(0, str(V[v]))
        v = p[v]
    final_route.insert(0, str(V[v]))
    print('最终路径', '-->'.join(final_route))

        
Dijkstra()
#d= [0, 7, 9, -1, -1, 14]
#d= [0, 7, 9, 22, -1, 14]
#d= [0, 7, 9, 20, -1, 11]
#d= [0, 7, 9, 20, 20, 11]
#最终路径 1-->3-->6-->5

使用堆

  • 关键思路中从已知最短路径中选择最小的一个,还要增加(更新)新的值,让人想到堆结构。

  • 利用python的堆结构heapq,可以很简单地获取到最小的已知最短路径。

  • 我们每次从堆中取出已知最短路径中最短的,将从该点探索下的所有新路径直接放入堆中,不用管重复,堆会返回有最小路径的,只要我们额外使用一个集合来记录已经探索过的顶点即可,使每个顶点只探索一次。

  • 先看没有记录路径只算长度的,代码相对上面简洁了不少。

from heapq import heappop, heappush

V = [ # 顶点列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市间距离, -1表示无穷大
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    N = len(V) # 顶点数
    S = set()
    Q = [(0, V.index(s))] # 路径长,城市虚拟序号
    
    v_d = V.index(dest)
    
    while Q : # 当Q非空
        d, u = heappop(Q)
        if u == v_d : 
            print(d)
            break # 可以在找到目的地之后立即终止,也可继续查找完所有最短路径
        # 如果到目的地的距离已经是当前所有距离中最短的,那不可能会有更短的,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到达的顶点
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q, ((d + w[u][v]), v)) # 到顶点v的某条路径的距离
                    
Dijkstra() # 20
  • 要记录路径,只需要在放入堆中的每个小元组尾部再加上个记号,改动很少
from heapq import heappop, heappush

V = [ # 顶点列表
      1,  2,  3,  4,  5,  6]

w = [ # 城市间距离, -1表示无穷大
    [ 0,  7,  9, -1, -1, 14],
    [ 7,  0, 10, 15, -1, -1],
    [ 9, 10,  0, 11, -1,  2],
    [-1, 15, 11,  0,  6, -1],
    [-1, -1, -1,  6,  0,  9],
    [14, -1,  2, -1,  9,  0]]

def Dijkstra(V=V, w=w, s=1, dest=5):
    N = len(V) # 顶点数
    S = set()
    Q = [(0, V.index(s), str(s))] # 路径长, 序号, 路径
    
    v_d = V.index(dest)
    
    while Q : # 当Q非空
        d, u, p = heappop(Q) 
        if u == v_d : 
            print(d, p)
            break # 可以在找到目的地之后立即终止,也可继续查找完所有最短路径
        # 如果到目的地的距离已经是当前所有距离中最短的,那不可能会有更短的,所以退出
        if u not in S:
            S.add(u)
            v_reached_from_u = [i for i in range(N) if w[u][i] != -1] # u能到达的顶点
            for v in v_reached_from_u:
                if v not in S:
                    heappush(Q,(
                        (d + w[u][v]), v, ''.join((p,'->',str(V[v])))
                    )) # 到顶点v的某条路径的距离
                    
Dijkstra() # 20 1->3->6->5

分解图 - 使用文末工具

Dijkstra_Animation-0.png
Dijkstra_Animation-1.png
Dijkstra_Animation-2.png
Dijkstra_Animation-3.png
Dijkstra_Animation-4.png
Dijkstra_Animation-5.png
Dijkstra_Animation-6.png
Dijkstra_Animation-7.png
Dijkstra_Animation-8.png
Dijkstra_Animation-9.png
Dijkstra_Animation-10.png
Dijkstra_Animation-11.png
Dijkstra_Animation-12.png
Dijkstra_Animation-13.png
Dijkstra_Animation-14.png
Dijkstra_Animation-15.png
Dijkstra_Animation-16.png
Dijkstra_Animation-17.png
Dijkstra_Animation-18.png
Dijkstra_Animation-19.png
Dijkstra_Animation-20.png
Dijkstra_Animation-21.png
Dijkstra_Animation-22.png
Dijkstra_Animation-23.png
Dijkstra_Animation-24.png
Dijkstra_Animation-25.png
Dijkstra_Animation-26.png

工具

  • 在网上找到的效果最好的从gif到png的转化代码
# https://gist.github.com/BigglesZX/4016539
import os
from PIL import Image


'''
I searched high and low for solutions to the "extract animated GIF frames in Python"
problem, and after much trial and error came up with the following solution based
on several partial examples around the web (mostly Stack Overflow).
There are two pitfalls that aren't often mentioned when dealing with animated GIFs -
firstly that some files feature per-frame local palettes while some have one global
palette for all frames, and secondly that some GIFs replace the entire image with
each new frame ('full' mode in the code below), and some only update a specific
region ('partial').
This code deals with both those cases by examining the palette and redraw
instructions of each frame. In the latter case this requires a preliminary (usually
partial) iteration of the frames before processing, since the redraw mode needs to
be consistently applied across all frames. I found a couple of examples of
partial-mode GIFs containing the occasional full-frame redraw, which would result
in bad renders of those frames if the mode assessment was only done on a
single-frame basis.
Nov 2012
'''


def analyseImage(path):
    '''
    Pre-process pass over the image to determine the mode (full or additive).
    Necessary as assessing single frames isn't reliable. Need to know the mode 
    before processing all frames.
    '''
    im = Image.open(path)
    results = {
        'size': im.size,
        'mode': 'full',
    }
    try:
        while True:
            if im.tile:
                tile = im.tile[0]
                update_region = tile[1]
                update_region_dimensions = update_region[2:]
                if update_region_dimensions != im.size:
                    results['mode'] = 'partial'
                    break
            im.seek(im.tell() + 1)
    except EOFError:
        pass
    return results


def processImage(path):
    '''
    Iterate the GIF, extracting each frame.
    '''
    mode = analyseImage(path)['mode']
    
    im = Image.open(path)

    i = 0
    p = im.getpalette()
    last_frame = im.convert('RGBA')
    
    try:
        while True:
            print("saving %s (%s) frame %d, %s %s" % (path, mode, i, im.size, im.tile))
            
            '''
            If the GIF uses local colour tables, each frame will have its own palette.
            If not, we need to apply the global palette to the new frame.
            '''
            if not im.getpalette():
                im.putpalette(p)
            
            new_frame = Image.new('RGBA', im.size)
            
            '''
            Is this file a "partial"-mode GIF where frames update a region of a different size to the entire image?
            If so, we need to construct the new frame by pasting it on top of the preceding frames.
            '''
            if mode == 'partial':
                new_frame.paste(last_frame)
            
            new_frame.paste(im, (0,0), im.convert('RGBA'))
            new_frame.save('%s-%d.png' % (''.join(os.path.basename(path).split('.')[:-1]), i), 'PNG')

            i += 1
            last_frame = new_frame
            im.seek(im.tell() + 1)
    except EOFError:
        pass

# 使用
import os
os.chdir(r'E:\python\2017_3_1\dijkst\png')
processImage('E:/python/2017_3_1/dijkst/Dijkstra_Animation.gif')

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 现实生活中有很大一类问题可以用简洁明了的图论语言来描述,可以转化为图论问题。 相关定义 图可以表示为G=(V, E...
    芥丶未央阅读 1,682评论 0 7
  • 课程介绍 先修课:概率统计,程序设计实习,集合论与图论 后续课:算法分析与设计,编译原理,操作系统,数据库概论,人...
    ShellyWhen阅读 2,270评论 0 3
  • 故事的主人公是只有我和当事人所知的一位姑娘,写此文之前也已取得姑娘的允许。如事有雷同,纯属巧合。 从前有一位姑娘,...
    恋念依旧阅读 190评论 0 1
  • 高二的暑假,减肥在我的朋友圈子里刮起了一阵龙卷风,身边人人都想减肥,于是大家就一起相约减肥,跑步打球快走,晚上吃完...
    国际小乡村阅读 753评论 0 1
  • 红翡缘二楼整个成了一个小医院,各种仪器摆满了最大那个房间。 林雪初得到了最好的医疗看护,每一个细节都能看出医院高度...
    可可豆子阅读 341评论 0 5