【算法】dijkstra--最短路径
1.算法特点
2.算法思路
3.算法实例演示
4.代码实现
参考:
Dijkstra算法详解https://blog.csdn.net/qq_35644234/article/details/60870719
代码实现 https://blog.csdn.net/mimi9919/article/details/51219176
1.算法特点
迪杰斯特拉算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。本质特点:按路径长度递增依次产生最短路径。该算法常用于路由算法或者作为其他图算法的一个子模块。
2.算法的思路
Dijkstra算法采用的是一种贪心的策略,声明一个数组dis来保存源点到各个顶点的最短距离和一个保存已经找到了最短路径的顶点的集合:T,初始时,原点 s 的路径权重被赋为 0 (dis[s] = 0)。若对于顶点 s 存在能直接到达的边(s,m),则把dis[m]设为w(s, m),同时把所有其他(s不能直接到达的)顶点的路径长度设为无穷大。初始时,集合T只有顶点s。
然后,从dis数组选择最小值,则该值就是源点s到该值对应的顶点的最短路径,并且把该该点加入到T中,OK,此时完成一个顶点。
然后,我们需要看看新加入的顶点是否可以到达其他顶点并且看看通过该顶点到达其他点的路径长度是否比源点直接到达短,如果是,那么就替换这些顶点在dis中的值。
然后,又从dis中找出最小值,重复上述动作,直到T中包含了图的所有顶点。
3.Dijkstra算法示例演示
下面求下图,从顶点v1到其他各个顶点的最短路径。
首先第一步,我们先声明一个dis数组,该数组初始化的值为:
我们的顶点集T的初始化为:T={v1}
既然是求 v1顶点到其余各个顶点的最短路程,那就先找一个离 1 号顶点最近的顶点。通过数组dis 可知当前离v1顶点最近是 v3顶点。当选择了 2 号顶点后,dis[2](下标从0开始)的值就已经从“估计值”变为了“确定值”,即 v1顶点到 v3顶点的最短路程就是当前 dis[2]值。将V3加入到T中。
为什么呢?因为目前离 v1顶点最近的是 v3顶点,并且这个图所有的边都是正数,那么肯定不可能通过第三个顶点中转,使得 v1顶点到 v3顶点的路程进一步缩短了。因为 v1顶点到其它顶点的路程肯定没有 v1到 v3顶点短.
OK,既然确定了一个顶点的最短路径,下面我们就要根据这个新入的顶点V3会有出度,发现以v3 为弧尾的有: < v3,v4 >,那么我们看看路径:v1–v3–v4的长度是否比v1–v4短,其实这个已经是很明显的了,因为dis[3]代表的就是v1–v4的长度为无穷大,而v1–v3–v4的长度为:10+50=60,所以更新dis[3]的值,得到如下结果:
因此 dis[3]要更新为 60。这个过程有个专业术语叫做“松弛”。即 v1顶点到 v4顶点的路程即 dis[3],通过 < v3,v4> 这条边松弛成功。这便是 Dijkstra 算法的主要思想:通过“边”来松弛v1顶点到其余各个顶点的路程。
然后,我们又从除dis[2]和dis[0]外的其他值中寻找最小值,发现dis[4]的值最小,通过之前是解释的原理,可以知道v1到v5的最短距离就是dis[4]的值,然后,我们把v5加入到集合T中,然后,考虑v5的出度是否会影响我们的数组dis的值,v5有两条出度:< v5,v4>和 < v5,v6>,然后我们发现:v1–v5–v4的长度为:50,而dis[3]的值为60,所以我们要更新dis[3]的值.另外,v1-v5-v6的长度为:90,而dis[5]为100,所以我们需要更新dis[5]的值。更新后的dis数组如下图:
然后,继续从dis中选择未确定的顶点的值中选择一个最小的值,发现dis[3]的值是最小的,所以把v4加入到集合T中,此时集合T={v1,v3,v5,v4},然后,考虑v4的出度是否会影响我们的数组dis的值,v4有一条出度:< v4,v6>,然后我们发现:v1–v5–v4–v6的长度为:60,而dis[5]的值为90,所以我们要更新dis[5]的值,更新后的dis数组如下图:
然后,我们使用同样原理,分别确定了v6和v2的最短路径,最后dis的数组的值如下:
因此,从图中,我们可以发现v1-v2的值为:∞,代表没有路径从v1到达v2。所以我们得到的最后的结果为:
起点 终点 最短路径 长度
v1 v2 无 ∞
v3 {v1,v3} 10
v4 {v1,v5,v4} 50
v5 {v1,v5} 30
v6 {v1,v5,v4,v6} 60
4.代码实现
// Dijkstra.cpp
// 迪杰斯特拉--最短路径问题
// 从源顶点到达各个顶点,的 最短距离
#include<iostream>
#include<vector>
#include<stdio.h>
#define INT_MAX 0x7fffffff
#define Alone -1;
using namespace std;
/*
function: Dijkstra(int v,int n,vector<int> &dist,vector<int> &pre,vector<vector<int>> &d)
param: int v:开始节点
int n: 节点数
dist : 距离
pre : 父辈节点
d : 图
return: void
注意:1.下标从0还是从1开始? 决定了n/ s(n)/s(n+1)
2.孤立节点怎么处理
*/
void Dijkstra(int v,int n,vector<int> &dist,vector<int> &pre,vector<vector<int> > &d)
{
vector<bool> s(n); // 黑色节点 初始化为 0-白色
for(int i=0;i<n;i++) //初始化 dist,pre
{
dist[i]=d[v][i];
if(dist[i] < INT_MAX)
{
pre[i]=v;
}
else pre[i]=Alone; //未找到父辈
}
dist[v]=0; //开始节点的距离 置0
s[v]=true; //开始节点 染黑
for(int i=1;i<n;i++) // n-1 次循环
{
int best=v;
int min=INT_MAX;
for(int j=1;j<n;j++) //找到最小距离
{
if(!s[j] && dist[j]<min)
{
min=dist[j];
best=j;
}
}
s[best]=true;
for(int k=1;k<n;k++)
{
if(! s[k] && d[best][k] !=INT_MAX) //k节点为白,且best->k有链路(相连)
{
int new_dist=dist[best]+d[best][k];
if(new_dist < dist[k])
{
dist[k]=new_dist;
pre[k]=best;
}
}
}
}
}
void print_path(vector<int> pre,int start,int end)
{
int temp=end;
vector<int> save;
while(temp != start) //逆序找头
{
save.push_back(temp);
temp=pre[temp];
}
save.push_back(start); //完整的路径
cout<<start<<"->" ;
for(int i=save.size() -2;i>0;i--) //逆序输出 注意开头
{
cout<<save[i]<<"->" ;
}
cout<<save[0];
cout<<endl;
save.clear();
}
int main()
{
freopen("2.txt","r",stdin);
int n,m; //n个节点,m条边
cin>>n>>m;
vector<vector<int> > d(n+1,vector<int>(n+1)) ; //图 路径图(n*n)
//vector<vector<int> > 后面两个> > 必须加空格
//[Error] '>>' should be '> >' within a nested template argument list
//初始化 图
for(int i=0;i<n;i++) //下标从0开始,无向图
{
for(int j=0;j<n;j++)
{
d[i][j]=INT_MAX;
}
}
int p,q,value; // p->q
for(int i=0;i<m;i++ )
{
cin>>p>>q>>value;
d[p][q]=value;
//d[q][p]=value; //无向图加此句
}
vector<int> dist(n),pre(n);
Dijkstra(0,n,dist,pre,d);
cout<<"path: " ;
print_path(pre,0,n-1);
cout<<"dist: "<<dist[n-1]<<endl;
return 0;
}
------------------------------------------- END -------------------------------------