最短路径
问题:
用带权的有向图表示一个交通运输网,图中:
- 顶点——城市
- 边——城市之间的交通联系
- 权——线路的长度或沿此路线运输所花的时间或费用等
问题:从某顶点出发,沿图的边到达另一个顶点所经过的路径中,各边上权值之和最小的一条路径——最短路径
Dijkstra算法
算法思想:
(以下内容复制度娘)
按路径长度递增次序产生算法:
把顶点集合V分成两组:
(1)S:已求出的顶点的集合(初始时只含有源点V0)
(2)V-S=T:尚未确定的顶点集合
将T中顶点按递增的次序加入到S中,保证:
(1)从源点V0到S中其他各顶点的长度都不大于从V0到T中任何顶点的最短路径长度
(2)每个顶点对应一个距离值
S中顶点:从V0到此顶点的长度
T中顶点:从V0到此顶点的只包括S中顶点作中间顶点的最短路径长度
依据:可以证明V0到T中顶点Vk的,或是从V0到Vk的直接路径的权值;或是从V0经S中顶点到Vk的路径权值之和
举例:
第一步:
S集合:V0
T集合:V1,V2,V3,V4,V5
下表中∞表示两个点之间无联系
顶点 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|
权值 | ∞ | 10 | ∞ | 30 | 100 |
选出权值最小的一个结点,为V2,权值为10
第二步:
S集合:V0,V2
T集合:V1,V3,V4,V5
顶点 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|
权值 | ∞ | 10 | ∞ | 30 | 100 |
权值 | ∞ | 60 | 30 | 100 |
选出权值最小的一个结点,为V4,权值为30
注意,这里的V3对应的权值为60是v0到V2的权值加上V2到V3的权值
第三步:
S集合:V0,V2,V4
T集合:V1,V3,V5
顶点 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|
权值 | ∞ | 10 | ∞ | 30 | 100 |
权值 | ∞ | 60 | 30 | 100 | |
权值 | ∞ | 50 | 90 |
选出权值最小的一个结点,为V3,权值为50
注意,这里的V3对应的权值为50是V0到V4的权值加上V4到V3的权值,也就是说,之前的路线不如现在的路线快捷
第四步:
S集合:V0,V2,V4,V3
T集合:V1,V5
顶点 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|
权值 | ∞ | 10 | ∞ | 30 | 100 |
权值 | ∞ | 60 | 30 | 100 | |
权值 | ∞ | 50 | 100 | ||
权值 | ∞ | 60 |
选出权值最小的一个结点,为V5,权值为60
第五步:
S集合:V0,V2,V4,V3,V5
T集合:V1
顶点 | V1 | V2 | V3 | V4 | V5 |
---|---|---|---|---|---|
权值 | ∞ | 10 | ∞ | 30 | 100 |
权值 | ∞ | 60 | 30 | 100 | |
权值 | ∞ | 50 | 100 | ||
权值 | ∞ | 60 | |||
权值 | ∞ |
至此所有的步骤也就结束了
通过最后一个表可以看到从V0到各个顶点对应的最小权值和,V1最后的∞表示无法从V0到V1
Dijkstra算法实现的代码在网上一搜有很多,主要的代码步骤为:
- S = { 源点 }
- for (i=1;i<n;i++) D[i] = c[源点,i] //D[i]为源点到各个顶点的权值和,c[源点,i]表示源点到顶点i的边长
- for(i=1;i<n;i++){
在V-S中选择一个结点W,使得D[w]最小,将W加入集合S
for(每一个在V-S中的结点V) D[v] = min(D[v], D[w]+C[w,v];
}
但老师布置的作业里,增加了一个路径的求法。
目前我的思路是,每一次都列出当前步骤最后求出的最小权值对应的顶点所对应的路径,在下一步里,如果某个顶点的权值和发生了变更,那就说明它使用到了上一步列出来的那个路径,那么,它的路径就会更改为上一步列出来的路径
听上去有点绕,举个例子:
比如V0到V3:
- 第一步中,权值和最小的是10,对应的是V2顶点,那么最短路径为V0->V2
- 第二步中,V0到V3的权值和发生了变化,从∞更改到了60,说明它用到了步骤一里V0->V2这一条路径,那么它自己的路径就要变成V0->V2->V3,而对应的比如V4,它的权值和依旧是30,它的路径也依旧是V0->V4.这一步骤结束后,权值和最小的是30,对应的是V4顶点,那么最短路径为V0->V4
- 第三步中,V0到V3的权值和再次发生了变化,从60更改到了50,说明它用到了步骤二里V0->V4这一条路径,那么它自己的路径就要变成V0->V4->V3,注意的是,它的路径里不再包有V2结点
那么在代码里,我使用了以下几个变量:
名称 | 作用 |
---|---|
vector< string> S | 已求出的顶点集合 |
vector< string> T | 未求出的顶点集合 |
vector<vector< string> > result(G.vexnum) | 对应的路径 |
VRType distance[G.vexnum] | 源点到各个顶点的权值和 |
vector< string> process | 每一步里权值和最小的顶点对应的路径 |
string p | 最小权值和对应的结点 |
weight | 最小权值和 |
process初始放入一个源结点,p初始化也为源结点,weight初始化为0
具体代码如下:
int getindex(MGraph &G, string u)
{
int min = 100000;
int u_index;
for(int i=0;i<G.vexnum;i++){
if(!u.compare(G.vexs[i])){
u_index = i;
break;
}
}
return u_index;
}
void Dijkstra(MGraph &G, string u, string v)
{
vector<string> S;
vector<string> T;
S.push_back(u);
for(int i=0;i<G.vexnum;i++){
if(u!=G.vexs[i]){
T.push_back(G.vexs[i]);
}
}
VRType distance[G.vexnum]; //VRType为权值的类型,int、double等
for(int i=0;i<G.vexnum;i++){
distance[i] = 100000; //100000表示无穷大
}
vector<vector<string> > result(G.vexnum);
vector<string> process;
process.push_back(u);
int weight = 0;
string p = u;
int index = getindex(G, u), index_, min_index;
for(int i=1;i<G.vexnum;i++){
double min = 10000;
for(int j=0;j<T.size();j++){
index_ = getindex(G, T[j]); //当前结点在图所有结点里的位置
if(G.arcs[index][index_].adj!=0&&(G.arcs[index][index_].adj+weight)<distance[index_]){
distance[index_] = G.arcs[index][index_].adj + weight;
result[index_].clear(); //路径需要发生变更
for(int k=0;k<process.size();k++) result[index_].push_back(process[k]);
result[index_].push_back(T[j]); //路径最后把自身结点加上
}
if(distance[index_]<min && distance[index_]!=0){ //寻找出当前最小的权值和和其最硬的结点
min = distance[index_];
p = T[j];
min_index = index_;
}
}
//min_index为最小权值和对应结点的位置,传给index是因为下一步就以该结点为源点继续了
index = min_index;
process.clear();
for(int j=0;j<result[index].size();j++) process.push_back(result[index][j]);
//for(int j=0;j<process.size();j++) cout<<process[j]<<" ";
cout<<endl;
S.push_back(p);
vector<string>::iterator iter=find(T.begin(),T.end(),p);
T.erase(iter);
weight = min;
}
int v_index = getindex(G, v);
cout<<result[v_index].size()<<endl;
if(result[v_index].size()!=0){
cout<<"路径为:";
for(int i=0;i<result[v_index].size();i++) cout<<result[v_index][i]<<" ";
cout<<endl<<"代价为:"<<distance[v_index]<<endl;
}
else{
cout<<"两点间无路径"<<endl;
return;
}
}
当然这个函数最后实现的是输入两个城市的名称就能获取对应的路径
下面是之前举得例子根据该算法得到的从V0到各个顶点对应的路径:
根据代码得到每一步的结果如下:
理解完毕后,就看下代码,会发现process变量的存在其实没什么必要,它就是result[源点],不过在开始进行for循环之前在result[源点]的容器里放上一个源点就好了,结果其实是一样的。
重新放一下更改后的for循环的代码吧:
int weight = 0;
string p = u;
int index = getindex(G, u), index_, min_index;
result[index].push_back(u);
for(int i=1;i<G.vexnum;i++){
cout<<"第"<<i<<"步:"<<endl;
double min = 10000;
for(int j=0;j<T.size();j++){
index_ = getindex(G, T[j]);
if(G.arcs[index][index_].adj!=0&&(G.arcs[index][index_].adj+weight)<distance[index_]){
distance[index_] = G.arcs[index][index_].adj + weight;
result[index_].clear();
for(int k=0;k<result[index].size();k++) result[index_].push_back(result[index][k]);
result[index_].push_back(T[j]);
}
if(distance[index_]<min && distance[index_]!=0){
min = distance[index_];
p = T[j];
min_index = index_;
}
}
index = min_index;
cout<<"最短路径:";
for(int j=0;j<result[index].size();j++) cout<<result[index][j]<<" ";
cout<<endl;
S.push_back(p);
vector<string>::iterator iter=find(T.begin(),T.end(),p);
T.erase(iter);
weight = min;
}