Dijkstra 算法
-方法、算法、代码和正确性的证明
摘要
本文介绍Dijkstra算法,包括方法、算法、正确性的证明和算法C++的实现。
§1 Dijkstra算法方法介绍
算法适用范围:Dijkstra算法解决的是带权重的问题的有向图上单源最短路径问题,该算法要求所有边的权重都为非负值。
Dijkstra算法(方法):
问题描述:在无向图 G=(V,E) 中,假设点v_i与点v_j的之间的权值w[i][j],找到由顶点 v_0 到其余各点的最短路径。(单源最短路径)
1.选择源点v_0,令V_G={ v_0 },令dis[i] 表示v_i到与源点v_0的路径长度,初始化各点,如果v_i与源点v_0没有直接的路径,则初始化为∞(dis[i] = dis[0][i]);
2.选择与源点最近的点,即未加入V_G的结点中dis[i] 值最小的点v_i,将该点加入V_G中,同时更新与v_i相邻且未加入V_G的点到源点 (dis[j]=min{dis[j],dis[i]+w[i][j]})
3.重复2至所有结点都加入V_G。
§2 Dijkstra算法实现
算法 : Dijkstra算法伪代码
输入:G是一个带权连通图的矩阵表示
输出:最小路径的权值以及对应路径
function Dijkstra(Graph, source):
create vertex set Q
for each vertex v in Graph: //初始化
dis[v] ← INFINITY //初始化各个点到源点的距离 pre[v] ← UNDEFINED //每个点的前面一个点(得到路径)
// All nodes initially in Q (unvisited nodes)
add v to Q
dis[source] ← 0 // Distance from source to source
while Q is not empty:
// Node with the least distance will be selected first
u ← vertex in Q with min dis[u]
remove u from Q
for each neighbor v of u: // where v is still in Q.
alt ← dis[u] + length(u, v)
// A shorter path to v has been found
if alt < dis[v]:
dis[v] ← alt
pre[v] ← u
return dis[], pre[]
§3 Dijkstra算法C++实现
//定义一些全局变量
//本代码默认0是源点,最后一个点是终点
#define maxPath 0x7FFF
#define maxNum 1000
int node_num, line; //// 图的结点数和路径数
int pre[maxNum] = {}; // 记录当前点的前一个结点
int weight[maxNum][maxNum]; // 记录图的两点间路径长度或权重
int path[maxNum] = {}; // 表示当前点到源点的最短路径长度
//Dijkstra算法实现:
void Dijkstra(){
path[0] = 0; //source
bool vertex[node_num] = {};
vertex[0] = true;
for(int i = 0;i < node_num;i++)
if(weight[0][i] != 0) path[i] = weight[0][i];
else path[i] = maxPath;
int minPath = maxPath , minPath_index = 0 , preIndex = 0;
for(int j = 0;j < node_num;j++){
minPath = maxPath;
preIndex = minPath_index; //记录上一个最小路径的点坐标
for(int i = 0;i < node_num;i++){
if(i == preIndex) continue;
else if(!vertex[i] && weight[preIndex][i] != 0 && \
path[i] > path[preIndex] + weight[preIndex][i]){
path[i] = path[preIndex] + weight[preIndex][i];
pre[i] = preIndex;
if(path[i] < minPath)
minPath_index = i,minPath = path[i];
}
}
for(int k = 1;k < node_num;k++)
if(path[k] < minPath && !vertex[k]){
minPath_index = k ;
minPath = path[k] ;
preIndex = pre[k];
}
vertex[minPath_index] = true;
}
}
//get the path
void findSmallestPath(int indexOfNode){
if(indexOfNode != 0)
findSmallestPath(pre[indexOfNode]);
if(indexOfNode != node_num-1) cout << indexOfNode << " -> ";
else cout << indexOfNode << endl;
}
//参考主函数
//0为source,node_num为sink
int main(){
cout <<"Please enter the num of the vertex : " << endl;
cin >> node_num ;
cout << "Please enter the num of the line : " << endl;
cin >> line;
cout << "Please enter the line in order : " << endl;
for(int i = 0;i < line;i++){
int first_node , second_node , w;
cin >> first_node >> second_node;
cin >> w;
weight[first_node][second_node] = weight[second_node][first_node] = w;
}
Dijkstra();
cout << "The smallest weight : " << path[node_num-1] << endl;
cout << "Show The Path : ";
findSmallestPath(node_num-1);
return 0;
}
§4 Dijkstra算法正确性证明
Dijkstra算法总是选择“最近”或“最轻”的结点加入到集合V,该算法使用的是贪心的策略。证明是通过对已经访问的结点数学归纳法证明的。(注:访问的点表示加入了集合的点)
不变前提:对于每一个已经访问过的点v,dis[v]代表从源到该点的最短距离;对于还未访问的结点u,dis[u]表示源点通过当前已访问的结点到该点的最短距离(当且仅当存在那么一条路径,否则为无穷大,我们不假定当前的最短路径dis[u]是未访问结点u的最短路径)
1.当只有两个结点时候,一个源点,一个汇点,结论显然成立。
2.假设n-1个结点的时候。现在我们选择一条边vu,其中结点u是未访问结点中具有最小的dis[u]的结点,并且dis[u] = dis[v] + length[v,u]。dis[u]必定是源点到所有未访问中最短路径长度因为假如如果存在一条更短的路径,假设w是该路径上第一个未访问的结点,这与假设dis[w] > dis[u]矛盾,所以dis[u]必定是源点到所有未访问结点中最短路径长度。类似的,如果在没有使用未访问结点情况下存在一条到结点u更短的路径,并且如果该路径上的最后一个节点是w,那么我们将具有dis [u] = dis [w] + length [w,u],这与前提矛盾。在处理完结点u后,对于其余未访问的结点进行相同的处理,都保证了源点到该点的路径最短。故Dijkstra算法成立。
参考文献: