最短路径算法及应用

article/2025/8/25 3:02:50

乘汽车旅行的人总希望找出到目的地的尽可能的短的行程。如果有一张地图并在图上标出每对十字路口之间的距离,如何找出这一最短行程?

  一种可能的方法就是枚举出所有路径,并计算出每条路径的长度,然后选择最短的一条。那么我们很容易看到,即使不考虑包含回路的路径,依然存在数以百万计的行车路线,而其中绝大多数是不值得考虑的。

  在这一章中,我们将阐明如何有效地解决这类问题。在最短路径问题中,给出的是一有向加权图G=(V,E,W),其中V为顶点集,E为有向边集,W为边上的权集。最短路径问题研究的问题主要有:单源最短路径问题、与所有顶点对之间的最短路径问题。

  一、 单源最短路径问题

  所谓单源最短路径问题是指:已知图G=(V,E),我们希望找出从某给定的源结点S∈V到V中的每个结点的最短路径。

  首先,我们可以发现有这样一个事实:如果P是G中从vs到vj的最短路,vi是P中的一个点,那么,从vs沿P到vi的路是从vs到vi的最短路。

  (一)Dijkstra算法

  对于图G,如果所有Wij≥0的情形下,目前公认的最好的方法是由Dijkstra于1959年提出来的。
  例1 已知如下图所示的单行线交通网,每弧旁的数字表示通过这条单行线所需要的费用,现在某人要从v1出发,通过这个交通网到v8去,求使总费用最小的旅行路线。

          500){this.resized=true;this.style.width=500;}">

  Dijkstra方法的基本思想是从vs出发,逐步地向外探寻最短路。执行过程中,与每个点对应,记录下一个数(称为这个点的标号),它或者表示从vs到该点的最短路的权(称为P标号)、或者是从vs到该点的最短路的权的上界(称为T标号),方法的每一步是去修改T标号,并且把某一个具T标号的改变为具P标号的点,从而使G中具P标号的顶点数多一个,这样至多经过n-1(n为图G的顶点数)步,就可以求出从vs到各点的最短路。

  在叙述Dijkstra方法的具体步骤之前,以例1为例说明一下这个方法的基本思想。例1中,s=1。因为所有Wij≥0,故有d(v1, v1)=0。这时,v1是具P标号的点。现在考察从v1发出的三条弧,(v1, v2), (v1, v3)和(v1, v4)。如果某人从v1出发沿(v1, v2)到达v2,这时需要d(v1, v1)+w12=6单位的费用;如果他从v1出发沿(v1, v3)到达v3,这时需要d(v1, v1)+w13=3单位的费用;类似地,若沿(v1, v4)到达v4,这时需要d(v1, v1)+w14=1单位的费用。因为min{ d(v1, v1)+w12,d(v1, v1)+w13,d(v1, v1)+w14}= d(v1, v1)+w14=1,可以断言,他从v1到v4所需要的最小费用必定是1单位,即从v1到v4的最短路是(v1, v4),d(v1, v4)=1。这是因为从v1到v4的任一条路P,如果不是(v1, v4),则必是先从v1沿(v1, v2)到达v2,或者沿(v1, v3)到达v3。但如上所说,这时他已需要6单位或3单位的费用 ,不管他如何再从v2或从v3到达v4,所需要的总费用都不会比1小(因为所有wij≥0)。因而推知d(v1, v4)=1,这样就可以使v4变成具P标号的点。现在考察从v1及v4指向其余点的弧,由上已知,从v1出发,分别沿(v1, v2)、(v1, v3)到达v2, v3,需要的费用分别为6与3,而从v4出发沿(v4, v6)到达v6所需的费用是d(v1, v4)+w46=1+10=11单位。因min{ d(v1, v1)+w12,d(v1, v1)+w13,d(v1, v4)+w46}= d(v1, v1)+w13=3。基于同样的理由可以断言,从v1到v3的最短路是(v1, v3),d(v1, v3)=3。这样又可以使点v3变成具P标号的点,如此重复这个过程,可以求出从v1到任一点的最短路。

  在下述的Dijstra方法具体步骤中,用P,T分别表示某个点的P标号、T标号,si表示第i步时,具P标号点的集合。为了在求出从vs到各点的距离的同时,也求出从Vs到各点的最短路,给每个点v以一个λ值,算法终止时λ(v)=m,表示在Vs到v的最短路上,v的前一个点是Vm;如果λ(v)=∞,表示图G中不含从Vs到v的路;λ(Vs)=0。
Dijstra方法的具体步骤:

  {初始化}
  i=0
  S0={Vs},P(Vs)=0 λ(Vs)=0
  对每一个v<>Vs,令T(v)=+ ∞,λ(v)=+ ∞,
  k=s
  {开始}
  ①如果Si=V,算法终止,这时,每个v∈Si,d(Vs,v)=P(v);否则转入②
  ②考察每个使(Vk,vj)∈E且vj Si的点vj。
  如果T(vj)>P(vk)+wkj,则把T(vj)修改为P(vk)+wkj,把λ(vj)修改为k。
  ③令500){this.resized=true;this.style.width=500;}" align=middle>
  如果500){this.resized=true;this.style.width=500;}" align=middle>,则把500){this.resized=true;this.style.width=500;}" align=middle>的标号变为P标号500){this.resized=true;this.style.width=500;}" align=middle>,令500){this.resized=true;this.style.width=500;}" align=middle> ,k=ji,i=i+1,转①,否则终止,这时对每一个v∈Si,d(vs,v)=P(v),而对每一个500){this.resized=true;this.style.width=500;}" align=middle> 。

  算法实现:

  1、 数据结构
  * 用邻接矩阵表示图G
  * 用一维数组D[I]存放从源点到I点的最短路径长度或其上界。(上面算法中的P标号与T标号实际上存放着源点到某点的最短路径长度或其上界,因此我们可以统一用D数组来表示)。
  * 用一维数组P,其中P[I]记录到I点的最短路径中前一个顶点的序号。
{$R+}

  2、源程序

  500){this.resized=true;this.style.width=500;}" resized="true">
  500){this.resized=true;this.style.width=500;}">
 500){this.resized=true;this.style.width=500;}" resized="true">
 500){this.resized=true;this.style.width=500;}">
 500){this.resized=true;this.style.width=500;}">

  (二)Bellman-Ford算法

  在单源最短路径问题的某些实例中,可能存在权为负的边。如果图G=(V,E)不包含从源s可达的负权回路,则对所有v∈V,最短路径的权定义d(s,v)依然正确,即使它是一个负值也是如此。但如果存在一从s可达的负回路,最短路径的权的定义就不能成立。S到该回路上的结点就不存在最短路径。当有向图中出现负权时,则Dijkstra算法失效。当不存在源s可达的负回路时,我们可用Bellman-Ford算法实现。

  下面我们介绍有向图中,存在具有负权的弧时,求最短路的方法。
  为了方便起见,不妨设从任一点vi到任一点vj都有一条弧(如果在Gk ,(vi,vj)不存在,则添加(vi,vj)且令wij=+∝)。

  显然,从vs到vj的最短路总是从vs出发,沿着一条路到某个点vi,再沿(vi,vj)到vj的(这里vi可以是vs本身),由本章开始时介绍的一个结论可知,从vs到vi的这条路必定是从vs到vi的最短路,所以d(vs,vi)必满足如下方程:
        500){this.resized=true;this.style.width=500;}" align=middle>

  为了求得这个方程的解500){this.resized=true;this.style.width=500;}" align=middle> (这里P为图G中的顶点数目),可用如下递推公式:
  开始时,令
        500){this.resized=true;this.style.width=500;}" align=middle>
  对t=2,3,...,
        500){this.resized=true;this.style.width=500;}" align=middle>
         
  若进行到某一步,例如第k步时,对所有j=1,2,...,p,有:
        500){this.resized=true;this.style.width=500;}" align=middle>
500){this.resized=true;this.style.width=500;}" align=middle> 即为vs到各点的最短路的权。

  不难证明:
  (1)如果G是不含回路的赋权有向图,那么,从vs到任一个点的最短路必可取为初等路,从而最多包含P-2个中间点;
  (2)上述递推公式中的 500){this.resized=true;this.style.width=500;}" align=middle>是在至多包含t-1个中间点的限制条件下,从vs到vj的最短路的权。

  由(1)(2)可知:当G中不含负回路时,上述算法最多经过p-1次迭代必定收敛,即对所有的j=1,2,...,P,均有500){this.resized=true;this.style.width=500;}" align=middle> ,从而求出从vs到各个顶点的最短路的权。
  如果经过p-1次迭代,存在某个j,使500){this.resized=true;this.style.width=500;}" align=middle> ,则说明G中包含有负回路。显然,这时从vs到vj的路是没有下界的。
  根据以上分析,Bellman-Ford算法可描述为:

  500){this.resized=true;this.style.width=500;}" align=middle resized="true">

  算法实现

  1、 数据结构(同Dijkstra算法,略)
  2、源程序
  
  500){this.resized=true;this.style.width=500;}" align=middle> 
   500){this.resized=true;this.style.width=500;}" align=middle resized="true">
   500){this.resized=true;this.style.width=500;}" align=middle>
   500){this.resized=true;this.style.width=500;}" align=middle>

  (三)有向无回路图中的单源最短路径

  若图G是一个无回路有向图,求图G的单源最短路径问题可以在O(V+E)时间内计算出单源最短路径。在有向无回路图中最短路径总是存在的,这是因为即使图中有权为负的边,也不可能存在权为负的回路。

  算法开始时先对有向无回路图进行拓朴排序,以便获得结点的线性序列。如果从结点u到结点v存在一通路,则在拓扑序列中u先于v。在拓扑排序过程中我们仅对结点执行一趟操作。当对每个结点进行处理时,从该结点出发的所有边也被松驰。

  算法描述如下:

 500){this.resized=true;this.style.width=500;}" align=middle>

  二、每对结点间的最短路径

  我们要讨论找出图中每对结点间最短路径问题。这个问题在实践中常常会出现。例如,对一公路图,要造表说明其上的每对城市间的距离时就可能出现这种问题。

  对于有向图G(V,E,W),要求每对结点间的最短路径,我们可以把单源最短路径算法运行|V|次来解决,每次依序把图中的每个结点作为源点。如果所有边的权为非负,可以采用Dijkstra算法,算法的运行时间为O(V3);如果允许有负权边的存在,我们必须对每个结点运行一次速度较慢的Bellman-Ford算法,其中运行时间为O(V2E),对稠密图则为O(V4)。

  下面我们介绍一种动态程序设计方案来解决可以存在负权边但无负回路的有向图G=(V,E),每对结点间的最短路径问题,所产生的算法称为Floyd-Warshall算法,其运行时间为O(V3)。

  (一)最短路径的结构

  在Floyd-Warshall算法中,考察的是一条最短路径上的"中间"结点,其中某条简单路径P=<V1,V2,...,Vj>的中间结点是P中除V1和Vj以外的任何结点,即任何属于集合{V2,V3...,Vj-1}的结点。

  该算法主要基于下列观察。设G的结点为V={1,2,...,n},并对某个k考察其结点子集{1,2,...,k}。对任意一对结点i,j∈V,考察从i到j且其中间结点皆属于集合{1,2,...,k}的所有路径,设P是其中一条最小权路径(因为我们假定G中不包含负权回路,所以P是简单路径)。Floyd-Warshall算法利用了路径P与从i到j间的最短路径(所有中间结点皆属于集合{1,2,...,k-1}之间的联系。这一联系取决于k是否是路径p的一个中间结点。

  如果k是路径p的中间结点,由如下图所示,我们把p分解为p1(i500){this.resized=true;this.style.width=500;}" align=absMiddle>k),p2(k500){this.resized=true;this.style.width=500;}" align=absMiddle>j)。由前面可知,p1是从i到k的一条最短路径,且其所有中间结构均属于集合{1,2,...,k}。
    500){this.resized=true;this.style.width=500;}" align=absMiddle>

  事实上,结点k不是路径p1的中间结点,所以p1是从i到k的一条最短路径,且满足所有中间结点均属于{1,2,...,k-1}。类似地,p2是从k到j的一条最短路径,且其中间结点皆属于集合{1,2,...,k-1}。

  (二)解决每对结点间最短路径问题的一种递归方案

  基于上述观察,我们将给出定义最短路径估计的一个递归公式。设500){this.resized=true;this.style.width=500;}" align=absMiddle> 为从结点i到结点j且满足所有中间均属于集合{1,2,...,k}的一条最短路径的权。当k=0时,从结点i到结点j的路径中根本不存在中间结点,因此它至多包含一条边,则有500){this.resized=true;this.style.width=500;}" align=absMiddle> ,递归定义由下式给出:

    500){this.resized=true;this.style.width=500;}" align=absMiddle>

  矩阵给出了最后解,对所有的i,j∈V成立――因为其所有中间点皆属于{1,2,...,n}。500){this.resized=true;this.style.width=500;}" align=absMiddle>

  (三)自底向上计算最短路径的权

  基于以上给出的递归定义,我们可以运用下面自底向上过程按k值的递增顺序计算500){this.resized=true;this.style.width=500;}" align=absMiddle> 。过程的输入是n*n的矩阵W。其返回值关于最短路径的权的矩阵。
  500){this.resized=true;this.style.width=500;}">

  (四) 建立最短路径

  有时除了需要计算出一个带权的有向图中从任一顶点到其他顶点之间的最短路径的长度外,还要确定相应的最短路径。为此,可以设置一个n*n的矩阵P,当k是在Floyd算法中,使Dij达到最小值时,就置P[i,j]=k。约定P[i,j]=0,表示从顶点i顶点j的最短路径就是从i到j的边。下面是修改后的Floyd算法,其中包含了计算矩阵矩阵P的步骤。

  500){this.resized=true;this.style.width=500;}" resized="true">
  Var
  Begin
   K:=P[i,j];
   If k=0 then return;
   Path(i,k);
   Print(k);
   Path(k,j);
  End;

  (五)源程序

  500){this.resized=true;this.style.width=500;}" resized="true">
  500){this.resized=true;this.style.width=500;}">
  500){this.resized=true;this.style.width=500;}">

  三、应用举例

  1、设备更新问题。某企业使用一台设备,在每年年初,企业领导部门就要决定是购置新的,还是继续使用旧的。若购置新设备,就要支付一定的购置费用;若继续使用旧设备,则需支付一定的维修费用。现在的问题是如何制定一个几年之内的设备更新计划,使得总的支付费用最少。
例如,我们一个五年之内要更新某种设备的计划,若已知该种设备在各年年初的价格为:

第一年
第二年
第三年
第四年
第五年
11
11
12
12
13

  还已知使用不同时间(年)的设备所需要的维修费用为:

使用年数
0-1
1-2
2-3
3-4
4-5
维修费用
5
6
8
11
18

  可供选择的设备更新方案显然很多的,例如,每年都购置一台新设备,则其购置费用为11+11+12+12+13=59,而每年支付的维修费用为5,五年合计为25,于是五年总的支付费用为59+25=84。

  双如决定在第一、三、五年各购进一台,这个方案的设备购置费为11+12+13=36,维修费为5+6+5+6+5=27。五年总的支付费用为63。

  这个例子中一种最佳方案为在第1年、第3年各购置一台新设备,五年总费用为53。
  编写一个程序,输入n年年初设备的价格与使用不同时间(年)的设备所需要的维修费用,为该企业领导部门确定一个方案使得在n年内为这台机器支付的总费用最少。

  2、工程安排
  一项工程由多道工序组成, 按照施工过程的要求,这些工序之间,客观上有一个必须遵守的先后关系。 对那些紧接在已知工序前的工序叫紧前工序,把在已知工序后边紧接的工序叫后项工序, 只有已知工序的所有紧前工序都完成,已知工序才能开始施工。例如某工程的工序表如下:

序代号
紧前工序
完成时间
序代号
紧前工序
完成时间
A
-
6
F
C
2
B
-
2
G
D
3
C
A
3
H
B,E
4
D
A
5
I
H
2
E
A
3
J
F,G,I
2

  一天中可以同时进行若干道工序。

  编程要求:
    求工程最少在几天内完成,并找出一种工程施工安排方案。

  输入数据
    输入文件名由键盘输入,该文件
    第一行为总工序数N;
    第二行至第N+1行为工序表的内容(依次是工序编号1到N的工序代号、 紧前工序、工序完成时间。

  输出数据
    输出文件名为OUTPUT.DAT,该文件有K+1行;
    第一行为工程施工最短天数K
    第二行至第K+1行为每一天施工的工序号

  参考文件
    参考输入文件example.txt(上表)
    参考输出文件answer.txt
    500){this.resized=true;this.style.width=500;}">


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

相关文章

关于最短路径算法的理解

“最短路径算法:Dijkstra算法&#xff0c;Bellman-Ford算法&#xff0c;Floyd算法和SPFA算法等。​从某顶点出发&#xff0c;沿图的边到达另一顶点所经过的路径中&#xff0c;各边上权值之和最小的一条路径叫做最短路径。” 我们解决最短路径问题&#xff0c;常用的是Dijkstra…

最短路径算法-----Dijkstra迪杰斯特拉算法

最近巩固一下算法&#xff0c;提高自己内力&#xff0c;网上看到查看到这篇介绍很详细的《Dijkstra迪杰斯特拉算法》&#xff0c;在这里转载记录一下。 1 前言 本章介绍迪杰斯特拉算法。和以往一样&#xff0c;本文会先对迪杰斯特拉算法的理论论知识进行介绍&#xff0c;然后给…

Dijkstra 最短路径算法 Python 实现

原文链接 问题描述 使用 Dijkstra 算法求图中的任意顶点到其它顶点的最短路径&#xff08;求出需要经过那些点以及最短距离&#xff09;。 以下图为例&#xff1a; 算法思想 可以使用二维数组来存储顶点之间边的关系 首先需要用一个一维数组 dis 来存储 初始顶点到其余各个顶…

神经网络最短路径算法,最短路径算法的原理

节约里程法求解最短路问题 你只要记住2点之间直线最短。节约里程法是用来解决运输车辆数目不确定的问题的最有名的启发式算法。1、节约里程法优化过程分为并行方式和串行方式两种。 核心思想是依次将运输问题中的两个回路合并为一个回路&#xff0c;每次使合并后的总运输距离…

最短路径算法及Python实现

最短路径问题 在图论中&#xff0c;最短路径问题是指在一个有向或无向的加权图中找到从一个起点到一个终点的最短路径。这个问题是计算机科学中的一个经典问题&#xff0c;也是许多实际问题的基础&#xff0c;例如路线规划、通信网络设计和交通流量优化等。在这个问题中&#…

图论:图的四种最短路径算法

目录&#xff1a; 1.DFS&#xff08;单源最短路径算法&#xff09; 例题1&#xff1a; DFS题目分析&#xff1a; 代码DFS&#xff1a; 2.Floyed&#xff08;时间复杂度On^3&#xff09; 1.应用场景&#xff1a; 2.解析算法&#xff1a; 核心代码1&#xff1a; 我的笔…

图的五种最短路径算法

本文总结了图的几种最短路径算法的实现:深度或广度优先搜索算法,费罗伊德算法,迪杰斯特拉算法,Bellman-Ford 算法。 1)深度或广度优先搜索算法(解决单源最短路径) 从起点开始访问所有深度遍历路径或广度优先路径,则到达终点节点的路径有多条,取其中路径权值最短的一…

最短路径算法——Dijkstra算法——python3实现

本文参考来自数据结构与算法分析 java语言描述。 文章目录 问题描述问题分析实现过程如何使用数据变化表代码实现优先队列中的堆排序使用set代替优先队列得到最短路径 负权边算法改进&#xff08;若为无圈图&#xff09; 问题描述 现有一个有向赋权图。如下图所示&#xff1a;…

最短路径算法的编程与实现 C语言

一 、目的&#xff1a; 1.掌握最短路径算法的基本原理及编程实现&#xff1b; 二 、环境&#xff1a; operating system version&#xff1a;Win11 CPU instruction set: x64 Integrated Development Environment&#xff1a;Viusal Studio 2022 三 、内容&#xff1a; 1…

图的四种最短路径算法

本文总结了图的几种最短路径算法的实现&#xff1a;深度或广度优先搜索算法&#xff0c;弗洛伊德算法&#xff0c;迪杰斯特拉算法&#xff0c;Bellman-Ford算法 1&#xff09;&#xff0c;深度或广度优先搜索算法&#xff08;解决单源最短路径&#xff09; 从起始结点开始访问所…

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

目录 1. Dijkstra算法2. Floyd算法3. Bellman-Ford 算法 1. Dijkstra算法 是解单源最短路径问题的贪心算法。 有一向带权图 G (V, E)&#xff0c;包含右n个顶点&#xff0c;其中每条边的权是非负实数&#xff0c;定义数组 dist 为原点到G中各个顶点的距离&#xff0c;初始化为…

最短路径的四种算法

最短路径四种算法 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;权值较大的结点离根较近。 树节点间的边…