其实这个算法写了很多次了,但是总是理解不深。这次专门拿出时间好好的分析一下。
【基本原理】
称为三角算法。以下面的简单图示表示。假设v1是我们的源点,它的最短路径长度已固定(为0)。那么首先找到的最短路径是v1-v2。找到v2之后,我们最短路径长度已固定的节点的集合就变成了{v1,v2}。那么需要更新与v2邻接的所有节点的最短路径长度的值,由于1+2<5,所以v3的最短路径长度就更新为3而不是保持为5。当然,如果v2到v3的距离大于4,v3的最短路径长度是不需要更新的。
Dijkstra的最短路径算法往往有几种存储方式,例如:
(1)邻接矩阵。形如a[i][j]来存储节点i与节点j之间的路径。
(2)邻接表。单是这一种存储方式就有两种变化:a)使用STL的vector,以节点为存储单位。每一个节点拥有一个node对象的vector,然后每一个node对象中存储下一个节点及到此节点的边长。虽然这种方法看起来很像上面的邻接矩阵表示法,但是由于vector的初始化比较灵活,不需要N^2的空间,因此实质上还是符合邻接表的初衷:在稀疏图中节省空间。b)以边为单位存储。每个边的对象包括起始节点、目的节点以及此边边长。另外还有将边存储为链表的,每个边的对象包括指向下条边的指针。
不光存储方式有变化,具体实现也有很多tricks。比如:在所有的边中选取距离当前集合中的节点最近的一条边时,可以采用堆来减少时间复杂度,无需遍历整个剩余边的集合。这样降低了整个程序的复杂度。
甚至输出方式也不止一种,有的要求输出源点到其他节点的最短路径,有的要求更高:要保存路径上的每一个节点。
【代码】
这里仅仅给出一个最原始的版本:使用邻接表存储,不使用堆,仅输出源点到其他节点的最短路径长度的值,而不输出整个路径。
const int INF = 0x7FFFFFFF;
typedef struct node{
int to;//以当前node为起点的边的终点
int length;//此边的边长
node(int n = -1, int l = INF ) { to = n ; length = l ; }
};
typedef vector<node> vnode;//每一个顶点的出边数组
//寻找最短路径尚未固定,且当前路径最小的那个节点。
static int nearestNotfixed( vector<int> &shortest, vector<bool> &fixed, int num ){
int nearestVertex = -1;
int nearestDist = INF;
for( int i = 0 ; i < num ; ++i )
if( ! fixed[i] )
//找到最短路径最小的那个节点
if( shortest[i] < nearestDist )
{
nearestDist = shortest[i];
nearestVertex = i;
}
return nearestVertex;
}
/*每当寻找到新的最短路径固定的节点之后,更新与它邻接的所有节点的最短路径。
@adjacent: 与刚找到的节点邻接的所有节点
@nearest : 最短路径已固定的节点到刚找到的节点之间的路径长度
*/
static void refreshDist(vector<node> &adjacent, vector<int> &shortest, vector<bool> &fixed, int nearestDist ){
vector<node>::iterator iter = adjacent.begin();
for( ; iter != adjacent.end() ; ++iter)
if( ! fixed[iter->to] )
if( iter->length + nearestDist < shortest[iter->to] )
shortest[iter->to] = iter->length + nearestDist;
}
//main function
void dijkstra( vector<vnode> &graph, vector<int> &shortest, vector<bool> &fixed, int num, int source ){
shortest[source] = 0;
for( ; ; ){
//寻找一个最短路径尚未固定的节点
int nearest = nearestNotfixed( shortest, fixed, num);
//所有节点的最短路径都已经固定,因此找不到可以更新的节点,程序退出。
if( nearest == -1 )
break;
fixed[nearest] = true;
//更新邻接于刚找到的节点的所有节点的最短路径
refreshDist( graph[nearest], shortest, fixed, shortest[nearest] );
}
}
int main(){
/////////////////initialization//////////////////////
int N;//total number of vertices
int source;//the source vertex
/*
输入文件格式:第一行为总顶点数 第二行为源点序号
以下每行为一条边:起点 终点 边长度
*/
ifstream infile("dijkstra.txt");
infile>>N;
infile>>source;
//graph[i]是第i个节点的邻接节点的数组。
vector<vnode> graph(N);
//距离源点的最短路径。
vector<int> shortest(N,INF);
//最短路径已固定(不会再被更新)的节点的集合,固定后标记为true。
vector<bool> fixed(N,false);
//input loop
//按照数据格式输入每条线段的起点、终点和长度
int from,to,length;
while( !infile.eof() ){
infile>>from>>to>>length;
graph[from].push_back( node(to,length) );
}
/////////////////////end of initialization//////////////////
dijkstra( graph, shortest, fixed, N, source );
//打印最短路径数组。其中源点对应的项肯定为0。
printVector(shortest);
return 0;
}
【输入数据】
输入的dijkstra.txt文件如下(这个例子实际上是《数据结构与算法分析C++描述》中文第三版中p.258的例子),但因为例子中的节点都是从1开始,输入的时候需要改为从0开始:
0
0 1 2
0 3 1
1 3 3
1 4 10
2 0 4
2 5 5
3 2 2
3 4 2
3 5 8
3 6 4
4 6 6
6 5 1
【示意图】
这个例子的图示如下: