图论知识之最短路算法——Dijkstra的朴素算法以及堆优化算法
作者:互联网
最短路算法是图论算法中的一个十分经典的问题,它是求在一个图中,若每条边都有一个数值(权值,可以是长度、成本、时间……),则找出两节点之间(或者多个点到一个点)经过边权值之和最少的一条路径。
最短路算法的分类如下图所示:
其中,Bellman-Ford算法和SPFA算法和Dijkstra算法的区别是它们可以求带负权或者负环的图。
今天我们就先来讲讲其中Dijkstra算法的朴素写法以及它的堆优化写法。
Dijkstra算法的中心思想是进行n(点数)次迭代去确定每个点到起点的最小值,最后输出的终点即为我们要找的最短路的距离。所以按照这个思路除了存储图外我们还要存储每个点到起点的最短距离以及在更新最短距离时,判断当前的点的最短距离是否确定,是否需要更新。
每次迭代的过程中我们都先找到当前未确定的最短距离的点中距离最短的点。
int t=-1; //将t设置为-1 因为Dijkstra算法适用于不存在负权边的图 for(int j=1;j<=n;j++) { if(!st[j]&&(t==-1||dist[t]>dist[j]) //该步骤即寻找还未确定最短路的点中路径最短的点 {
t=j;
} }
通过上述操作当前我们的t代表的就是剩余未确定的最短路的点中路径最短的点,与此同时该点的最短路径也已经确定,所以我们将该点标记。
st[t]=true;
然后用这个去更新其余未确定点的最短距离。
for(int j=1;j<=n;j++) { dist[j]=min(dist[j],dist[t]+g[t][j]); }
进行n次迭代后就可以确定每个点的最短距离,然后再根据题意输出相应的要求的最短距离即可。
完整代码:
#include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N=510; int g[N][N]; //为稠密阵所以用邻接矩阵存储 int dist[N]; //用于记录每一个点距离第一个点的距离 bool st[N]; //用于记录该点的最短距离是否已经确定 int n,m; int Dijkstra() { memset(dist, 0x3f,sizeof dist); //初始化距离 0x3f代表无限大 dist[1]=0; //第一个点到自身的距离为0 for(int i=0;i<n;i++) //有n个点所以要进行n次 迭代 { int t=-1; //t存储当前访问的点 for(int j=1;j<=n;j++) //这里的j代表的是从1号点开始 if(!st[j]&&(t==-1||dist[t]>dist[j])) t=j; st[t]=true; for(int j=1;j<=n;j++) //依次更新每个点所到相邻的点路径值 dist[j]=min(dist[j],dist[t]+g[t][j]); } if(dist[n]==0x3f3f3f3f) return -1; //如果第n个点路径为无穷大即不存在最低路径 return dist[n]; } int main() { cin>>n>>m; memset(g,0x3f,sizeof g); //初始化图 因为是求最短路径 //所以每个点初始为无限大 while(m--) { int x,y,z; cin>>x>>y>>z; g[x][y]=min(g[x][y],z); //如果发生重边的情况则保留最短的一条边 } cout<<Dijkstra()<<endl; return 0; }
接着,我们再来看一下堆优化版的Dijkstra算法。
堆优化版的Dijkstra是对朴素版Dijkstra进行了优化,在朴素版Dijkstra中时间复杂度最高的寻找距离
它的中心思想就是将最短的点O(n^2)使用小根堆优化。
1. 一号点的距离初始化为零,其他点初始化成无穷大。
2. 将一号点放入堆中。
3. 不断循环,直到堆空。每一次循环中执行的操作为:
(1)弹出堆顶(与朴素版diijkstra找到S外距离最短的点相同,并标记该点的最短路径已经确定)。
(2)用该点更新临界点的距离,若更新成功就加入到堆中。
堆优化Dijkstra完整代码:
#include<iostream> #include<cstring> #include<queue> using namespace std; typedef pair<int, int> PII; const int N = 100010; // 把N改为150010就能ac // 稀疏图用邻接表来存 int h[N], e[N], ne[N], idx; int w[N]; // 用来存权重 int dist[N]; bool st[N]; // 如果为true说明这个点的最短路径已经确定 int n, m; void add(int x, int y, int c) { // 有重边也不要紧,假设1->2有权重为2和3的边,再遍历到点1的时候2号点的距离会更新两次放入堆中 // 这样堆中会有很多冗余的点,但是在弹出的时候还是会弹出最小值2+x(x为之前确定的最短路径), // 并标记st为true,所以下一次弹出3+x会continue不会向下执行。 w[idx] = c; e[idx] = y; ne[idx] = h[x]; h[x] = idx++; } int dijkstra() { memset(dist, 0x3f, sizeof(dist)); dist[1] = 0; priority_queue<PII, vector<PII>, greater<PII>> heap; // 定义一个小根堆 // 这里heap中为什么要存pair呢,首先小根堆是根据距离来排的,所以有一个变量要是距离, // 其次在从堆中拿出来的时候要知道知道这个点是哪个点,不然怎么更新邻接点呢?所以第二个变量要存点。 heap.push({ 0, 1 }); // 这个顺序不能倒,pair排序时是先根据first,再根据second,这里显然要根据距离排序 while(heap.size()) { PII k = heap.top(); // 取不在集合S中距离最短的点 heap.pop(); int ver = k.second, distance = k.first; if(st[ver]) continue; st[ver] = true; for(int i = h[ver]; i != -1; i = ne[i]) { int j = e[i]; // i只是个下标,e中在存的是i这个下标对应的点。 if(dist[j] > distance + w[i]) { dist[j] = distance + w[i]; heap.push({ dist[j], j }); } } } if(dist[n] == 0x3f3f3f3f) return -1; else return dist[n]; } int main() { memset(h, -1, sizeof(h)); scanf("%d%d", &n, &m); while (m--) { int x, y, c; scanf("%d%d%d", &x, &y, &c); add(x, y, c); } cout << dijkstra() << endl; return 0; }
下篇预告:可以求带负权(负环)的单源最短路算法——SPFA算法和Bellman-Ford算法。
标签:图论,dist,int,Dijkstra,st,算法,heap 来源: https://www.cnblogs.com/YZYc/p/Dijkstra-Class-YPPAH.html