算法之几个常见的经典最短路径算法

article/2025/8/25 5:40:48

1. Dijkstra算法

是解单源最短路径问题的贪心算法。

  • 有一向带权图 G =(V, E),包含右n个顶点,其中每条边的权是非负实数,定义数组 dist 为原点到G中各个顶点的距离,初始化为无穷大,
  • 维护一个顶点集合 S,初始时只包含源(即原点)
  • 每一步添加 v ∈ 𝑉 − S中具有到原点最小距离的顶点到 S 中,并更新dist数组
  • 经过n-1步,所有顶点都添加到S中,结束

更新数组的详情:

  • 假设当前S中只包含原点s,那么首先从s出发,第一次修改dis数组,此时,小于无穷大的代表和原点s直连(即连通),在dis数组中找到最小值对应的顶点v,加入到S中

  • 此时S中有{s,v}两个顶点,并且能保证的是dis数组中,s和v两个顶点的距离已经达到了最小值,因为选择v的时候,就是因为s到v的距离最短,反证:s到v如果不是最小,必然存在一条路径,s出发先经过非v的直连顶点,再可能经过其它顶点到s,显然,后者的距离不可能小于dis数组中的s到v的距离(也就是直连的距离)。所以能证明S中的顶点已经求出了最小值。

  • 当S中有{s,v}两个顶点时,可以第二次修改dis数组,修改的原则是:对于V-S中的顶点如w,第一次修改dis数组保留的是s和w直连的距离,当加入v后,s和w之间的距离就可以通过:计算出w到v的距离+v到s的距离,如果比之前dis数组中小,就更新,这一步的更新不能保证V-S中的顶点就已经求出了最短路径值。

  • 显然,上一步更新dis数组能求出w到s更短的路径,没有理由不去更新。此时,再考虑S-V中的顶点,在dis数组中,也就是寻找原点到自身和原点到之前加入的顶点s之外的值里面找最小值,假设为顶点v2到s的值。那么把v2加入到S中

  • 类似于2的分析,新加入的顶点v2到s的值此时已经达到了最小值。这个最小值可能是s-v-v2也有可能是s-v2。同样反证:假设s到v2之间不是前面说的那两种可能,而是s-u-…-v2,…表示没有或者其它的一个或多个顶点,显然,要先算出s-u的距离,s-u的距离,该怎么算呢?有以下几种可能,先回顾之前的v2怎么找到的呢?就是在dis数组中(除s和v)寻找最小值,此时的dis数组中顶点u对应的距离可能是s-u或者s-v-u,当然此时的s-u并未求出最小值,可以假设最终求出的最小值是s-w-…-u,要注意,只要经过非v和v2的点w(假设是直连于s),那么只是s-w的距离就大于s-v2,因为小于的话,选的就是w点加入S了,而不是顶点v2加入S;前面假设w直连s,如果非直连,总得先经过某个直连s的顶带q才能再连接其它顶点,那逻辑是一样的,s-q是大于s-v2的,不然就加入q了。至此可以说明s-v2确实已经找到了最小值。所以再次能证明S中的顶点已经求出了最小值。

  • 把v2加入S后,类似于3,第三次修改dis数组,然后寻找最小值,再加入,再寻找,循环这个步骤,到所有顶点都加入了S。同理,能证明S中的顶点确实都答到了最小值。

下面是个距离案例(这里盗用了上课讲的ppt )在这里插入图片描述在这里插入图片描述
再讨论以下复杂性分析:

  • 首先,复杂性分析需要依赖于特定的数据结构,不同的数据结构实现Dijkstra算法的复杂度是不同的。
  • 粗略的估计:用数组实现,最外层有n-1次的for循环,依赖于V的顶点数,假设为n,循环内部,更新一次dis数组,n的规模,这样就是O( n 2 n^2 n2)
  • 当然也可以用其它的数据结构实现,就是别的复杂度了,先不讨论。

上代码

	public final int MAX_ROUTE = Integer.MAX_VALUE;  // 代表无穷public int[] myDijstra(int[][] graph, int src){int n = graph.length;	// 顶点个数为nboolean[] used = new boolean[n];	// 是否加入S的标志位used[src] = true;int[] path = new int[n];	// 存储最短路径的数组System.arraycopy(graph[src],0,path,0,n);	// 第一次更新数组for (int i = 1; i < n; i++) {	// 需要n-1轮更新操作int tempMin = MAX_ROUTE;int tempIndex = -1;		// 记录即将加入S的顶点/* 寻找要加入的顶点 */for (int j = 0; j < n; j++) {if(!used[j] && path[j] < tempMin){tempMin = path[j];tempIndex = j;}}if(tempIndex == -1) break;	// 没找到到要加入的顶点,即所有距离都是无穷大,直接跳出循环used[tempIndex] = true;/* 松弛操作 */for (int j = 0; j < n; j++) {if( !used[j] && tempMin + graph[tempIndex][j] < path[j]){path[j] = tempMin + graph[tempIndex][j];}}}return path;}

2. Floyd算法

和Dijkstra相比,直接算出的就是图中任意两点的最短距离,另外就是路径的负权值情况也可以计算,不过要求图中不能存在负环。它的算法思想是动态规划。比如:要求j-k的最短路径值,假设存在一个中间节点i,那么最小值就是对i进行遍历,求出每一个i对应的j-i的值+i-k的值,取其中的最小值。
实际实现的时候,可以这样理解:

  • dis[j][k]表示j-k的距离,初始化为图G的描述的二维数组graph[][]
  • 第一轮松弛,比如依赖第一个中间结点v(就是图的顶点),之前的dis数组描述的都是任意两结点直接相连的情况,现在加入第一个结点v,更改所有的dis[j][k],更新规则就是:dis[j][i] + dis[i][k] < dis[j][k]则更新
  • 第二轮松弛,比如再加入一个中间结点比如v2,之前的dis数组描述的是任意两结点直接相连或者经过中间结点v,此时,仍然执行上述的跟新规则,此时的k和v2对应,为了便于理解,无外乎此轮更新包括下面的情况:j-k,j-v-k,j-v2-k,j-v-v2-k,j-v2-v-k五种情况,第一轮松弛求出了j-i和j-v-i之间的较小值,第二轮松弛求的是dis[j][i] + dis[i][k]是否小于第一轮求出的dis[j][k],小于号前面包括:上一轮求出的j到k值和v2到k的值之和,j到v2无非是j-v2或者j-v-v2,v2到k无非是v2-k或者v2-v-k,也就是第二轮松弛求出了j-v2-k,j-v-v2-k,j-v2-v-k中的最小值,和第一轮松弛求出的j-k,j-v-k中最小值比较,取当中小值做为新的dis[j][k],也就是目前第二轮求出了任意两个结点j和k经过或不经过或经过部分v和v2的当前的最短路径。
  • 当松弛到第n轮,任意两个结点都算出了经过或者不经过或经过部分所有结点的最短路径。
  • 复杂度的计算:经过要有3次for循环,O( n 3 n^3 n3)

上代码

	public int[][] MyFloyd(int[][] graph){int n = graph.length;int[][] dis = new int[n][n];System.arraycopy(graph,0,dis,0,n);for(int i = 0; i < n; i ++){for (int j = 0; j < n; j++) {for (int k = 0; k < n; k++) {if(dis[j][i] + dis[i][k] < dis[j][k]){dis[j][k] = dis[j][i] + dis[i][k];path[j][k] = i;}}}}return dis;}

3. Bellman-Ford 算法

遵循一个比一个强大的规则,这个算法可以处理负权重的环,只不过存在负权重环的话,就可能没有最短路径存在了,无限转圈…

Floyd算法和Dijkstra算法的思考角度差不多,都是从顶点的角度出发去松弛边,前者比后者充满了暴力般的简洁。而Bellman-Ford 并不是纯粹从顶点出发的思路,而是外层循环顶点,内层循环边,和Dijkstra相似的是,也是处理从单个顶点出发求到其它所有顶点的最短距离,具体思路如下:

  • 首先,定义一个dis数组,表示原点到其它结点的距离,初始化为无穷大,
  • 先考虑一轮循环,加入第一条边w(u,v),很明显,除非u或者v中有一个是原点,才能更新dis数组
  • 再加入一条边,考虑一种特殊情况,第一次加入的u是原点,这样,更新了原点到v的距离,这次的加入是w(v,v2),那么很明显,v2也能更新了,需要w(u,v)+w(v,v2),但是能直接更新吗?先需要和当前值比以下,取较小值才是合理的,当然只有两条边的情况下是和无穷在比较
  • 再加入第三条边,加入碰巧是w(u,v2),显然需要和第二步的值做比较才能决定更新
  • 不断的加入新的边w(x,y),统一的计算dis[x] + w(x,y) < dis[y]吗?小于则更新dis[y]
  • 全部的边都加入了,是不是求出了原点到其它结点的最短距离呢?有可能。分析第一轮循环做了哪些,加入一条边w(x,y),就算一下从原点到y的距离是不是先经过x再到y会更短,但是值得注意,上面的特殊情况是先加入和原点相邻的边,再加入和原点相邻的边的相邻的边,如果相反呢,那么判断原点到相邻的边距离是无穷大,明显就不会更新了,也就没有路径中存在两个边的情况了。
  • 所以需要再更新,之前的更新dis假设保留了只经过一条边结果,这一轮更新,便能把经过2条边的情况包含进去,因为每轮更新都只能再上一轮更新的基础上再加一条边
  • 所以考虑极端情况,最短路径包含n-1条边,那么就需要n-1轮更新
  • 另外,如果某一轮更新后,dis值没有变化,那么更新就可以停止,如果n-1轮更新完毕后dis还有变化,说明存在负环
  • 复杂度的话明显是顶点个数*边的个数

上代码

    public int[] MyBellmanFord(int[][] graph){int n = graph.length;int m = 0;int[] path = new int[n];Arrays.fill(path,MAX_ROUTE);List<List<Integer>> route = new ArrayList<>();/* 便于后面取边,不是核心代码 */for (int i = 0; i < n; i++) {for(int j = 0; j < n; j ++){if(graph[i][j] != MAX_ROUTE && graph[i][j] != 0) {route.add(new ArrayList<Integer>(Arrays.asList(i,j,graph[i][j])));m++;}}}path[0] = 0;	// 初始化原点到原点为0int[] watch = new int[n];	// 判断提前终止boolean negCircle = false;	// 负环标志位/* 核心算法 */for (int i = 0; i < n - 1; i++) {System.arraycopy(path,0,watch,0,n);for (int j = 0; j < m; j++) {List<Integer> list = route.get(j);if(path[list.get(0)] + list.get(2) < path[list.get(1)]){path[list.get(1)] = path[list.get(0)] + list.get(2);}}// 判断是否可以停止更新for (int k = 0; k < n; k++) {if(path[k] != watch[k]) {break;}if(k == n - 1)   negCircle = true;}if(negCircle)   break;}/* 判断负环的存在 */for (int i = 0; i < n - 1; i++) {for (int j = 0; j < m; j++) {List<Integer> list = route.get(j);if(path[list.get(0)] + list.get(2) < path[list.get(1)]){negCircle = true;}}}return path;}

http://chatgpt.dhexx.cn/article/TjysP3Ic.shtml

相关文章

最短路径的四种算法

最短路径四种算法 1234FloydDijkstraBellman-Ford队列优化的Bellman-Ford 一&#xff0c;只有四行的算法——Floyd-Warshall 假设求顶点 V i Vi Vi到 V j Vj Vj的最短路径。弗洛伊德算法依次找从 V i Vi Vi到 V j Vj Vj&#xff0c;中间经过结点序号不大于 0 0 0的最短路径&…

最短路径算法

1.最短路径算法分为单源最短路径算法和多源最短路径算法 &#xff08;a&#xff09;单源最短路径算法&#xff0c;可以计算出从起点到任意一个起点的最短路径。 例如&#xff1a;Dijkstra算法 &#xff0c;SPFA算法 &#xff08;b&#xff09;多源最短路径算法&#xff0c;可…

哈夫曼树及其应用

1、哈夫曼树的基本概念 ---- 哈夫曼&#xff08;Huffman&#xff09;树又称作最优二叉树&#xff0c;它是n个带权叶子结点构成的所有二叉树中&#xff0c;带权路径长度最小的二叉树。 ---- “路径”就是从树中的一个结点到另一个结点之间的分支构成的部分&#xff0c;而分支…

哈夫曼树的C语言实现

什么是哈夫曼树 当用 n 个结点&#xff08;都做叶子结点且都有各自的权值&#xff09;试图构建一棵树时&#xff0c;如果构建的这棵树的带权路径长度最小&#xff0c;称这棵树为“最优二叉树”&#xff0c;有时也叫“赫夫曼树”或者“哈夫曼树”。 在构建哈弗曼树时&#xff0…

哈夫曼树的构建及编码

哈夫曼树的构建及编码 文章目录 哈夫曼树的构建及编码一、什么是哈夫曼树二、什么是哈夫曼编码三、怎么建哈夫曼树、求哈夫曼编码四、为什么哈夫曼编码能实现压缩 声明&#xff1a;关于文件压缩&#xff0c;不是本文的重点&#xff0c;本文只说明并讨论哈夫曼树的构建和编码&am…

如何构建一棵哈夫曼树

给一个数列{10,7,8,3,26,5,1},要求转成为一棵哈夫曼树。 分析思路以及图解&#xff1a; 第一步&#xff1a;将数列进行排序&#xff0c;按从小到大的顺序。最终结果为{1,3,5,7,8,10,26}&#xff0c;根据每个数值创建结点&#xff0c;组成结点数组 第二步&#xff1a;取出权值最…

哈夫曼树 (100分)哈夫曼树

4-1 哈夫曼树 (100分)哈夫曼树 第一行输入一个数n&#xff0c;表示叶结点的个数。 需要用这些叶结点生成哈夫曼树&#xff0c;根据哈夫曼树的概念&#xff0c;这些结点有权值&#xff0c;即weight&#xff0c;题目需要输出哈夫曼树的带权路径长度&#xff08;WPL&#xff09;。…

哈夫曼树的编码和解码

哈夫曼树的作用&#xff1a;在数据通信中&#xff0c;需要将传送的文字转换成二进制的字符串&#xff0c;用0&#xff0c;1码的不同排列来表示字符。例如&#xff0c;需传送的报文为“AFTER DATA EAR ARE ART AREA”&#xff0c;这里用到的字符集为“A&#xff0c;E&#xff0c…

哈夫曼树与哈夫曼编码

哈夫曼树 给定n个权值作为n个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若带权路径长度达到最小&#xff0c;称这样的二叉树为最优二叉树&#xff0c;也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树&#xff0c;权值较大的结点离根较近。 树节点间的边…

【例题】哈夫曼树

【例1】由五个分别带权值为9&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;14的叶子结点构成的一棵哈夫曼树&#xff0c;该树的带权路径长度为_______________。 A、60 B、66 C、67 D、50 答案&#xff1a;C 解析&#xff1a; 关键点在于要学会如何构造哈夫曼树 已知有5…

哈夫曼树以及哈夫曼算法

目录 一、哈夫曼树的定义 二、哈夫曼树的特点 三、哈夫曼算法(构造哈夫曼树的方法) 四、哈夫曼树的构造过程 五、哈夫曼树构造算法的实现 一、哈夫曼树的定义 1、哈夫曼树:最优树即带权路径长度(WPL)最短的树 “带权路径长度最短”是在"度相同”的树中比较而得的结果…

哈夫曼树的绘制

数据结构之哈夫曼树绘制 本人还是一个年轻的程序猿(还是个学生)&#xff0c;请大家多多指教&#xff01; 哈夫曼树 已知权重绘制哈夫曼树 开始我的表演 Step 1. 已知权重&#xff1a;2&#xff0c;3&#xff0c;3&#xff0c;4&#xff0c;7&#xff0c;6 Step 2. 选取其中…

哈夫曼树 哈夫曼编码

哈夫曼树 哈夫曼树的定义&#xff1a;设二叉树具有 n 个带权值的叶节点&#xff0c;那么从根节点到各个叶节点的路径长度与相应叶节点权值的乘积的和&#xff0c;叫作二叉树的带权路径长度 WPL (Weighted Path Length)。具有最小带权路径长度的二叉树称为哈夫曼树 (Huffman Tr…

哈夫曼树(Huffman Tree)

定义 哈夫曼树又称最优二叉树&#xff0c;是一种带权路径长度最短的二叉树。所谓树的带权路径长度&#xff0c;就是树中所有的叶结点的权值乘上其到根结点的路径长度&#xff08;若根结点为0层&#xff0c;叶结点到根结点的路径长度为叶结点的层数&#xff09;。树的路径长度是…

哈夫曼树详解

一、哈夫曼树的介绍 Huffman Tree&#xff0c;中文名是哈夫曼树或霍夫曼树&#xff0c;它是最优二叉树。 定义&#xff1a;给定n个权值作为n个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若树的带权路径长度达到最小&#xff0c;则这棵树被称为哈夫曼树。 这个定义里面涉…

哈夫曼树(Huffmantree)

1.基本概念 哈夫曼树又称为最优树&#xff0c;是一类带权路径长度最短的树。 一些概念的定义&#xff1a; &#xff08;1&#xff09;路径&#xff1a;树的两个结点之间的连线称为路径。 &#xff08;2&#xff09;路径长度&#xff1a;路径上的分支数目称作路径长度。若规定…

哈夫曼树详解及其应用(哈夫曼编码)

一&#xff0c;哈夫曼树的基本概念 路径&#xff1a;从树中一个结点到另一个结点之间的分支构成这两个结点间的路径 结点的路径长度&#xff1a;两结点之间路径上的分支数 树的路径长度&#xff1a;从树根到每一个结点的路径长度之和&#xff0e;记作&#xff1a;&#xff3…

哈夫曼树编码的实现+图解(含全部代码)

目录 哈夫曼树的基本概念 ------------哈夫曼树的构造方法 ------------------------哈夫曼编码 ------------------------------------全部代码 哈夫曼树的基本概念 哈夫曼树通常以二叉树的形式出现&#xff0c;所以也称最优二叉树&#xff0c;是一类带权路径长度最短的树…

哈夫曼树(C语言实现)

文章目录 哈夫曼树的基本概念哈夫曼树的构建构建思路代码实现 哈夫曼编码的生成编码生成思路代码实现 完整代码展示以及代码测试 哈夫曼树的基本概念 在认识哈夫曼树之前&#xff0c;你必须知道以下几个基本术语&#xff1a; 1、什么是路径&#xff1f; 在一棵树中&#xff0c…

打开VS2010提示:产品密钥框

打开VS2010提示&#xff1a;产品密钥框&#xff0c;如下图&#xff1a; …