参考:《啊哈算法》
Floy-Warshall
有向图的多远最短路径问题
问题
求任意两个城市之间的最短路程,也就是求任意两个点之间的最短路径
简单分析:先通过条件构造出图的二位矩阵形式,然后再通过不断更新,在一个二维矩阵的一行中表示出横坐标点到纵坐标点的最短路径
算法本质:假如两个点间距离为a,若找不出其他路径(第三点中转)使得距离小于a,则a是他们的最小距离。
展示
#include <cstdio>
#define N 21
#define M 99999
int e[N][N];
int main() {
int m,n;
scanf("%d%d",&m,&n);
int a,b,c;
for(int i=1; i<=m; i++) {
for(int j=1; j<=m; j++) {
if(i==j) {
e[i][j]=0;
} else {
e[i][j]=M;
}
}
}
for(int i=1; i<=n; i++) {
scanf("%d%d%d",&a,&b,&c);
e[a][b]=c;
}
//最关键部分,不停的中转,中转更新,这就是核心代码
for(int k=1; k<=m; k++) {
for(int i=1; i<=m; i++) {
for(int j=1; j<=m; j++) {
if(e[i][j]>e[i][k]+e[k][j]) {
e[i][j]=e[i][k]+e[k][j];
}
}
}
}
for(int i=1; i<=a; i++) {
for(int j=1; j<=a; j++) {
printf("%3d",e[i][j]);
}
printf("\n");
}
return 0;
}
注意此算法并不能解决负权回路,但是能解决负权,两者是不能混为一谈的,我之前就是搞错了。
Dijkstra 算法,单源最短路
所谓的单源最短路
就是指定一个点,然后求出这个点到其余各个点的最短路径。
展示(本例是求第一个点到其余各点的最短路):
#include <cstdio>
#define N 21
#define M 99999
int book[N];
int dis[N];
int e[N][N];
int main() {
int m,n;
scanf("%d%d",&m,&n);//输入顶点数和边数(有向图)
for(int i=1; i<=m; i++) {//初始化邻接数组
for(int j=1; j<=m; j++) {
if(i==j) {
e[i][j]=0;
} else {
e[i][j]=M;
}
}
}
book[1]=1;
int a,b,c;
for(int i=1; i<=n; i++) {//输入边
scanf("%d%d%d",&a,&b,&c);
e[a][b]=c;
}
for(int i=1; i<=m; i++) {//初始化距离数组
dis[i]=e[1][i];
}
int u;
for(int i=1; i<=m-1; i++) {//这个for是控制循环次数的,我要更新距离数组的所有元素,但是第一个点已经不用更新了,因此剩下m-1个点,还需进行m-1次循环
int mi=M;//每次都假设一个最小值
for(int j=1; j<=m; j++) {//找出最小值,排序后再找是不现实的,因为排序的过程更改了一些元素的位置
if(book[j]==0&&dis[j]<mi) {
mi=dis[j];
u=j;
}
}
book[u]=1;//找到最小距离的点,表示访问了这个点了,并使用这个点进行中转,这个点的dis值已经是确定值,我们使用这个确定值来尝试到达其他的点,路径就是其他点的出边。
for(int k=1; k<=m; k++) {//松弛
if(e[u][k]<M) {//小于M即说明它们之间存在路径,是该点的一条出边,等于则说明不存在,也就没有松弛的必要,
if(dis[k]>dis[u]+e[u][k]) {
dis[k]=dis[u]+e[u][k];//松弛,通过已经确定的点中转
}
}
}
}
for(int i=1; i<=m; i++) {
printf("%d ",dis[i]);
}
printf("\n");
return 0;
}
测试
6 9
1 2 1
1 3 12
2 3 9
2 4 3
3 5 5
4 3 4
4 5 15
5 6 4
至于如何求其他路,就是变着来了,第一次先访问哪个点,怎么做的标记
还有使用邻接链表进行优化。实在是有点没弄懂就不实现了
Bellman-Ford——-解决负权边
核心代码
for(int k=1;k<=n-1;k++){//核心语句,好家伙这个算法连图都不用存。为什么要进行n-1轮的松弛。任意两点之间的最短路径最多包含n-1条边
for(int i=1;i<=m;i++){
if(dis[v[i]]>dis[u[i]]+w[i]){//1号点到v[i]点的距离能不能通过u[i]点进行缩小
dis[v[i]]=dis[u[i]]+w[i];
}
}
}
分析
这与前两个算法有很大的不同,并没有使用邻接矩阵来存储数据,而是直接在输入数据中进行松弛,注意比较的点u,v与下输入时的顺序不一样,还有就是为什么要进行n-1轮松弛,因为任意两点之间的最短路径最多包含n-1条边。同时注意到这个算法是能解决负权边的,同时还能显性判断有没有负权边,就是执行n-1轮循环结束后,仍然可以继续松弛,那就一定是含有负权边了。
展示(同样是求1号点到其余各点的最短路)
#include<cstdio>
#define N 21
#define M 99999
int dis[N];
int u[N];
int v[N];
int w[N];
int main()
{
int m,n;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++){
dis[i]=M;
}
dis[1]=0;
for(int i=1;i<=n;i++){
scanf("%d%d%d",&u[i],&v[i],&w[i]);
}
for(int k=1;k<=n-1;k++){//核心语句,好家伙这个算法连图都不用存。为什么要进行n-1轮的松弛。任意两点之间的最短路径最多包含n-1条边
for(int i=1;i<=m;i++){
if(dis[v[i]]>dis[u[i]]+w[i]){//1号点到v[i]点的距离能不能通过u[i]点进行缩小
dis[v[i]]=dis[u[i]]+w[i];
}
}
}
for(int i=1;i<=n;i++){
printf("%d ",dis[i]);
}
return 0;
}
测试
5 5
2 3 2
1 2 -3
1 5 5
4 5 2
3 4 3
优化:
每次松弛之后,有些顶点已经求得最短路,此后这些点的估计值基本上是不会发生改变的,但是每轮松弛的时候还是要判断是否需要松弛,浪费了时间,因此可以每次仅对最短路估计值发生了变化的顶点的所有出边执行松弛操作
总结
最短路径算法要根据实际需求和每一种算法的特性,选择合适的算法