上学期学习离散数学的时候当时做了些最短路的题,不过都很皮毛,这学期学数据结构刚好学到了,再次总结和整理一下。
首先复习一次dijkstra算法。
复杂度是n平方,用的是动态规划的思想,用一个数组L[i]来表示源点到i点的最短路径,我用map[i][j]表示从顶点i到顶点j的距离,然后将顶点划分成两部分,一部分是已经求出从源点到该点最短路径的点,一部分是还不知道最短路径的点,用P[i]为true来表示这个点还不知道最短路径是多少。然后外层循环依次选点,内层首先找到离这点最近且不知道源点到该点最短路的一点,然后再松弛该点即可。
理解算法成立的一个要点就是要理解到松弛是互不影响的,比如A点通过B点到D的最短路径是5,而通过C到D的最短路径是3.所以最后L[D]=3. 那么是不是必须按顺序松弛呢?这是没有必要的,比如你先算A到D的最短路径是3,而后遍历到B点开始松弛,发现路径为5,这时候是不会更改L[D]的,所以不用担心啦。
这次我的参考书是<<算法导论>>,里头首先介绍的是松弛技术.
所谓的松弛技术,是对一个点而言的,假设对顶点v,搜索所有可以到达v的点,我们用j来代表遍历到的v点可以到达的点。看看L[j]是否大于L[v]+map[v][j]如果成立则令L[j]=L[v]+map[v][j].根据那页底下的注释,这个名字的由来和三角不等式有关,满足此约束并没有“压力”,所以这个不等式约束是“松弛的”。
松弛前还要进行初始化。
我写的dijkstra代码如下
#include <iostream>
using namespace std;
#define MAXN 101
#define INF 0x3fffffff
int N,M,map[MAXN][MAXN],rounte[105];//rounte[i]=j表示i的前驱是j,也就是算法导论里头的那个π,所谓前驱就是从源点到i的最短路的i的前头那个点
void print(int endp,int startp) //递归打印最短路径函数
{
if(rounte[endp]!=startp) //如果endp的前驱不是起始点的话,继续递归,现在打出路线的话路线是反的
print(rounte[endp],startp);
else cout<<startp<<" "; //这时候递归到起始点了,先把起始点打出来
cout<<endp<<" ";
}
int dijkstra(int source,int destination)
{
int L[105]; //L[i]表示源点到i点的最短路径
bool P[105]; //P[i]为true表示还不知道源点到i点的最短路
for(int i=1;i<=N;++i) //初始化,L[i]=无穷表示不可到达,即不存在最短路,
L[i]=INF,P[i]=true,rounte[i]=i;
L[source]=0; //到本身的最短路为0
for(int i=1;i<=N;++i) //每个顶点遍历一次
{
int x,min=INF;
for(int j=1;j<=N;++j) //找到距离当前点最近的点x
{
if(P[j] && min>L[j])
{
min=L[j];
x=j;
}
}
P[x]=false;
for(int j=1;j<=N;++j) //松弛该点x
{
if(map[x][j]!=INF && L[j]>L[x]+map[x][j])
{
L[j]=L[x]+map[x][j];
rounte[j]=x;
}
}
}
print(destination,source);
cout<<endl;
return L[destination]; //返回源点到点destination的最短路
}
int main()
{
while(cin>>N>>M && N && M) //N表示有N个顶点,M表示有M条路,且我假设是无向路
{
for(int i=1;i<=N;++i)
for(int j=1;j<=N;++j)
map[i][j]=INF;
for(int i=1;i<=M;++i)
{
int source,destination,length;
cin>>source>>destination>>length;
map[source][destination]=map[destination][source]=length;
}
cout<<dijkstra(1,N)<<endl;
}
return 0;
}