#include<iostream>#include<algorithm>#include<cstring>usingnamespace std;int g[505][505];//稠密图使用邻接矩阵存图,方便初始化为无穷的操作int mark[505];//更新过的点即走过的点集合int dis[505];//1点到各个点的最小距离int n , m , a , b , c;intdij(){//先将1到所有点的距离全部初始化为无穷大;memset(dis ,0x3f,sizeof dis);//将初始点距离设置为0
dis[1]=0;//开始更新n次,知道更新到n这个点for(int i =1;i <= n ; i++){//用来找出剩余所有未走过的点中dis距离最小的点int t =-1;//扫描1-n所有点找出此时未走过的点dis最小值for(int j =1; j <= n ; j++){//如果这个点没走过,并且是所有没走过的点中dis最小的,t一直在帮助记录最小值的点,出现第j个点比上一个统计到了最小值点t//还要小,那么就更新tif(!mark[j]&&(t ==-1|| dis[t]> dis[j])){
t = j;}}//找到最小点之后,要将最小点标记为走过
mark[t]=1;//然后用这个点将他能到达的所有点进行松弛,也就更新这个点到各个点的dis,因为从这个点出发到达到达其他点,可能会因为路径不同//而产生dis的变得更小for(int j =1; j <= n ; j++)//函数调用时先将dis全部设置为无穷大就是因为很多无法从t到达的点,会因为初始值为无穷大,所以加上t的dis,还是无穷大//不会对我们更新距离产生影响
dis[j]=min(dis[j], dis[t]+ g[t][j]);//用原来从其他点到j点的最短距离和从t点到j点的最短距离做最小值比较,更新dis[j]}//这里为啥不是等于而是大于。因为如果到达不了n点//那么n点会因为每次的当j=n时的dis[j] = min(dis[j] , dis[t] + g[t][j]);不停的增大if(dis[n]>=0x3f3f3f3f)return-1;return dis[n];}intmain(){
cin >> n >> m;memset(g ,0x3f,sizeof g);//将边数组初始化for(int i =1; i <= m ; i++){//存图scanf("%d %d %d",&a ,&b ,&c);if(g[a][b]> c) g[a][b]= c;}
cout <<dij();return0;}
堆优化版Dijstra
时间复杂度:O(mlogn)
实现方法和朴素Dij做法相同,只不过利用堆的特性来优化查找最小值的点的过程
代码实现
#include<iostream>#include<cstring>#include<algorithm>#include<queue>usingnamespace std;constint maxn =2e5+10;//first为1点到第second点最短距离 , second内装的是第几个点typedef pair<int,int> PII;//使用优先队列来构造大根堆,将每次查找最小值的过程优化为logn
priority_queue<PII , vector<PII>, greater<PII>> q;//已确定最短距离的集合int mark[maxn];//1-n所有点的距离int dis[maxn];//链式前向星存边以及边权int h[maxn], e[maxn], w[maxn], ne[maxn], idx;int n , m;//链式前向星加边voidadd(int a ,int b ,int c){
e[idx]= b , w[idx]= c , ne[idx]= h[a], h[a]= idx++;}//dij实现函数intddij(){//将1到1~n所有点的距离初始化为无穷大,待更新memset(dis ,0x3f,sizeof dis);//从1开始将1到1本身的距离赋值为0
dis[1]=0;//将第一个初始点进队,开始更新
q.push({0,1});//开始更新//如果队列没有为空,证明还有能到达的点未被更新while(!q.empty()){//将此时所有能到达的待更新的点中最小的dis拿出来
PII t = q.top();
q.pop();int v = t.second;//v存储从哪个点开始//判断过滤重边的问题if(mark[v])continue;//更新完出队,即进入集合
mark[v]=1;//开始用此时最小点v更新v所能到达的点的最小距离for(int i = h[v]; i !=-1; i = ne[i]){int k = e[i];//存储v的出边中第i条边到达哪个点if(dis[k]> dis[v]+ w[i]){//如果从v这个点出发到达k点的距离小于之前的更新的k的最小距离
dis[k]= dis[v]+ w[i];//那么就更新起始点1到k的最小距离dis[k]
q.push({dis[k], k});//将k这个点以及他此时的最小距离入队}}//将所有v能到达的点的距离全部更新结束之后,再去队头找到此时dis最小的点,再重复}//直到全部完成出队即完成全部点的更新if(dis[n]==0x3f3f3f3f)return-1;return dis[n];}intmain(){
cin >> n >> m;//初始化链式前向星全部头结点数组为-1,表示目前从此结点还没有任何一条出边memset(h ,-1,sizeof h);for(int i =1; i <= m ; i++){int a , b , c;scanf("%d %d %d",&a ,&b ,&c);add(a , b , c);}
cout <<ddij();return0;}
存在负权边
Bellman Ford
时间复杂度:O(nm)
实现方法:
从起点开始,逐层更新
每层即为起点到n的边数增1
一定要注意负权回路的问题,如果不规定遍历层数即边数,那么会进入无线回环,不存在最短路径
bellmanford算法可以用来判断是否有环,即n个点n条边一定是因为一个点充当两个点导致的
代码实现
#include<iostream>#include<cstring>#include<algorithm>usingnamespace std;constint maxn =1e5+10;//用结构体存图structnode{int u , v , w;}arr[maxn];//定义两个数组,dis存储距离,backdis是用来更新所有边时记录上一层的全部状态防止发生串联更新的int dis[505], backdis[505];int n , m , k;//函数实现boolbellman(){//将全部dis初始化为无穷大,待更新memset(dis ,0x3f,sizeof dis);//将起始点距离初始为0
dis[1]=0;//遍历k层,即限制k条边到达n点for(int i =1; i <= k ; i ++){//将本轮用来更新所有边的这一层状态记录下来memcpy(backdis , dis ,sizeof dis);//开始更新所有边for(int j =1; j <= m ;j++){//记录更新的第j条边的起点,终点,边权int u = arr[j].u , v = arr[j].v , w = arr[j].w;//做一个松弛操作
dis[v]=min(dis[v], backdis[u]+ w);}}//因为随着每轮的松弛,因为存在负权边,所以随着轮数增多,如果出现负权边,那么会导致dis[n]的值,会被降低,但是因为//题目数据的大小(边数为10000),所以不会大的变化if(dis[n]>=0x3f3f3f3f/2)returntrue;returnfalse;}intmain(){
cin >> n >> m >> k;for(int i =1; i <= m ; i++){int a , b , c;scanf("%d %d %d",&a ,&b ,&c);
arr[i]={a , b , c};}if(bellman()) cout <<"impossible";else cout << dis[n];return0;}