一.简介
在一个带权值的图中,从一个顶点走到另一个顶点经过的边的权值之和最小称为两个顶点的最短路径.最短路径的找法有两种常见的算法,分别是迪杰斯特拉算法和弗洛伊德算法.本文中实现图所用的代码见数据结构和算法学习笔记六:图的相关实现 - movin2333 - 博客园 (cnblogs.com).
二.代码实现
/************************************ * 创建人:movin * 创建时间:2021/7/6 21:39:43 * 版权所有:个人 ***********************************/ using System; using System.Collections.Generic; using System.Text; namespace GraphCore { public class ShortestPathUtil { /// <summary> /// 最短路径问题-迪杰斯特拉算法 /// 采用贪心策略,不可靠 /// </summary> /// <param name="graph"></param> /// <param name="startIndex">起始顶点下标</param> /// <param name="paths">存储最短路径走法的数组,下标的链式存储</param> /// <returns>起始点到其他点的最短距离数组</returns> public static int[] ShortestPath_Dijkstra(AdjacencyMatrixGraph graph,int startIndex,out int[] paths) { //数组中没有找过的顶点下标值对应临时最短路径,找过的顶点下标值对应最短路径 //最后返回的结果数组 int[] minDistance = new int[graph.Count]; //存储最短路径的走法,如果要找到开始顶点到某个顶点的最短路径,遍历这个数组即可 paths = new int[graph.Count]; //标识顶点是否已经在最短路径中的标志位 bool[] isSearched = new bool[graph.Count]; //初始化 for (int i = 0; i < graph.Count; i++) { isSearched[i] = false; minDistance[i] = graph.adjacencyMatrix[startIndex, i]; } //开始顶点到开始顶点距离为0,已经找过 isSearched[startIndex] = true; paths[0] = startIndex; //当前正在找的顶点下标和用来找当前最短距离的临时最小值 int nowIndex = startIndex; int tempMin = int.MaxValue; //外循环,每次循环找到一个顶点 for (int i = 1; i < graph.Count; i++) { tempMin = int.MaxValue; //一轮内循环,找到没有找的顶点中的路径的最小值和顶点下标 for (int j = 0; j < graph.Count; j++) { if(!isSearched[j] && minDistance[j] < tempMin) { nowIndex = j; tempMin = minDistance[j]; } } //找到最小值后更新 //校验最小值顶点没有更新,则说明已经找完了 if (isSearched[nowIndex]) { break; } //更新找到的最小值 isSearched[nowIndex] = true; paths[i] = nowIndex; //二轮内循环,更新所有的最小距离 for(int j = 0;j < graph.Count; j++) { //校验顶点没有找过且当前最小顶点到某顶点距离比当前存储的最短距离短,则更新最短距离 if(!isSearched[j] && graph.adjacencyMatrix[nowIndex,j] != int.MaxValue && (tempMin + graph.adjacencyMatrix[nowIndex,j]) < minDistance[j]) { minDistance[j] = tempMin + graph.adjacencyMatrix[nowIndex, j]; } } } return minDistance; } /// <summary> /// 最短路径问题-弗洛伊德算法 /// 采用动态规划策略 /// </summary> /// <param name="graph"></param> /// <param name="paths">任意两个顶点之间的最短路径走法索引表</param> /// <returns>任意两个顶点之间的最短路径距离</returns> public static int[,] ShortestPath_Floyd(AdjacencyMatrixGraph graph,out int[,] paths) { //路径数组,存储所有最短走法的路径,其中每个位置存储的都是下一个顶点的下标 paths = new int[graph.Count, graph.Count]; //距离数组,存储所有最短走法的距离 int[,] distances = new int[graph.Count, graph.Count]; //初始化两个数组 for (int i = 0; i < graph.Count; i++) { for (int j = 0; j < graph.Count; j++) { paths[i, j] = j; distances[i, j] = graph.adjacencyMatrix[i, j]; } } //三层循环,最外层i循环每循环一次是一次迭代 for (int i = 0; i < graph.Count; i++) { //内部两层循环是遍历路径数组和距离数组 for (int j = 0; j < graph.Count; j++) { for (int k = 0; k < graph.Count; k++) { //每次判断经过顶点i的距离是否比直接走要短,如果更短则更新距离数组和路径数组 if(distances[j,i] != int.MaxValue && distances[i,k] != int.MaxValue && distances[j,k] > distances[j,i] + distances[i, k]) { //更新最短距离 distances[j, k] = distances[j, i] + distances[i, k]; //更新路径,paths[j,k]保存的是从j到k顶点路径中j的下一个顶点 //这里更新的意思是将从j到i的最短路径走法中的j的下一个顶点下标赋值给从j到k的最短路径走法中j的下一个顶点下标 //因为从j到k的最短路径走法已经是先从j走到i再从i走到k,所以这样赋值即可 paths[j, k] = paths[j, i]; } } } } return distances; } } }
三.总结:
1.迪杰斯特拉算法:这个算法基于贪心的思想,节约了性能但是可靠性不高.这个算法使用了两层循环,时间复杂度O(n2).外层循环一次找到一个顶点到开始顶点的最短路径,内层循环两次,第一次找到没有找的顶点中到开始顶点的最短距离,第二次刷新所有没有找的顶点到开始顶点的最短路径.迪杰斯特拉算法如果想要将所有两个顶点间的最短路径和走法都找到,就需要将每一个顶点作为起点代入算法计算,那么找到任意两个顶点之间的最短路径的时间复杂度就是O(n3).
2.弗洛伊德算法:这个算法是基于动态规划的思想,在计算的过程中有明显的三层循环嵌套,显然时间复杂度是O(n3).最外层循环用于作迭代,内部两层循环用于遍历记录最短路径走法和距离值的二维数组,每次迭代都判断从j到k的走法是直接从j到k距离更短还是从j到i再从i到k走更短,如果更短就更新最短路径和走法(i,j,k是三层循环的临时变量).这个算法一次性将任意两个顶点之间的最短路径和走法都计算了出来,并且支持负权值.
3.如果是计算任意两个顶点之间的最短路径及走法,两种算法的时间复杂度相同,推荐弗洛伊德算法,因为它基于动态规划的思想,计算结果更为可靠;但是弗洛伊德算法只能一次性算出任意两个顶点的最短路径和距离,如果我们只需求某个顶点到其他顶点(或特定顶点)的最短路径走法和距离,显然使用迪杰斯特拉算法更合适.