迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
该算法复杂度为n^2
这里有一篇讲解的很清晰的文章:http://blog.chinaunix.net/uid-26548237-id-3834514.html
下面说说我个人的理解:
就以这张图为例:
要找出A点到其他点的最短路径,应该怎么找?
这个算法的思路是:
- 先找出离A直接连通,且最近的那个点。怎么找?遍历所有的点与A的距离,可以知道AB=6,AC=3。DEF没有与A相连,那AD就设为无穷大。如此一轮比较下来就能知道离A最近的点是C。
- 最近的点找到了,就以这个点出发,继续找离C最近的点。同上也是遍历所有点,但在这个过程中,同时比较A经过C到该点的距离与A直接与该点连通的距离。比如遍历到B点时,比较ACB和AB,如果ACB小于AB,则将A到B的距离保存为ACB。到D点时,比较ACD和AD,以此类推。一轮比较后,在找到离C最近的点的同时,更新了A到各个点的最短距离。直到最后一个点遍历完毕,则A与所有点的最短距离也就知道了。
具体的实现如下:
1 #include <iostream> 2 using namespace std; 3 4 #define MAXVEX 20 5 #define MAXEDGE 20 6 #define INFINITY 65535 7 8 9 10 typedef struct 11 { 12 int vexs[MAXVEX]; 13 int arc[MAXVEX][MAXVEX]; 14 int numVertexes,numEdges; 15 }MGraph; 16 17 typedef int Patharc[MAXVEX]; 18 typedef int ShortPathTable[MAXVEX]; 19 20 void CreateGraph(MGraph* G) 21 { 22 cout<<"请输入边数和顶点数: "; 23 int d,n,i,j; 24 cin>>d>>n; 25 G->numVertexes = n; 26 G->numEdges = d; 27 28 //给顶点和边初始化 29 for(i = 0;i<G->numVertexes;i++) 30 G->vexs[i] = i; 31 for(i = 0;i<G->numVertexes;i++) 32 { 33 for(j = 0;j<G->numVertexes;j++) 34 { 35 if(i==j) 36 G->arc[i][j] = 0; 37 else 38 G->arc[i][j] = G->arc[j][i] = INFINITY; 39 } 40 } 41 42 G->arc[0][1]=1; 43 G->arc[0][2]=5; 44 G->arc[1][2]=3; 45 G->arc[1][3]=7; 46 G->arc[1][4]=5; 47 48 G->arc[2][4]=1; 49 G->arc[2][5]=7; 50 G->arc[3][4]=2; 51 G->arc[3][6]=3; 52 G->arc[4][5]=3; 53 54 G->arc[4][6]=6; 55 G->arc[4][7]=9; 56 G->arc[5][7]=5; 57 G->arc[6][7]=2; 58 G->arc[6][8]=7; 59 60 G->arc[7][8]=4; 61 62 for(i = 0;i<G->numVertexes;i++) 63 { 64 for(j = i;j<G->numVertexes;j++) 65 { 66 G->arc[j][i] = G->arc[i][j]; 67 } 68 } 69 } 70 71 /* Floyd算法,求网图G中各顶点v到其余顶点w的最短路径P[v][w]及带权长度D[v][w]。 */ 72 void Dijkstra(MGraph G,int v0, Patharc *P, ShortPathTable *D) 73 { 74 int v,w,k,min; 75 76 int final[MAXVEX];/* final[w]=1表示求得顶点v0至vw的最短路径 */ 77 78 for(v = 0;v<G.numVertexes;v++) 79 { 80 (*P)[v] = 0; 81 /* 初始化P */ 82 (*D)[v] = G.arc[v0][v]; 83 /* D[v]值即为对应点间的权值 */ 84 final[v] = 0; 85 } 86 87 (*D)[v0] = 0; 88 final[v0] = 1; 89 90 /* 开始主循环,每次求得v0到某个v顶点的最短路径 */ 91 for(v = 1;v<G.numVertexes;v++) 92 { 93 min = INFINITY;/* 当前所知离v0顶点的最近距离 */ 94 for(w = 0;w<G.numVertexes;w++)/* 寻找离v0最近的顶点 */ 95 { 96 if((*D)[w]<min && !final[w]) 97 { 98 min = (*D)[w]; /* w顶点离v0顶点更近 */ 99 k = w; 100 } 101 } 102 final[k] = 1; /* 将目前找到的最近的顶点置为1 */ 103 104 /* 修正当前最短路径及距离 */ 105 for(w = 0;w<G.numVertexes;w++)/* 寻找离v0最近的顶点 */ 106 { 107 /* 如果经过v顶点的路径比现在这条路径的长度短的话 */ 108 if(min+G.arc[k][w] < (*D)[w] && !final[w]) 109 { 110 /* 说明找到了更短的路径,修改D[w]和P[w] */ 111 (*D)[w] = min+G.arc[k][w]; 112 (*P)[w] = k; 113 } 114 } 115 } 116 117 } 118 119 120 int main() 121 { 122 int v0,i,j; 123 124 MGraph G; 125 126 Patharc P; 127 ShortPathTable D; 128 129 CreateGraph(&G); 130 v0 = 0; 131 Dijkstra(G,v0,&P,&D); 132 133 cout<<"最短路径倒序如下: "; 134 for(i = 1;i<G.numVertexes;i++) 135 { 136 cout<<"v0 - v"<<i<<" : "; 137 j = i; 138 while(P[j]!=0) 139 { 140 cout<<P[j]<<" "; 141 j = P[j]; 142 } 143 cout<<endl; 144 } 145 146 cout<<" 源点到各顶点的最短路径长度为: "; 147 148 for(i=1; i<G.numVertexes; ++i) 149 { 150 cout<<"v"<<G.vexs[0]<<" - v"<<G.vexs[i]<<" : "<<D[i]<<endl; 151 } 152 return 0; 153 154 155 }
算法优化:
参考这篇文章:http://blog.csdn.net/zhongyanghu27/article/details/8221276
该算法复杂度为n^2,我们可以发现,如果边数远小于n^2,对此可以考虑用堆这种数据结构进行优化,取出最短路径的复杂度降为O(1);每次调整的复杂度降为O(elogn);e为该点的边数,所以复杂度降为O((m+n)logn)。
实现
1 /* 2 Dijkstra的算法思想: 3 在所有没有访问过的结点中选出dis(s,x)值最小的x 4 对从x出发的所有边(x,y),更新 5 dis(s,y)=min(dis(s,y),dis(s,x)+dis(x,y)) 6 */ 7 8 #include <iostream> 9 #include <cstdio> 10 #include <queue> 11 #include <vector> 12 using namespace std; 13 const int Ni = 10000; 14 const int INF = 1<<27; 15 16 17 //这里是位运算<<,1<<3=8,1<<27即2的27次方 18 19 typedef struct node 20 { 21 int x; 22 int d; 23 node(){} 24 node(int a,int b) 25 {x = a;d = b;} 26 27 //>对应小顶堆,<对应大顶堆 28 bool operator <(const node& a) const 29 { 30 if(d == a.d) 31 return x<a.x; 32 else 33 return d>a.d; 34 } 35 }node; 36 37 vector<node> eg[Ni];//eg是一个node数组,保存邻接关系 38 39 int dis[Ni],n;//dis使用1-n的部分 40 41 void Dijkstra(int s) 42 { 43 int i,j; 44 for(i=0;i<=n;i++)//要到n 45 dis[i] = INF; 46 priority_queue<node> q; 47 dis[s] = 0; 48 q.push(node(s,dis[s])); 49 while(!q.empty()) 50 { 51 node x = q.top(); 52 q.pop(); 53 for(j = 0;j<eg[x.x].size();j++)//遍历x的所有邻接点 54 { 55 node y = eg[x.x][j];//y是x的邻接点 56 if(dis[y.x]>x.d+y.d) 57 { 58 dis[y.x] = x.d+y.d; 59 q.push(node(y.x,dis[y.x])); 60 } 61 } 62 } 63 64 } 65 66 67 int main() 68 { 69 int m,a,b,d;//关系个数 70 while(cin>>n>>m,m+n) 71 { 72 for(int i = 0;i<=n;i++) 73 eg[i].clear();//初始化 74 while(m--) 75 { 76 cin>>a>>b>>d; 77 eg[a].push_back(node(b,d)); 78 eg[b].push_back(node(a,d)); 79 } 80 Dijkstra(1); 81 for(int i = 1;i<=n;i++) 82 cout<<dis[i]<<" "; 83 } 84 85 return 0; 86 } 87 /* 88 6 6 89 1 2 2 90 3 2 4 91 1 4 5 92 2 5 2 93 3 6 3 94 5 6 3 95 */
1 /* 2 使用pair代替结构 3 */ 4 5 #include <iostream> 6 #include <cstdio> 7 #include <queue> 8 #include <vector> 9 using namespace std; 10 const int Ni = 10000; 11 const int INF = 1<<27; 12 13 typedef pair<int,int> pa; 14 15 int dis[Ni],n;//dis使用1-n的部分 16 17 vector<pair<int,int> > eg[Ni]; 18 19 void Dijkstra(int s) 20 { 21 int i,j; 22 for(i=0;i<=n;i++)//要到n 23 dis[i] = INF; 24 priority_queue< pa,vector<pa>,greater<pa> >q; //优先级队列:小顶堆 25 dis[s] = 0; 26 q.push(make_pair(s,dis[s])); 27 28 while(!q.empty()) 29 { 30 pa x = q.top(); 31 q.pop(); 32 int w = x.first; 33 for(j = 0;j<eg[w].size();j++)//遍历x的所有邻接点 34 { 35 pa y = eg[w][j];//y是x的邻接点 36 int u = y.first; 37 if(dis[u]>x.second+y.second) 38 { 39 dis[u] = x.second+y.second; 40 q.push(make_pair(u,dis[u])); 41 } 42 } 43 } 44 45 } 46 47 48 int main() 49 { 50 int m,a,b,d;//关系个数 51 while(cin>>n>>m,m+n) 52 { 53 for(int i = 0;i<=n;i++) 54 eg[i].clear();//初始化 55 while(m--) 56 { 57 cin>>a>>b>>d; 58 eg[a].push_back(make_pair(b,d)); 59 eg[b].push_back(make_pair(a,d)); 60 } 61 Dijkstra(1); 62 for(int i = 1;i<=n;i++) 63 cout<<dis[i]<<" "; 64 } 65 66 return 0; 67 } 68 /* 69 6 6 70 1 2 2 71 3 2 4 72 1 4 5 73 2 5 2 74 3 6 3 75 5 6 3 76 */
优化策略:将要扫描的结点按其对应弧的权值进行顺序排列,每循环一次即可得到符合条件的结点,大大提高了算法的执行效率
A*算法优化策略
问题:Dijkstra算法基于广度优先搜索策略,即从源点出发,通过权值迭代遍历所有其他结点后,最后得到从源点到其他各结点的最短路径。整个搜索好似一个圆形向外展开,直到到达目的地,这样的搜索方式是盲目的。很明显这样求解一定能找到最优解,但节点展开的数量和距离成级数增加,会导致大量无效点的搜索,大大的降低搜索的效率。
优化策略:采用改进的Dijkstra算法——A*算法。A*算法是人工智能运用在游戏中的一个重要实践,它主要是解决路径搜索问题。A*算法实际是一种启发式搜索。发表论文。所谓启发式搜索,就是利用一个估价函数judge()评估每次决策的价值,决定先尝试哪一种方案。这样可以极大地优化普通的广度优先搜索。从Dijkstra算法到A*算法是判断准则的引入,如果这个判断条件不成立,同样地,只能采用Dijkstra算法。所以A*算法中的估价函数是至关重要[3]。
扇形优化策略
问题:Dijkstra算法的搜索过程好似以源点为圆心的一系列同心圆向外展开,搜索过程中没有考虑到终点所在方向或位置,搜索是盲目的。这样导致大量的无用临时结点被反复搜索,成为实际应用中的瓶颈。
优化策略:从尽量减少最短路径分析过程中搜索的临时结点数量,限制范围搜索和限定方向搜索考虑进行优化。那么这种有损算法是否可行呢?我们知道,现实生活中行进,不会向着目的地的相反方向行进,否则就是南辕北辙。所以,当所研究的网络可以抽象化为平面网络的条件下,也不必搜索全部结点,可以在以源点到终点所在直线为轴线的扇形区域内搜索最短路径。这样,搜索方向明显地趋向终点,提高了搜索速度,虽然抛弃了部分结点,但基本上不影响搜索的成功率[5]。
邻接点优化策略
问题:Dijkstra算法在提取最短路径结点时需要访问所有的未确定最短路径的结点,算法的时间复杂度为O(n2),如果只希望找到从源点到某一特定的终点的最短路径也不例外。结点数n越大,算法的计算效率和存储效率越低。
优化策略:只对最短路径上结点的邻接点作处理,不涉及其他结点。即(1)只从源点的邻接点集合中选择权值最小的结点作为转接点,将此转接点加入已确定最短路径的结点集合S中;(2)对此转接点的邻接点集合与S的差集中的结点的权值进行更新;(3)从S中所有结点的邻接点集合的并集与S的差集中选择权值最小的结点作为下一个转接点,并将此转接点加入S中。重复(2),(3)操作,直至所有的结点确定最短路径。优化算法在更新最短路径值与选择最短路径值最小的结点时,仅仅涉及相关结点的邻接点集合及S集合中所有结点的邻接点集合与S集合的差集,时间复杂度取决于转接点的邻接点的数量多少,减少了计算次数与比较次数
参考文献:
[1] 严蔚敏,吴伟民. 数据结构(C语言版)[M]. 北京:清华大学出版社,1997,186~190.
[2] 谢柏青,佘晓歌. 算法与数据结构[M]. 北京:高等教育出版社,2001,230~232.
[3] 陈益富,卢 潇,丁豪杰. 对Dijkstra算法的优化策略研究[J]. 计算机技术与发展,2006,16(9):73~75.
[4] 章永龙. Dijkstra最短路径算法优化[J]. 南昌工程学院学报,2006,25(3):30~33.
[5] 胡树玮,张修如,赵 洋.扇形优化Dijkstra算法[J]. 计算机技术与发展,2006,16(12):49~51.