贪心法——单源最短路径问题——dijkstra算法
单源最短路径
给定带权有向图G =(V,E),其中每条边的权是非负实数。另外,还给定V中的一个顶点,称为源。现在要计算从源到所有其它各顶点的最短路长度。这里路的长度是指路上各边权之和。这个问题通常称为单源最短路径问题。
dijkstra算法
思想: 初始顶点集合S为源点,每次扩充一个距离源点的特殊路径最短的点到该顶点集合S中,所谓特殊路径是指路径中的所有中间点都在集合S中的路径,S中的所有点都是已经找到最短路径的点,当所有点都加入S后,算法完成。
为什么最短的那个特殊路径就是对应的点的最短路径呢?可以用归纳法和反证法证明。
采用一个数组dis[]来记录所有点的最短特殊路径,一个数组vis[]来记录某个点是否被选择,每次选取未选择的点中特殊路径最短的那个点加入已选择集合,并更新dis[]:其它每个未选择的点到这个新加入的点的直接距离加上这个新加入点的最短特殊路径长度是否小于自身原来的最短特殊路径长度。
为了记录最短的路径是什么,一般而言是要根据每个终点记录其自身的路径,但是在这里可以只记录每个终点的前一个节点,然后倒推到源点,因为去掉最短路径的最后一个边所得到的的一定还是最短路径,因此需要一个前驱节点记录数组pre[]。
dijkstra算法只能处理权值非负的情况。
代码:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 105
#define INF 0x3f3f3f
int m[MAXN][MAXN],vis[MAXN],dis[MAXN],pre[MAXN];
// dijkstra算法计算单源最短路径
void dijkstra(int s,int n){
// 初始化
memset(vis, 0, sizeof(vis));
for(int i = 0;i < n;i++)
dis[i] = m[s][i];
memset(pre, s, sizeof(pre));//初始化为源点,正常情况下会全部被覆盖
// 选取源点
vis[s] = 1;
dis[s] = 0;
pre[s] = s;
// 选取最小dis
for(int i = 1;i < n;i++){
int min = INF,p = -1;
for(int j = 0;j < n;j++){
if(vis[j]) continue;
if(min > dis[j]){
min = dis[j];
p = j;
}
}
// 假如找不到最小的dis
if(p == -1){
cout<<"error!"<<endl;
return ;
}
// 将选取的点加入已选点集合中,并更新dis
vis[p] = 1;
for(int j = 0;j < n;j++){
if(vis[j]) continue;
// 假如更新了,那么pre就是p了,否则pre就是上一次的值
// prime记录结果也是在更新处
if(m[p][j]+dis[p] < dis[j]){
dis[j] = m[p][j]+dis[p];
pre[j] = p;
}
}
}
}
int main(){
int n = 5,s = 0;
memset(m, INF, sizeof(m));
for(int i = 0;i < n;i++)
m[i][i] = 0;
m[0][1] = 10;
m[0][3] = 30;
m[0][4] = 100;
m[1][2] = 50;
m[2][4] = 10;
m[3][2] = 20;
m[3][4] = 60;
dijkstra(s, n);
for(int i = 0;i < n;i++)
cout<<i+1<<" "<<dis[i]<<endl;
for(int i = 0;i < n;i++){
cout<<i+1<<"->";
int end = i;
while(pre[end] != s){
cout<<pre[end]+1<<"->";
end = pre[end];
}
cout<<s+1<<endl;
}
}
复杂度:
时间复杂度:O(n2)
空间复杂度:O(n2)