这是鄙人的第一篇技术博客,作为算法小菜鸟外加轻度写作障碍者,写技术博客也算是对自己的一种挑战和鞭策吧~
言归正传,什么是dijkstra算法呢?
-dijkstra算法是一种解决最短路径问题的简单有效的方法~也算是一种非常naive&effcient的最优化算法吧~
最短路径问题
如上图,从点A->点F,最短路径为A->C->D->F,Min=3+3+3=9
假设用暴力深度搜索遍历所有路径的话,时间复杂度O将会是指数级的(NP-hard Problem)~
dijkstra算法
作为我最喜欢的经典算法之一,dijkstra算法并没有用到高深的图论和拓扑学的知识,
它的核心思想也很简单:贪心法+动态规划
下面贴出维基百科上的伪代码:
1 function Dijkstra(Graph, source): 2 dist[source] := 0 // Distance from source to source 3 for each vertex v in Graph: // Initializations 4 if v ≠ source 5 dist[v] := infinity // Unknown distance function from source to v 6 previous[v] := undefined // Previous node in optimal path from source 7 end if 8 add v to Q // All nodes initially in Q (unvisited nodes) 9 end for 10 11 while Q is not empty: // The main loop 12 u := vertex in Q with min dist[u] // Source node in first case 13 remove u from Q 14 15 for each neighbor v of u: // where v has not yet been removed from Q. 16 alt := dist[u] + length(u, v) 17 if alt < dist[v]: // A shorter path to v has been found 18 dist[v] := alt 19 previous[v] := u 20 end if 21 end for 22 end while 23 return dist[], previous[] 24 end function
贪心的思想体现在 Line12: u := vertex in Q with min dist[u]
从所有的待优化的点Q中找出距离起点最近的点u~
动态规划的思想体现在 Line16-20: 最优表达式为 dist[v] := min{dist[v],dist[u] + length(u, v)}
即如果存在dist[start->u->v]<dist[start->v],则将路径更新~
在dijkstra算法里,每个点的路径可能会被更新N次(length(Q)>N>=0),并不存在一个明确的状态递进。
因此,如何保证在更新N次后,一定能求出最优解,是算法的核心问题~
答案就是贪心法,运用贪心法优化更新的顺序,确保从小到大,更新一遍Q即求出最优解。
优先队列+dijkstra算法
让我们分析一下dijkstra算法的时间复杂度:
总时间复杂度=找最短距离 u := vertex in Q with min dist[u] 的时间复杂度 +
更新距离 dist[v] := min{dist[v],dist[u] + length(u, v)} 的时间复杂度
对于一个无向图G(V,E)来说,
找最短距离的时间复杂度为O(|V|*|V|)(共循环V次,每次V个点),考虑到Q每次递减1,实际复杂度为O(|V|^2/2);
由于图共有E条边,每条边最多被更新2次(1条边2个端点),因此更新距离的时间复杂度为O(2*|E|)。
因此,总时间复杂度=O(2*|E|+|V|^2/2)
然后,实际情况中经常会遇到 |V|^2>>|E| 的稀疏图,即O(2*|E|+|V|^2/2)=O(|V|^2/2)~
因此,如果我们能够优化 findMIN部分,即可大大优化稀疏图下的dijkstra算法~
findMIN的部分优化方法很多,最简单的就是用二分搜索O(logN)代替线性搜索 O(N)~
这里我们将集合Q转化成一个优先队列(priority queue),这样findMIN的时间复杂度变成了O(1),而每次更新priority queue需要花费O(log|V|)~
综上,采用优先队列之后,总时间复杂度=O(2*|E|+|V|*log|V|),
这样的优化对于稀疏图(|V|^2>>|E|)来说,尤为有效~
P.S. 本文仅想谈谈鄙人对dijkstra算法的一些浅见,即粗浅又不够系统,还望各位大牛多多指点~
下一篇博文将会拓展 优先队列(priority queue) 的内容(如果鄙人木有被板砖拍死的话^ ^)
最后贴上鄙人用python实现的dijkstra+priority queue的demo,经测试在G(V=2000,E=10000)时,priority queue能够提升近1倍的运算速度:
1 # -*- coding: utf-8 -*- 2 """ 3 Created on Sun Aug 24 2014 4 5 @author: Sperling 6 """ 7 import random,time,heapq 8 9 ###采用优先队列的dijkstra算法### 10 def dijkstra_pq(conj): 11 ##初始化 各点到起点的最优距离(dis)## 12 dis=[0] 13 dis.extend([-1]*(len(conj)-1)) 14 for c in conj[0]: 15 dis[c[1]]=c[0] 16 ##初始化 待优化的点(排除起点)## 17 pool=[i for i in range(1,len(conj))] 18 ##初始化 路径记录列表## 19 pre=[0 if dis[i]>=0 else -1 for i in range(len(conj))] 20 pre[0]=-1 21 ##初始化 优先队列(Fibonacci heap)## 22 pq=[(dis[i],i) for i in range(1,len(conj)) if dis[i]>=0] 23 heapq.heapify(pq) 24 25 while len(pool)>0: 26 ##找出待优化池中的最短路径点(argmin)## 27 argmin=-1 28 while (argmin not in pool) and len(pq)>0: 29 MIN,argmin=heapq.heappop(pq) 30 ##将最短路径点(argmin)从池中捞出## 31 if argmin not in pool: 32 break 33 else: 34 pool.remove(argmin) 35 ##更新与argmin直接连通的点<->起点的最短路径## 36 for c in conj[argmin]: 37 if c[0]+MIN<dis[c[1]] or dis[c[1]]<0: 38 dis[c[1]]=c[0]+MIN 39 pre[c[1]]=argmin 40 heapq.heappush(pq,(dis[c[1]],c[1])) 41 42 return dis,pre 43 44 ###原始dijkstra算法### 45 def dijkstra(conj): 46 ##初始化 各点到起点的最优距离(dis)## 47 dis=[0] 48 dis.extend([-1]*(len(conj)-1)) 49 for c in conj[0]: 50 dis[c[1]]=c[0] 51 ##初始化 待优化的点(排除起点)## 52 pool=[i for i in range(1,len(conj))] 53 ##初始化 路径记录列表## 54 pre=[0 if dis[i]>=0 else -1 for i in range(len(conj))] 55 pre[0]=-1 56 57 while len(pool)>0: 58 ##找出待优化池中的最短路径点(argmin)## 59 MIN,argmin=1000000,-1 60 for i in pool: 61 if dis[i]>0 and MIN>=dis[i]: 62 MIN,argmin=dis[i],i 63 ##将最短路径点(argmin)从池中捞出## 64 if argmin<0: 65 break 66 else: 67 pool.remove(argmin) 68 ##更新与argmin直接连通的点<->起点的最短路径## 69 for c in conj[argmin]: 70 if c[0]+MIN<dis[c[1]] or dis[c[1]]<0: 71 dis[c[1]]=c[0]+MIN 72 pre[c[1]]=argmin 73 74 return dis,pre 75 76 ###回溯最优路径### 77 def trace(pre,p): 78 route=[] 79 while p>=0: 80 route.append(p) 81 p=pre[p] 82 return route[::-1] 83 84 ###构造随机图,不能保证图的连通性### 85 def randomroute(nV,nE): 86 ##构造nE*nE的随机关联矩阵(mat),其中有2*nV个点是连通的## 87 mat=[[0 if i==j else -1 for i in range(nV)] for j in range(nV)] 88 rE=random.sample(xrange(nV*(nV-1)/2),nE) 89 for k in rE: 90 i,j=0,k 91 while i<j: 92 i+=1 93 j-=i 94 i+=1 95 r=random.randint(1,100) 96 mat[i][j],mat[j][i]=r,r 97 ##将随机关联矩阵(mat) 转化为 每个顶点的直接连通点列表(route)## 98 route=[] 99 for i in range(len(mat)): 100 route.append([]) 101 for j in range(len(mat[i])): 102 if mat[i][j]>0: 103 route[i].append((mat[i][j],j)) 104 105 return route 106 107 if __name__=='__main__': 108 conj=randomroute(2000,10000) 109 t0=time.clock() 110 dis,pre=dijkstra(conj) 111 t1=time.clock() 112 print 'time_cost dijkstra:%f'%(t1-t0) 113 print dis[len(conj)-1] 114 print trace(pre,len(conj)-1) 115 116 t0=time.clock() 117 dis,pre=dijkstra_pq(conj) 118 t1=time.clock() 119 print 'time_cost dijkstra+priority queue:%f'%(t1-t0) 120 print dis[len(conj)-1] 121 print trace(pre,len(conj)-1)