今天训练的时候遇到了一到比较暴力的最短路的题,才发现做算法一年多了连简单的最短路算法都没有怎么去了解,所以去学习了一下最短路,在看了网上的博客之后决定整理一下加深自己的理解。
先来说Floyd算法:
Floyd算法是用来求多源最短路径问题的,也就是求图上任意两点间的最短路。
仔细想一下,如果要求两点间的最短距离,我们需要引入第三个点,如果由起点经由这第三个点再到终点的距离比由起点到终点距离近的话就会更新起点和终点间的最短距离,那么,如果我们枚举每个点作为中间点,用这个中间点来更新所有的点,那么就完成了每两个点之间最短路的更新,这就是Floyd算法,这么说有点抽象,我们用下面这个例子来模拟这个过程。
如图,求任意两点间的距离,我们先用一个邻接矩阵来表示各个点间的距离:
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 10 | 1 | ∞ | 3 |
2 | 10 | 0 | 2 | 4 | ∞ |
3 | 1 | 2 | 0 | ∞ | ∞ |
4 | ∞ | 4 | ∞ | 0 | ∞ |
5 | 3 | ∞ | ∞ | ∞ | 0 |
2→3距离为2,2→1→3距离为11,不更新;2→4距离为4,2→4→1距离为∞,不更新;
2→5距离为∞,2→1→5距离为13,更新2→5距离为13;3→4距离为∞,3→1→4距离为∞,不更新;
3→5距离为∞,3→1→5距离为4,更新3→5距离为4;4→5距离为∞,4→1→5距离为∞,不更新;
新的邻接矩阵为:
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 10 | 1 | ∞ | 3 |
2 | 10 | 0 | 2 | 4 | 13 |
3 | 1 | 2 | 0 | ∞ | 4 |
4 | ∞ | 4 | ∞ | 0 | ∞ |
5 | 3 | 13 | 4 | ∞ | 0 |
1→3距离为1,1→2→3距离为12,不更新;1→4距离为∞,1→2→4距离为14,1→4更新为14;
1→5距离为3,1→2→5距离为23,不更新;3→4距离为∞,3→2→4距离为6,3→4更新6;
3→5距离为4,3→2→5距离为15,不更新;4→5距离为∞,4→2→5距离为17,4→5更新为17;
新的邻接矩阵为:
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 10 | 1 | 14 | 3 |
2 | 10 | 0 | 2 | 4 | 13 |
3 | 1 | 2 | 0 | 6 | 4 |
4 | 14 | 4 | 6 | 0 | 17 |
5 | 3 | 13 | 4 | 17 | 0 |
1→2距离为10,1→3→2距离为3,1→2更新为3;1→4距离为14,1→3→4距离为7,1→4更新为7;
1→5距离为3,1→3→5距离为7,不更新;2→4距离为4,2→3→4距离为8,不更新;
2→5距离为13,2→3→5距离为6,2→5更新为6;4→5距离为17,4→3→5距离为10,4→5更新为10;
新的邻接矩阵为:
1 | 2 | 3 | 4 | 5 | |
1 | 0 | 3 | 1 | 7 | 3 |
2 | 3 | 0 | 2 | 4 | 6 |
3 | 1 | 2 | 0 | 6 | 4 |
4 | 7 | 4 | 6 | 0 | 10 |
5 | 3 | 6 | 4 | 10 | 0 |
接下来依次枚举4,5为中间点,但是已经没有更新了,上表已经为结果。
下面是Floyd算法的代码:
void floyd(){
for(int k = 1;k <= n;k++){
for(int i = 1;i<=n;i++){
for(int j = 1;j <= n;j++){
if(e[i][k] < INF&&e[k][j]<INF&&e[i][j]>e[i][k]+e[k][j]){//INF为∞
e[i][j] = e[i][k]+e[k][j];
}
}
}
}
}
再来说说Dijkstra算法:
Dijkstra算法是用来求单源最短路径问题的,就是从一个起点到其他所有点的最短路径,但是这个算法有一个局限的地方就是不能有负权边。
我们先定义一个包含最短路的点的集合,初始时只有起点在这个集合中,依然是前面引入第三个点的问题,距起点最近的那个点是无法再通过中间点来找到一个更近的路来更新这两点间的距离的,因为这两个点之间距离已经是最近,如果有更近的路,则说明选择的点不是最近的点,所以,从起点到这个最近的点一定在最短路中,到起点最近的点就加入到这个点集中,之后我们借助这个点来更新到其他所有点的距离,再选择距离起点最近的点加入到点集中,再更新,一直重复操作,直到没有可选的点为止。说起来没有那么直观,我们再用上面的例子来模拟一下这个过程。
求点1到其他所有点的最短距离。
我们先用一个dis数组来存一下点1到其他点的距离。
1 | 2 | 3 | 4 | 5 | |
dis | 0 | 10 | 1 | ∞ | 3 |
可以看出来,3是距离1最近的点,那么我们把3加入到最短路的集合,同时以3来更新dis数组:
1→2距离为10,1→3→2距离为3,dis[2]更新为3;1→4距离为∞,1→3→4距离为∞,不更新;1→5距离为3,1→3→5距离为∞,不更新;
1 | 2 | 3 | 4 | 5 | |
dis | 0 | 3 | 1 | ∞ | 3 |
1和3已经在最短路点集中了,我们选择最近的2加入点集,同时以2来更新dis数组:
1→4距离为∞,1→2→4距离为7,dis[4]更新为7;1→5距离为3,1→2→5距离为∞,不更新;
1 | 2 | 3 | 4 | 5 | |
dis | 0 | 3 | 1 | 7 | 3 |
接下来选择最近的5,但是已经没有什么可更新的了,加入点集,再选择4加入点集,可选集合为空,算法结束。
下面是Dijkstra算法代码:
void Dijkstra(){
memset(visit,false,sizeof(visit));
int temp = INF;
int index = -1;
dis[1] = 0;
for(int i = 2;i <= n;i++){
dis[i] = e[1][i];
if(dis[i] < temp){
temp = dis[i];
index = i;
}
}
visit[1] = 1;
while(!visit[index]&&index!=-1){//当最近的点已经被访问过,说明没有新的点加入
visit[index] = 1;
int index1 = index;
temp = INF;
for(int i = 1;i <= n;i++){
if(!visit[i]){
if(dis[i]>dis[index1]+e[index1][i]){
dis[i] = dis[index1]+e[index1][i];
}
if(dis[i]<temp){
index = i;
temp = dis[i];
}
}
}
}
}
最后再贴一下这两个算法的模板(HDU2544):
#include <bits/stdc++.h>
using namespace std;
const int MAX = 110;
const int INF = 1e7;
int n,m;
int e[MAX][MAX];
int dis[MAX];
bool visit[MAX];
void floyd(){
for(int k = 1;k <= n;k++){
for(int i = 1;i<=n;i++){
for(int j = 1;j <= n;j++){
if(e[i][k] < INF&&e[k][j]<INF&&e[i][j]>e[i][k]+e[k][j]){
e[i][j] = e[i][k]+e[k][j];
}
}
}
}
}
void Dijkstra(){
memset(visit,false,sizeof(visit));
int temp = INF;
int index = -1;
dis[1] = 0;
for(int i = 2;i <= n;i++){
dis[i] = e[1][i];
if(dis[i] < temp){
temp = dis[i];
index = i;
}
}
visit[1] = 1;
while(!visit[index]&&index!=-1){
visit[index] = 1;
//cout << index << '\n';
int index1 = index;
temp = INF;
for(int i = 1;i <= n;i++){
if(!visit[i]){
if(dis[i]>dis[index1]+e[index1][i]){
dis[i] = dis[index1]+e[index1][i];
}
if(dis[i]<temp){
index = i;
temp = dis[i];
//cout << i << '\n';
}
}
}
}
}
int main(){
while(cin >> n >> m&&m+n){
int x,y,c;
for(int i = 1;i<=n;i++){
for(int j = 1;j <= n;j++){
if(i == j){
e[i][j] = 0;
}
else{
e[i][j] = INF;
}
}
}
for(int i = 1;i <= m;i++){
cin >> x >> y >> c;
e[x][y] = c;
e[y][x] = c;
}
//Floyd
/*floyd();
cout << e[1][n] << '\n';
*/
//Dijkstra
Dijkstra();
cout << dis[n] << '\n';
}
return 0;
}
至此,两个常用的求最短路的算法已经整理完成,再回想起来没有想象中那么难,只是没有静下心来好好学习罢了。