简介
最短路径:从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径
算法:迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题(主要用于解决带权图中的单源最短路径)
迪杰斯特拉算法(Dijkstra)运用贪心思想,从起点开始,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到遍历到终点;
算法思想
由于算法运用贪心的思想,首先声明一个数组dis与一个数组visited分别用来保已经找到了最短路径的顶点和该点是否被访问过;
存储上采用邻接矩阵存图;
定义一个**二维数组map[MAX][MAX]**来表示一个图,数组的值是边权。
开始时,将源点x到各个点的距离存入dis数组中;即将map[x][j](j从1~n)存进dis(无法直接到达的点标为无穷大),并将visited数组中除x外所有值标为0,代表未访问;
随后,找到dis中最小权值的边的点min,此时min点已经确定,使visited[min]=1;
之后以min点为媒介,对未确定的点进行更新;
注意:对于已确定的点不参与最小值的查找和更新;
循环操作,直到所求的点被确定;
图解:
算法详解
直接上代码(所求为源点到其余各个点的最短距离):
int map[110][110],dis[110],visited[110];//map存图,dis源点到各个点的距离,visited为标记数组
void Dijkstra(int n,int x)//n表示图中点的个数,x为源点
{
int i,p,j,min;
for (i=1;i<=n;i++)
{
dis[i]=map[1][i];//将最初源点所连接的点存入dis数组
visited[i]=0;//所有点计为0,即都未确定
}
visited[x]=1;//确定源点到自己的距离
for (i=1;i<=n;i++)
{
min=INF;
for (j=1;j<=n;j++)//找到dis中最小的未被确定的值
{
if(!visited[j] && dis[j]<min)
{
p=j;
min=dis[j];
}
}
visited[p]=1;//确定该最小值
for (j=1;j<=n;j++)//以刚才被确定的点为媒介,更新源点到其他未被确定的点的距离
{
if(!visited[j] && dis[p]+map[p][j]<dis[j])
{
dis[j]=dis[p]+map[p][j];
}
}
}
}
例题
之后老规矩(例题):
luoguP4779(单源最短路径(标准版))
观察上面数据会发现,n最大可为10^5,此时,若是使用二维数组存储,则需要的数组空间会超出,
那么,我们要换用别的算法吗?
NO!
其他算法如由于其时间复杂度,被卡掉的几率很大;
所以仍然选择Dijkstra
这个时候就需要用到一个神奇的堆优化
Dijkstra堆优化
首先,存储方面,选择vector数组存储输入的路径,用小根堆加优先队列进行优化。
struct lq{
int x,d;
};
struct ha{//小根堆
int x,d;
bool operator <(const ha &a) const{//优先队列默认从大到小排列,这个让优先队列从小到大排列
return x>a.x;
}
};
priority_queue<ha> q;//优先队列
ha e;
lq t;
int dis[100005];//与普通版的dijkstra一样存储源点的最短路径
vector<lq> a[100005];//存储输入路径
输入:
int n,m,s,x,y,z;
cin>>n>>m>>s;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);//考虑到m可能很大,用scanf读入可能好一点
t.d=y;
t.x=z;
a[x].push_back(t);//a数组的第i个元素到其结构体的d的距离是x;
//这里建议大家去详细看看其他博主对于vector数组的讲解;
}
主函数:
#define Max 0x3f3f3f3f;//在int 类型中,这个数相当于是正无穷
void Dijkstra(int n,int s){//n未结点个数,s是源点
for(int i=1;i<=n;i++)dis[i]=Max;//将dis数组都赋值未正无穷
e.d=s;
e.x=0;
dis[s]=0;
q.push(e);//将最开始的源点到源点加入优先队列
while(!q.empty()){//队列不为空则继续循环
e=q.top();q.pop();//出队操作
int v=e.d;
int u=e.x;
if(dis[v]<u) continue;//如果dis中的值比队列元素中的少,则后续操作无意义,直接下一位
int len=a[v].size();//数组元素的长度
for(int i = 0; i < len; i++)
{
lq g = a[v][i];
if (dis[v] + g.x < dis[g.d])//将所有比dis小的统统入队
{
dis[g.d] = dis[v] + g.x;
e.d = g.d;
e.x = dis[g.d];
q.push(e);
}
}
}
}
上述操作理解上较难,建议大家去看看其他有关vector数组和优先队列的文章;
o(*≧▽≦)ツ┏━┓
来源:oschina
链接:https://my.oschina.net/u/4416282/blog/4480155