迪杰斯特拉算法是由荷兰计算机科学家狄克斯特拉于19591959年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有向图中最短路径问题。迪杰斯特拉算法主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止。
定义:
Dijkstra算法一般的表述通常有两种方式,一种用永久和临时标号方式,一种是用OPENOPEN,CLOSECLOSE表的方式,这里均采用永久和临时标号的方式。注意该算法要求图中不存在负权边。
原理:
1.首先,引入一个辅助向量DD,它的每个分量D_iDi表示当前所找到的从起始点vv(即源点vv)到其它每个顶点v_ivi的长度。
例如,D_3=2D3=2表示从起始点到顶点3的路径相对最小长度为2。这里强调相对就是说在算法执行过程中D的值是在不断逼近最终结果但在过程中不一定就等于长度。
2.DD的初始状态为:若从vv到v_ivi有弧(即从vv到v_ivi存在连接边),则D_iDi为弧上的权值(即为从vv到v_ivi的边的权值);否则置D_iDi为∞∞。
显然,长度为D_j=min\left \{ v_i \in V\right \}Dj=min{vi∈V}的路径就是从vv出发到顶点v_jvj的长度最短的一条路径,此路径为(v,v_j)(v,vj)。
3.那么,下一条长度次短的是哪一条呢?也就是找到从源点vv到下一个顶点的最短路径长度所对应的顶点,且这条最短路径长度仅次于从源点vv到顶点v_jvj的最短路径长度。
假设该次短路径的终点是v_kvk,则可想而知,这条路径要么是(v,v_kv,vk),或者是(v,v_j,v_kv,vj,vk)。它的长度或者是从vv到v_kvk的弧上的权值,或者是D_jDj加上从v_jvj到v_kvk的弧上的权值。
4.一般情况下,假设SS为已求得的从源点vv出发的最短路径长度的顶点的集合,则可证明:下一条次最短路径(设其终点为xx)要么是弧(v,xv,x),或者是从源点vv出发的中间只经过SS中的顶点而最后到达顶点xx的路径。
因此,下一条长度次短的的最短路径长度必是D_j=min\left \{ D_i|v_i \in V-S \right \}Dj=min{Di∣vi∈V−S},其中D_iDi要么是弧(v,v_i)(v,vi)上的权值,或者是D_k(v_k∈S)Dk(vk∈S)和弧(v_k,v_i)(vk,vi)上的权值之和。
算法描述如下:
1)令arcsarcs表示弧上的权值。若弧不存在,则置arcsarcs为∞∞(在本程序中为MAXCOSTMAXCOST)。SS为已找到的从vv出发的的终点的集合,初始状态为空集。那么,从vv出发到图上其余各顶点v_ivi可能达到的长度的初值为D=arcs[Locate Vex(G,v_i)]D=arcs[LocateVex(G,vi)],v_i∈Vvi∈V;
2)选择v_jvj,使得D_j=min(D|v_i∈V-S)Dj=min(D∣vi∈V−S);
3)修改从vv出发的到集合V-SV−S中任一顶点v_kvk的最短路径长度。
问题描述:
在无向图G=(V,E)G=(V,E)中,假设每条边E_iEi的长度为w_iwi,找到由顶点V_0V0到其余各点的最短值。
算法思想:
按路径长度递增次序产生算法:
把顶点集合VV分成两组:
(1)SS:已求出的顶点的集合(初始时只含有源点V_0V0)
(2)V-S=TV−S=T:尚未确定的顶点集合
将T中顶点按递增的次序加入到SS中,保证:
(1)从源点V_0V0到SS中其他各顶点的长度都不大于从V_0V0到TT中任何顶点的最短路径长度
(2)每个顶点对应一个距离值
SS中顶点:从V_0V0到此顶点的长度
TT中顶点:从V_0V0到此顶点的只包括SS中顶点作中间顶点的最短路径长度
依据:可以证明V_0V0到TT中顶点V_kVk的,或是从V_0V0到V_kVk的直接路径的权值;或是从V_0V0经SS中顶点到V_kVk的路径权值之和
(反证法可证)
求最短路径步骤
算法步骤如下:
G=(V,E)G=(V,E)
1.初始时令S=(V_0)S=(V0),T=V-S=T=V−S={其余顶点},TT中顶点对应的距离值
若存在<V_0V0,V_iVi>,d(V_0,V_i)d(V0,Vi)为<V_0V0,V_iVi>弧上的权值
若不存在<V_0V0,V_iVi>,d(V_0,V_i)d(V0,Vi)为∞∞
2.从TT中选取一个与SS中顶点有关联边且权值最小的顶点WW,加入到SS中
3.对其余TT中顶点的距离值进行修改:若加进WW作中间顶点,从V_0V0到V_iVi的距离值缩短,则修改此距离值
重复上述步骤2、3,直到SS中包含所有顶点,即W=V_iW=Vi为止
1.0版标程:
#include<cstdio>
using namespace std;
int n,m,s,tot,dis[10003],head[500003],mi,t;
bool vis[10003];
struct edge {
int next,to,w;
edge() {
this->next=this->to=this->w=0;
}
} h[500003];
void add(int u,int v,int w) {
h[++tot].next=head[u];
h[tot].to=v;
h[tot].w=w;
head[u]=tot;
}
void dijkstra() {
for(int i=1; i<=n; i++) {
dis[i]=2147483647;
}
dis[s]=0;
for(int i=1; i<=n; i++) {
mi=2147483647;
t=-1;
for(int j=1; j<=n; j++) {
if(!vis[j]&&dis[j]<mi) {
mi=dis[j];
t=j;
}
}
if(t==-1) {
break;
}
vis[t]=1;
for(int j=head[t]; j; j=h[j].next) {
if(!vis[h[j].to]&&dis[h[j].to]>(h[j].w+dis[t])) {
dis[h[j].to]=h[j].w+dis[t];
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&s);
for(int i=0; i<m; i++) {
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
dijkstra();
for(int i=1; i<=n; i++) {
printf("%d ",dis[i]);
}
return 0;
}
堆优化:
思考:
该算法复杂度为n^2n2,我们可以发现,如果边数远小于n^2n2,对此可以考虑用堆这种数据结构进行优化,取出最短路径的复杂度降为O(1)O(1);每次调整的复杂度降为O(elogn)O(elogn);ee为该点的边数,所以复杂度降为O((m+n)logn)O((m+n)logn)。
实现:
1.将与源点相连的点加入堆,并调整堆。
2.选出堆顶元素uu(即代价最小的元素),从堆中删除,并对堆进行调整。
3.处理与uu相邻的,未被访问过的,满足三角不等式的顶点
1):若该点在堆里,更新距离,并调整该元素在堆中的位置。
2):若该点不在堆里,加入堆,更新堆。
4.若取到的uu为终点,结束算法;否则重复步骤2、3。
2.0版标程:
#include<cstdio>
#include<queue>
using namespace std;
int head[100003],d[100003],ver[200003],edge[200003],Next[200003],n,m,tot,x,i,y,z,s,w,u,k;
bool v[100003];
priority_queue<pair<int,int> >q;
inline void add(int x,int y,int z) {
ver[++tot]=y;
edge[tot]=z;
Next[tot]=head[x];
head[x]=tot;
}
int main() {
scanf("%d%d%d",&n,&m,&s);
while(m--) {
scanf("%d%d%d",&u,&k,&w);
add(u,k,w);
}
for(i=1; i<=n; ++i) {
d[i]=2147483647;
}
d[s]=0;
q.push(make_pair(0,s));
while(q.size()) {
x=q.top().second;
q.pop();
if(v[x]) {
continue;
}
v[x]=1;
for(i=head[x]; i; i=Next[i]) {
y=ver[i];
z=edge[i];
if(d[y]>d[x]+z) {
d[y]=d[x]+z;
q.push(make_pair(-d[y],y));
}
}
}
for(i=1; i<=n; ++i) {
printf("%d ",d[i]);
}
}