目录
一、引言
根据上周所学内容:
如何求任意两点之间的最短路径呢?通过之前的学习,我们知道通过深度或广度优先搜察可以求出两点之间的最短路径。所以进行n^2 遍深度或广度优先搜索,即对每两个点都进行一次深度或广度优先搜索,便可以求得任意两点之间的最短路径。可是还有没有别的方法呢?
下面就给大家介绍一下这周所要学习的三个算法,弗洛伊德算法、迪杰斯特拉算法和贝尔曼算法,不过在了解这个算法之前,我们还要了解一下动态规划(dp)问题。
动态规划(Dynamic Programming,简称DP),是一种通过分解复杂问题为简单子问题,并存储子问题的解以避免重复计算,从而解决最优化问题的算法。它广泛应用于计算机科学、数学和经济学等领域。以下是对DP算法的分析:
1.算法思想
DP算法的核心思想是将问题分解为重叠的子问题,并利用子问题的解来构建原问题的解。这种分解过程通常涉及递归和状态转移方程,以逐步推导出最终解。
2. 算法解题步骤
使用DP算法解决问题通常包括以下步骤:
- 定义状态:确定描述问题的状态变量,这些变量将用于表示子问题的解。
- 状态转移方程:建立状态转移方程,描述如何从子问题的解推导出原问题的解。这通常涉及对当前状态和之前状态的组合进行操作。
- 初始化:为状态变量设置初始值或边界条件。
- 计算最优解:通过迭代或递归来计算状态变量的值,直到达到原问题的解。
- 构造解:使用存储的状态值来构造原问题的最优解。
3. 优缺点分析
- 优点:
- 能够有效解决具有重叠子问题和最优子结构性质的问题。
- 通过存储子问题的解,避免了重复计算,提高了效率。
- 能够处理大规模问题,因为它将问题分解为较小的子问题。
- 缺点:
- 可能需要大量的存储空间来存储子问题的解。
- 对于不具有最优子结构性质或无后效性的问题,DP算法可能不适用。
- 设计状态转移方程和确定状态变量可能需要较高的技巧和经验。
4. 应用
DP算法在多个领域都有广泛应用,如背包问题、最短路径问题、编辑距离问题、最优二叉搜索树问题等。此外,在自动驾驶决策系统中,DP算法也扮演着重要角色,用于根据代价进行决策,找到最优路径。
总之,DP算法是一种强大的优化算法,通过合理利用子问题的解来构建原问题的解,有效提高了解决问题的效率。然而,在应用DP算法时,需要仔细分析问题是否满足其前提条件,并合理设计状态变量和状态转移方程。
二、弗洛伊德算法
1.算法简介
多源最短路径问题是图论中的一个经典问题,其目标是求出给定图中所有顶点对之间的最短路径。这意味着,对于图中的任意两个顶点,我们都需要找到它们之间的最短路径。这种问题在实际应用中具有广泛的用途,如路由算法、网络拓扑分析、交通规划等。解决多源最短路径问题的一个常用算法是Floyd-Warshall算法,也被称为Floyd算法。
弗洛伊德算法(Floyd's Algorithm),又称佛洛伊德算法或弗洛依德算法,是一种用于查找图中所有顶点对之间的最短路径的动态规划算法。该算法由英国计算机科学家David Floyd于1962年提出,并发表在ACM Communications杂志上。
弗洛伊德算法的核心思想是,通过逐步构建一个距离矩阵来找出所有顶点对之间的最短路径。这个距离矩阵最初被初始化为图的邻接矩阵,然后通过考虑所有可能的顶点对和中间顶点来迭代地更新这个距离矩阵。在每次迭代中,算法会检查通过某个顶点作为中间点,是否可以使从起点到终点的路径变得更短。如果可以,则更新距离矩阵中的相应值。
弗洛伊德算法的一个主要优点是,它能够正确处理带有负权重的边,只要这些负权重边不构成负权重回路即可。这使得它在处理某些实际问题时具有很大的灵活性。然而,需要注意的是,弗洛伊德算法的时间复杂度为O(V^3),其中V是图中顶点的数量。因此,在处理大型图时,该算法可能会显得不够高效。
简而言之我们做一个总结:弗洛伊德算法(Floyd's Algorithm)是一个经典的动态规划算法,用于在带权重的图中找出所有顶点对之间的最短路径。
2.示例
上图中有4个城市8条公路,数字表示路的长短,现在我们需要求任意两个城市之间的最短路径。
3.弗洛伊德算法步骤
首先需要一个二维数组a来存储图的信息,比如1城市到2城市距离为2,则设a[1][2]的值为2,如果两个城市无法到达,例如2城市到1城市,则设a[2][1]的值为无穷大。
如图所示:
根据生活的经验,如果要让任意两点(例如从顶点a到顶点b)之间的路程变短,只能引入第三个点(顶点k),并通过这个顶点k中转即 a→k-→b,才可能缩短原来从顶点a到顶点b的路程。甚至有时候不只通过一个点,而是经过两个点或者更多点中转会更短,即 a→k1→h2→b或者 a→k1→k2→……ki……→b。比如上图中从 4 号城市到3 号城市(4→3)的路程c[4][3]原本是12,如果只通过1号城市中转(4→1→3),路程将缩短为 11 (a[4][1]+a[1][3]=5+6=11)。所以如果同时通过1号和2号两个城市中转的话,从4号城市到3号城市的路程会进一步缩短为10(a[4][1]+a[1][2]+a[2][3]=5+2+3=10)。通过这个例子,我们发现每个顶点都有可能使得另外两个顶点之间的路程变短。
当任意两点之间不允许经过第三个点时,这些城市之间的最短路程就是初始路程。
void Floyd()//弗洛伊德核心代码
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。(hahaha引入第三者当小三不愧是你)。
其实这是一种“动态规划”的思想,关于这个思想下面给出这个算法的完整代码:
4.完整代码题解
测试样例
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
完整代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
#define inf 0x3f3f3f
int a[105][105];
int n,m;
void Floyd()//弗洛伊德核心代码
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
int main()
{
cin>>n>>m;//表示N个顶点,M条边。
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) a[i][j]=0;
else a[i][j]=inf;
int u,v,w;
while(m--)
{
cin>>u>>v>>w;
a[u][v]=w;
}
Floyd();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<a[i][j];
}
cout<<endl;
}
return 0;
}
5.代码分析
引入头文件
#include <iostream>
#include <cstdio>
#include <algorithm>
这里引入了三个头文件,其中<iostream>
用于输入输出,<cstdio>
虽然在这里没有用到,但通常用于C风格的输入输出,<algorithm>
虽然在这里也没有用到,但通常用于算法操作。
定义宏和变量
using namespace std;
#define inf 0x3f3f3f // 定义一个宏,表示无穷大
int a[105][105]; // 存储图的邻接矩阵
int n,m; // n表示顶点数,m表示边数
这里定义了一个宏inf
表示无穷大,以及一个二维数组a
来存储图的邻接矩阵。n
和m
分别表示顶点数和边数。
Floyd-Warshall算法实现
void Floyd()//弗洛伊德核心代码
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(a[i][j]>a[i][k]+a[k][j])
a[i][j]=a[i][k]+a[k][j];
}
这是Floyd-Warshall算法的核心实现。它通过三层循环来遍历所有可能的顶点对和中间点,更新从i
到j
的最短路径。如果通过中间点k
的路径更短,则更新a[i][j]
。
主函数
int main()
{
cin>>n>>m; // 输入顶点数和边数
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(i==j) a[i][j]=0; // 对角线上的值为0,表示到自身的距离为0
else a[i][j]=inf; // 其他位置初始化为无穷大
int u,v,w;
while(m--) // 遍历每一条边
{
cin>>u>>v>>w; // 输入边的两个顶点和权值
a[u][v]=w; // 设置边的权值
}
Floyd(); // 调用Floyd算法
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cout<<a[i][j]; // 输出所有顶点对之间的最短路径
}
cout<<endl;
}
return 0;
}
主函数中首先读取顶点数和边数,然后初始化邻接矩阵。接着,读取每一条边的信息并设置邻接矩阵的对应位置。之后调用Floyd()
函数进行最短路径的计算。最后,输出所有顶点对之间的最短路径。
三、迪杰斯特拉算法
1.算法简介
单源最短路径问题,顾名思义,就是从一个指定的源点出发,寻找到达图中其他所有顶点的最短路径。这种问题在图论和网络分析中非常常见,具有广泛的应用价值,如导航系统中的路线规划、通信网络的路由选择等。
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)在1959年提出的一种单源最短路径算法。该算法主要用于解决有权图(即图中的边具有权重)中从一个顶点到其余各顶点的最短路径问题。
迪杰斯特拉算法的核心思想是采用贪心策略。算法从起始点开始,逐步向外扩展,每次从未访问的顶点中选择距离起始点最近的顶点,并更新其邻居顶点的距离。这个过程一直持续到所有顶点都被访问为止。在算法执行过程中,会维护一个距离列表,用于记录从起始点到每个顶点的当前最短距离估计。同时,还会使用一个集合来记录已经访问过的顶点,以避免重复访问。
迪杰斯特拉算法的一个重要前提是图中的边权重必须是非负的。这是因为算法依赖于贪心策略,通过逐步逼近的方式找到最短路径。如果存在负权重的边,那么贪心策略可能会失效,导致算法无法得出正确的最短路径。
迪杰斯特拉算法的时间复杂度主要取决于图的规模和边的数量。在稀疏图中,算法的性能通常较好。然而,在稠密图中,由于需要遍历较多的边,算法的时间复杂度可能会较高。
简而言之我们做一个总结:迪杰斯特拉算法(D