简单路径规划算法
1 Floyd - Warshall
①动态规划
②可以处理负边权的图,不可处理负权环的图
1.1 C语言实现
#include<stdio.h>
int main()
{
int e[10][10], k, i, j, n, m, t1, t2, t3;
int inf = 99999999; //正无穷值
printf("%s","请输入:顶点数 边数\n");
scanf("%d %d", &n, &m);
//初始化
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
if (i == j) e[i][j] = 0;
else e[i][j] = inf;
//读入边
printf("%s","请输入:起点 终点 权值\n");
for (i = 1; i <= m; i++)
{
scanf("%d %d %d",&t1,&t2,&t3);
e[t1][t2] = t3;
}
//Floyd-Warshall算法核心语句
for (k = 1; k <= n; k++) //一个个遍历每个点做中转点
for (i = 1; i <= n; i++)
for (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];
//输出
printf("%s","任意两点最短路程:\n");
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
printf("%10d", e[i][j]);
}
printf("\n");
}
return 0;
}
2 Dijkstra(狄克斯特拉)
2.1 C语言实现
①贪心算法
②单源最短路径问题,实用于有向 无环 正权图
③思想:每次找到离源点最近的一个顶点,然后以该点为中心进行拓展松弛,最终得到源点到所有点的最短路径
④举例:若离1点是起点,最近的点是2,肯定不可能通过第三个点中转,使1号到2号路程进一步缩短。因为1号顶点到其他顶点路程 肯定没有 1号到2号点短
#include<stdio.h>
int main()
{
int e[10][10], dis[10], book[10], i, j, n, m, t1, t2, t3, u, v, min;
int inf = 99999999; //正无穷值
printf("%s", "请输入:顶点数 边数\n");
scanf("%d %d", &n, &m);
//初始化
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
if (i == j) e[i][j] = 0;
else e[i][j] = inf;
//读入边
printf("%s", "请输入:起点 终点 权值\n");
for (i = 1; i <= m; i++)
{
scanf("%d %d %d", &t1, &t2, &t3);
e[t1][t2] = t3;
}
//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
for (i = 1; i <= n; i++)
dis[i] = e[1][i];
//book数组初始化
//book数组记录已知最短路程的顶点
for (i = 1; i <= n; i++)
book[i] = 0;
book[1] = 1;
//dijkstra 狄克斯特拉 算法核心语句
//松弛
for (i = 1; i <= n - 1; i++)
{
//找到离1号顶点最近的顶点
min = inf;
for (j = 1; j <= n; j++)
{
if (book[j] == 0 && dis[j] < min)
{
min = dis[j];
u = j;
}
}
book[u] = 1;
//对u顶点所有出边进行松弛
for (v = 1; v <= n; v++)
{
if (e[u][v] < inf)
{
if (dis[v] > dis[u] + e[u][v])
dis[v] = dis[u] + e[u][v];
}
}
}
//输出
printf("%s", "第一点到各点的最短路程:\n");
for (i = 1; i <= n; i++)
printf("%d ", dis[i]);
getchar();
getchar();
return 0;
}
3 Bellman-Ford
3.1 C语言实现
①单源最短路径问题
②思想:对所有边进行n-1次松弛操作
②1号顶点只能通过一条边到达其余顶点(第1轮对所有边进行松弛) -> 1号顶点只能通过二条边到达其余顶点(第2轮对所有边进行松弛) -> … -> 1号顶点只能通过k条边到达其余顶点(第k轮对所有边进行松弛)。最多n-1轮
③每一轮松弛,有些顶点已经确定是最短路,此后值会一直不变,不再受后续松弛影响,但每次还需判断是否松弛,优化见Bellman-Ford的队列优化
#include<stdio.h>
int main()
{
int dis[10], bak[10], i, k, n, m, u[10], v[10], w[10], check, flag;
int inf = 99999999; //正无穷值
printf("%s", "请输入:顶点数 边数\n");
scanf("%d %d", &n, &m);
//读入边
printf("%s", "请输入:起点 终点 权值\n");
for (i = 1; i <= m; i++)
scanf("%d %d %d", &u[i], &v[i], &w[i]);
//初始化dis数组,这里是1号顶点到其余各顶点的初始路程
for (i = 1; i <= n; i++)
dis[i] = inf;
dis[1] = 0;
//Bellman-Ford算法核心语句
for (k = 1; k <= n - 1; k++)
{
check = 0; //用来标记在本轮松弛中数组dis是否会发生更新
//进行一轮松弛
for (i = 1; i <= m; i++)
{
if (dis[v[i]] > dis[u[i]] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
check = 1; //若数组dis发生变化,改变check的值
}
}
//松弛完毕后检测数组dis是否有更新
if (check == 0) break; //如果数组dis没有更新,提前退出循环结束算法
//每一轮是对所有边进行松弛,所以不一定判n-1轮
}
//检测负权回路
flag = 0;
for (i = 1; i <= m; i++)
if (dis[v[i]] > dis[u[i]] + w[i]) flag = 1;
if(flag ==1) printf("此图含有负权回路");
else
{
//输出最终结果
for (i = 1; i <= n; i++)
printf("%d ", dis[i]);
}
getchar();
getchar();
return 0;
}
4 Bellman-Ford的队列优化
4.1 C语言实现
①只有在前一遍松弛中改变了最短路程估计值的顶点,才可能引起它们邻接点最短路程估计值发生改变。因此用一个队列来存放被成功松弛的顶点,之后只对队列中的点的出边进行松弛。
②临接表的数组表示:first[u[i]]保存顶点u[i]的第一条编号,next[i]存储“编号为i的边”的下一条边的编号。
③数组模拟邻接表可见:https://blog.csdn.net/qq_17550379/article/details/94871801
#include<stdio.h>
int main()
{
int n, m, i, j, k, flag;
//u,v,w的数组大小要根据实际情况来设置,要比m的最大值要大1
int u[8], v[8], w[8];
//first要比n的最大值要大1,next要比m的最大值要大1
int first[6], next[8];
//book数组用来记录哪些顶点已经在队列中
int dis[6] = { 0 }, book[6] = { 0 };
//定义一个队列,并初始化队列
int que[101] = { 0 }, head = 1, tail = 1;
int inf = 99999999; //正无穷值
printf("%s", "请输入:顶点数 边数\n");
scanf("%d %d", &n, &m);
//初始化dis数组,这里是1号顶点到其余各个顶点的初始路程
for (i = 1; i <= n; i++)
dis[i] = inf;
dis[1] = 0;
//初始化book数组,初始化为0,刚开始都不在队列中
for (i = 1; i <= n; i++) book[i] = 0;
//初始化first数组下标1~n的值为-1,表示1~n顶点暂时都没有边
for (i = 1; i <= n; i++) first[i] = -1;
//读入边
printf("%s", "请输入:起点 终点 权值\n");
for (i = 1; i <= m; i++)
{
scanf("%d %d %d", &u[i], &v[i], &w[i]);
//建立临接表的关键
next[i] = first[u[i]];
first[u[i]] = i;
}
//1号顶点入队
que[tail] = 1;
tail++;
book[1] = 1; //标记1号顶点已经入队
while (head < tail) //队列不为空的时候循环
{
k = first[que[head]]; //当前需要处理的队首顶点
while (k != -1) //扫描当前顶点的所有出边
{
if (dis[v[k]] > dis[u[k]] + w[k]) //判断是否松弛成功
{
dis[v[k]] = dis[u[k]] + w[k]; //更新顶点1到顶点v[k]的路程
//book用来判断顶点v[k]是否在队列中
if (book[v[k]] == 0) //表示不在队列中,将顶点v[k]加入队列中
{
//入队
que[tail] = v[k];
tail++;
book[v[k]] = 1; //同时标记顶点v[k]已经入队
}
}
k = next[k];
}
//出队
book[que[head]] = 0;
head++;
}
//判断负权回路
flag = 0;
for (i = 1; i <= m; i++)
if (dis[v[i]] > dis[u[i]] + w[i]) flag = 1;
if(flag ==1) printf("此图含有负权回路");
else
{
//输出最终结果
for (i = 1; i <= n; i++)
printf("%d ", dis[i]);
}
getchar();
getchar();
return 0;
}
5 小结
Floyd | Dijkstra | Bellman-Ford | 队列优化的Bellman-Ford | |
---|---|---|---|---|
空间复杂度 | O(N^2) | O(M) | O(M) | O(M) |
时间复杂度 | O(N^3) | O((M+N)logN)) | O(NM) | O(NM) |
适用情况 | 稠密图和顶点关系密切 | 稠密图和顶点关系密切 | 稀疏图和边关系密切 | 稀疏图和边关系密切 |
负权 | 可以解决 | 不可解决 | 可以解决 | 可以解决 |
所求 | 任两点间最短路 | 单源最短路径问题 | 单源最短路径问题 | 单源最短路径问题 |
判定是否存在负权回路 | 不能 | 不能 | 可以 | 可以 |
思路 | 三层循环枚举判断是否会有更短路 | 从所求点更新周围所有点然后每次走到最近的点 | 对所有边进行n-1次松弛操作更新dis | 与未优化差不多,只是用了队列 |