单源最短路径
下图
S 表示已经找到最短路径
V-S 表示最短路径没有找到
下图
一开始345顶点属于V-S中,现在要连接,先连接3顶点
连接新顶点时,必须从S中选顶点,连接2到3,1-3最短路径是4
如果是链接1-3那么路径就是5,明显不是最短路径
这样一次链接
(迪杰斯特拉连路径怎么连可以看看王道考研数据结构视频,讲的比看文字强多了)
下图
重复执行的是n-1次,一共有n个点,除去源点1个,就剩n-1个,在剩下n-1个点里面循环找
例子
下图
dist[]存储的是源点到各个顶点的路径,45没有与当前源点1相连所以距离为无穷
p[]存储的是顶点的前驱,这个前驱是源点,如果这个顶点前驱是源点p[i]=源点值,与前驱不是源点就p[i]=-1表示没有前驱
除了源点S,其他都是V-S集合
V-S中找最小,找到就加入到S集合,比如现在V-S中2顶点值最小,那就将顶点2加入S集合中,此时V-S就只剩345顶点,进行下一步
下图
这也是松弛操作,3顶点能不能借1顶点或者借2顶点,发现借1顶点之后距离是5m,借2顶点距离为4,所以借2顶点
现在从顶点2开始链接V-S中的顶点,那就要更新V-S中的数据
dist[]存储的是源点到该点距离,现在源点链接了顶点2,那么距离有了一个2m,V-S中的到源点的距离就更新
顶点 3 4 5
距离源点 4 8 无穷
p[]中V-S的顶点前驱也要更改那么现在dist[]里面的V-S中顶点3的距离距离源点最小为4m,把3顶点加入到S集合中,现在V-S剩余45两个结点
松弛操作
4顶点借3顶点或者2顶点
借3顶点 距离到源点就是4+7 = 11
借2顶点 距离到源点就是2+6 = 8
很明显8 < 11,所以不借3顶点,借用2顶点5顶点借3或4
借3 距离4+1 = 5
借4 距离8+4 = 12V-S剩余的顶点4 5距离源点距离分别为8 5
顺便改前驱
所以把5顶点加入S,V-S剩余4顶点
4顶点借5顶点,但是没有链接,不能借
所以4顶点只能不动,无法更新,所以没有领接点,什么也不用做,剩余的就是在V-S找最小值,现在V-S只有一个顶点4,距离源点为8m,将4顶点加入S
现在所有顶点都加入了S
比如找4顶点到源点1最短路径,那就是路径1——>2——>4,就是4的前驱是2,2的前驱是1,只有这几个前驱时,路径才最短
注意最短路径是那几个顶点不是正向,是逆向的1——>2——>4,这个是输出时候输出正向,但是在代码最后是4 2 1这个顺序,是输出时候逆序输出方便观看
下面是第一个代码的测试数据
第一行是顶点个数(5)和边数(8)
剩余8行是每条边两段顶点和边的权
5 8
1 2 2
1 3 5
2 3 2
2 4 6
3 4 7
3 5 1
4 3 2
4 5 4
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 1005;//顶点(结点)数
const int INF = 0x3f3f;//无穷大,领接矩阵初始值
const int inf = 0x3f3f3f;
int G[N][N];//领接矩阵,保存图
int dist[N]; //dist[i]表示源点到结点i的最短路径
int p[N];//p[i]表示源点到顶点i的最短路径上的i的前驱
int n,m;//n为顶点数,m为边数
bool flag[N];// 如果flag[i]为true,说明顶点i加入S集合,否则i属于V-S集合
int sum=0;//最短路径长度
//初始化领接矩阵
void init(){
cin >>n >> m;
int u,v,w;
for(int i = 1 ; i <= n ; i++){
for(int j = 1 ; j <= n ; j ++){
G[i][j] = INF;
}
}
for(int i = 1 ; i <= m ; i ++){
cin >> u >> v >> w;
G[u][v] = w;
}
for(int i = 1 ; i <= n ; i ++){
for(int j = 1 ; j <= n ; j++){
cout<<G[i][j]<<" ";
}
cout<<endl;
}
}
void djtsl(int u){//u是源点,求的是源点到其他顶点最短路径
//初始化
for(int i = 1 ; i <= n ; i++){
//dist[]表示的是源点到各个顶点的最短路径,注意是从源点!比如源点是1,现在dist存储的就是1到各个顶点的距离
//领接矩阵中直接1顶点到1顶点距离是0
dist[i] = G[u][i];//将源点到各个顶点i的距离读入dist[i]
flag[i] = false;//初始化,还没有一个顶点加入S
if(dist[i] == INF){ //==INF就是不与源点连接,dist[i]值是从领接矩阵获取的
p[i] = -1;//不邻接说明前驱不是源点,那么前驱赋值为-1
} else{
p[i] = u;//连接的话前驱就是u
}
}
dist[u] = 0;//自己到自己是0
flag[u] = true;//u加入S集合
// 找最小 - 松弛 循环n-1次
for(int i = 1 ; i <= n-1 ; i++ ){
int temp = inf;
int t = u;//加入S,这个初始为u是一会拿来判断的
//找最小 在 V-S集合中
for(int j = 1 ;j <= n ; j++){
//由于不知道谁是V-S集合,先判断,在V-S找最小放入S集合
if(!flag[j] && dist[j] < temp){//是V-S集合 并且 dist[j]小于当前就更改
temp = dist[j];
t = j;//记录顶点,一会放入S集合
}
}
if(t == u) return;//如果还是t==u说明上面找最小就没有找到,直接返回就可以
flag[t] = true;//找到就加入S
//松弛操作 判断V-S集合的顶点是否借助t松弛dist[]
for(int j = 1 ; j <= n ; j++){
//如果借助t顶点比之前距离段就松弛更新
//dist[t]+G[t][j]这是源点到t再+t到j距离
if(!flag[j] && dist[t]+G[t][j] <dist[j] ){
dist[j] = dist[t]+G[t][j];
p[j] = t;//因为借助t,所以前驱更改
}
}
}
}
//递归输出源点到终点的路径
void fin(int x){ //这个x是终点不是源点
if(x == -1) return ;
fin(p[x]);
cout<<x<<" ";
}
int main(){
//函数的里面的参数自己可以再设置变量传入,本人为了省事就直接写参数
init();
cout<<"-------------------"<<endl;
djtsl(1);
cout<<"--------------------"<<endl;
//这是输入终点,假设终点为顶点5
fin(5);
cout<<"--------------------"<<endl;
//输出源点到终点顶点5的最短路径
cout<<dist[5];
}
相对简便的
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 510;
int g[N][N], dist[N];
int n, m;
bool st[N];
int dijkstra(){
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
for (int i = 0; i < n; i ++){
int t = -1;
for (int j = 1; j <= n; j ++)
if (!st[j] && (t == -1 || dist[t] > dist[j]))
t = j;
for (int j = 1; j <= n; j ++)
dist[j] = min(dist[j], dist[t] + g[t][j]);
st[t] = true;
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main(){
memset(g, 0x3f, sizeof g);
cin >> n >> m;
while (m --){
int a, b, c;
cin >> a >> b >> c;
g[a][b] = min(g[a][b], c);
}
cout << dijkstra() << endl;
return 0;
}
第二个代码的测试数据
输入
3 3
1 2 2
2 3 1
1 3 4
输出最短路径 3