0引言
最短路径研究是图论中的一个重要领域,它研究的是在一个图中找到两个节点之间最短路径的问题。这个问题在现实生活中有着广泛的应用,包括交通路线规划、网络通信、物流配送等领域。最短路径问题其研究意义在于提高交通效率、优化资源利用以及改善社会生活。随着交通和通信技术的发展,对于寻找最短路径的需求越来越迫切。
本文对最短路径研究,旨在于研究最短路径的相关问题,进而推动社会的发展和进步
1设计内容
1.1 最短路径问题:
最短路径问题的数学定义:设G=(V, E)为一个有向图或无向图,V为节点集合,E为边集合。设d(s, v)表示从起始节点s到节点v的最短路径的长度,最短路径问题可以表示为:找到一条路径P = (v1, v2, …, vn),其中v1=s为起始节点,vn=v为目标节点,使得路径上各边的权重之和最小,即min(∑w(vi, vi+1))[1]。
图1.1最短路径的概述图示意
形式化描述:最短路径问题可以描述为给定一个有向图G=(V,E),其中V表示图中的顶点集合,E表示图中的边集合。每条边e∈E都有一个非负的权重值w(e),代表从起点到终点的距离或代价。最短路径问题的目标是找到从给定的起点s∈V到目标终点t∈V的最短路径,即路径上所有边的权重之和最小。
最短路径问题在现实生活中有广泛的应用场景和实列。在导航系统中,最短路径算法可以用于计算从起点到目的地的最短路径,帮助用户规划出行路线。例如,谷歌地图和百度地图就使用了最短路径算法来计算最优路线;最短路径在物流配送的方面也有应用,物流配送过程中,需要找到从仓库到目的地的最短路径,以减少运输成本和时间。最短路径算法可以帮助物流公司规划出最优的配送路线。实际上,最短路径问题在各个领域中都有广泛的应用,如交通规划、通信网络、资源分配等。通过求解最短路径问题,可以提高效率、降低成本和优化规划。
1.2 实现Dijkstra算法,求解有向带权图中单源最短路径问题
1.2.1 Dijkstra算法的基本思想和主要步骤
基本思想:Dijkstra算法是最著名的单起点最短路径算法。其基本思想是使用贪心策略,通过逐步选择最短路径节点,并更新相邻节点的最短路径长度,最终得到从源点到其他所有节点的最短路径[2]。
主要步骤:Dijkstra算法按照从给定起点到图中顶点的距离,顺序求出最短的路径。首先,它求出从起点到最接近起点的顶点之间的最短路径,然后求出第二近的,以此类推。推而广之,在第i次迭代开始以前,该算法已经确定了i-1条连接起点和离起点最近顶点之间的最短路径。这些顶点、起点和从起点到顶点的路径上的边, 构成了给定图的一棵子树Tᵢ。因为所有边的权重都是非负数,可以从与T₁的顶点相邻的顶点中找到下一个和起点最接近的顶点。和Tᵢ的顶点相邻的顶点集合称为“边缘顶点”,以它们为候选对象,Dijkstra算法可以从中选出下一个最接近起点的顶点。(实际上,所有其他的顶点也可以看作边缘顶点,只是它们以权重无限大的边和树中顶点相连。)为了确定第i个最接近的顶点,对于每一个边缘顶点u,该算法求出它到最近的树中顶点v的距离(根据边(v,u)的权重)以及从起点到v的最短路径长度dv的和,再从中选出具有最小和的顶点。Dijkstra算法的洞察力在于它意识到只要比较这些特定的路径就足够了[3]。
1.2.2 基于Dijkstra算法求解有向带权图中单源最短路径问题的代码示例
#include<iostream>
#include<iomanip>
#include<string>
using namespace std;
#define INFINITY 65535
#define MAX_VERTEX_NUM 10
typedef struct MGraph
{
string vexs[10];
int arcs[10][10];
int vexnum,arcnum;
}MGraph;
int LocateVex(MGraph G,string u)
{
for(int i=0;i<G.vexnum;i++)
if(G.vexs[i]==u)
return i;
return -1;
}
void CreateDN(MGraph &G)
{
string v1,v2;
int w;
int i,j,k;
cout<<"请输入顶点数和边数:";
cin>>G.vexnum>>G.arcnum;
cout<<"请输入顶点:";
for(i=0;i<G.vexnum;i++)
cin>>G.vexs[i];
for(i=0;i<G.vexnum;i++)
{
for(j=0;j<G.vexnum;j++)
{
G.arcs[i][j]=INFINITY;
}
}
cout<<"请输入边和权值:"<<endl;
for(k=0;k<G.arcnum;k++)
{
cin>>v1>>v2>>w;
i=LocateVex(G,v1);
j=LocateVex(G,v2);
G.arcs[i][j]=w;
}
}
void ShortestPath_DIJ(MGraph G,int v0,int p[][MAX_VERTEX_NUM],int D[])
{
int v,w,i,j,min;
bool final[10];
for(v=0;v<G.vexnum;v++)
{
final[v]=false;
D[v]=G.arcs[v0][v];
for(w=0;w<G.vexnum;w++)
p[v][w]=-1;
if(D[v]<INFINITY)
{
p[v][0]=v0;
p[v][1]=v;
}
}
D[v0]=0;
final[v0]=true;
for(i=1;i<G.vexnum;i++)
{
min=INFINITY;
for(w=0;w<G.vexnum;w++)
if(!final[w]&&D[w]<min)
{
v=w;
min=D[w];
}
final[v]=true;
for(w=0;w<G.vexnum;w++)
{
if(!final[w]&&min<INFINITY&&G.arcs[v][w]<INFINITY&&(min+G.arcs[v][w])<D[w])
{
D[w]=min+G.arcs[v][w];
for(j=0;j<G.vexnum;j++)
{
p[w][j]=p[v][j];
if(p[w][j]==-1)
{
p[w][j]=w;
break;
}
}
}
}
}
}
int main()
{
int i,j;
MGraph g;
CreateDN(g);
int p[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
int D[MAX_VERTEX_NUM];
ShortestPath_DIJ(g,0,p,D);
cout<<"最短路径数组p[i][j]如下:"<<endl;
for(i=0;i<g.vexnum;i++)
{
for(j=0;j<g.vexnum;j++)
cout<<setw(3)<<p[i][j]<<" ";
cout<<endl;
}
cout<<g.vexs[0]<<"到各顶点的最短路径及长度为:"<<endl;
for(i=0;i<g.vexnum;i++)
{
if(i!=0&&D[i]!=INFINITY)
{
cout<<g.vexs[0]<<"-"<<g.vexs[i]<<"的最短路径长度为:"<<D[i];
cout<<"最短路径为:";
for(j=0;j<g.vexnum;j++)
{
if(p[i][j]>-1)
cout<<g.vexs[p[i][j]]<<" ";
}
cout<<endl;
}
else if(D[i]==INFINITY)
cout<<g.vexs[0]<<"-"<<g.vexs[i]<<":"<<"不可达"<<endl;
}
}
1.2.3 Dijkstra算法求解有向带权图中单源最短路径问题的图形化界面显示输入的图和输出的结果
若设有带权图如下,并求以v0为源点的最短路径问题则有
1.2.4 Dijkstra算法求解有向带权图中单源最短路径问题的优点、缺点
优点:经过代码研究发现,算法保证能够找到图中所有节点到起始节点的最短路径。对于单源最短路径问题,Dijkstra算法是一种高效的解决方案。算法使用了贪心策略,每次选择当前距离起始节点最近的节点进行扩展,这种策略保证了得到的路径是最短的。
缺点:研究发现算法对于图中存在负权边的情况不适用。当图中存在负权边时,Dijkstra算法可能无法正确计算出最短路径。需要使用其他算法算法对于大规模图的计算效率较低。当图中节点数量较大时,Dijkstra算法的时间复杂度较高,可能导致计算时间过长。算法只能计算单源最短路径,即从起始节点到其他节点的最短路径。对于其他类型的最短路径问题,如多源最短路径问题或任意两点之间的最短路径问题,Dijkstra算法无法直接应用。
1.3 实现贪心算法,求解无向带权图中单源最短路径问题
1.3.1 贪心算法,求解无向带权图中单源最短路径问题的基本思想
基本思想:创建一个集合S用于存放已经确定最短路径的顶点,初始化为空集。
创建一个数组dist用于存放源点到每个顶点的最短距离,初始化为无穷大。将源点的最短距离设为0。重复以下步骤,直到集合S包含所有顶点: a. 从集合V-S中选择一个顶点u,使得dist[u]最小。 b. 将顶点u加入集合S。 c. 对于顶点u的所有邻接顶点v,更新其最短距离:如果dist[u] + weight(u, v) < dist[v],则更新dist[v]为dist[u] + weight(u, v)。返回数组dist,即为从源点到每个顶点的最短距离。
1.3.2 基于贪心算法求解无向带权图中单源最短路径问题的代码示例
#include <stdio.h>
#define V 20
#define INFINITY 65535
typedef struct {
int vexs[V];
int arcs[V][V];
int vexnum, arcnum;
}MGraph;
int LocateVex(MGraph * G, int v) {
int i = 0;
for (; i < G->vexnum; i++) {
if (G->vexs[i] == v) {
break;
}
}
if (i > G->vexnum) {
printf("no such vertex.\n");
return -1;
}
return i;
}
void CreateDG(MGraph *G) {
printf("输入图的顶点数和边数:");
scanf("%d %d", &(G->vexnum), &(G->arcnum));
printf("输入各个顶点:");
for (int i = 0; i < G->vexnum; i++) {
scanf("%d", &(G->vexs[i]));
}
for (int i = 0; i < G->vexnum; i++) {
for (int j = 0; j < G->vexnum; j++) {
G->arcs[i][j] = INFINITY;
}
}
printf("输入各个边的数据:\n");
for (int i = 0; i < G->arcnum; i++) {
int v1, v2, w;
scanf("%d %d %d", &v1, &v2, &w);
int n = LocateVex(G, v1);
int m = LocateVex(G, v2);
if (m == -1 || n == -1) {
return;
}
G->arcs[n][m] = w;
G->arcs[m][n] = w;
}
}
void Dijkstra_minTree(MGraph G, int v0, int p[V], int D[V]) {
int final[V];
for (int v = 0; v < G.vexnum; v++) {
final[v] = 0;
D[v] = G.arcs[v0][v];
p[v] = 0;
}
D[v0] = 0;
final[v0] = 1;
int k = 0;
for (int i = 0; i < G.vexnum; i++) {
int min = INFINITY;
for (int w = 0; w < G.vexnum; w++) {
if (!final[w]) {
if (D[w] < min) {
k = w;
min = D[w];
}
}
}
final[k] = 1;
for (int w = 0; w < G.vexnum; w++) {
if (!final[w] && (min + G.arcs[k][w] < D[w])) {
D[w] = min + G.arcs[k][w];
p[w] = k;
}
}
}
}
int main() {
MGraph G;
CreateDG(&G);
int P[V] = { 0 };
int D[V] = { 0 };
Dijkstra_minTree(G, 0, P, D);
printf("最短路径为:\n");
for (int i = 1; i < G.vexnum; i++) {
printf("%d - %d的最短路径中的顶点有:", i, 0);
printf(" %d-", i);
int j = i;
while (P[j] != 0) {
printf("%d-", P[j]);
j = P[j];
}
printf("0\n");
}
printf("源点到各顶点的最短路径长度为:\n");
for (int i = 1; i < G.vexnum; i++) {
printf("%d - %d : %d \n", G.vexs[0], G.vexs[i], D[i]);
}
return 0;
}
1.3.3 基于贪心算法求解无向带权图中单源最短路径问题的图形化界面显示输入的图和输出的结果
1.3.4基于贪心算法求解无向带权图中单源最短路径问题的优点和缺点
优点: 高效性:贪心算法可以在较短的时间内找到最短路径,特别是当图的大小较小时; 简单易用:贪心算法的原理简单,易于理解和实现,不需要过多的计算和比较。
缺点:适用性有限:贪心算法并不适用于所有的最短路径问题,特别是当图的大小较大时,其求解结果可能不是最优解,需要使用其他更复杂的算法;缺乏全局优化能力:贪心算法只考虑了当前节点到目标节点的距离,而未考虑其他节点的因素,因此无法获得全局最优解; 无法处理负权边情况:贪心算法无法处理负权边情况,因为负权边的存在可能导致无法找到最短路径。
1.4实现动态规划算法,求解有向带权图中所有顶点之间的最短路径问题(基于弗洛伊德算法)
1.4.1 实现动态规划算法,求解有向带权图中所有顶点之间的最短路径问题的基本思想与步骤(基于弗洛伊德算法)
基本思想:弗洛伊德算法既适用于无向加权图,也适用于有向加权图。使用弗洛伊德算法查找最短路径时,只允许环路的权值为负数,其它路径的权值必须为非负数,否则算法执行过程会出错。任意两端点i、j,如果存在中间端点k,满足 i->k 和 k -> j 的权值之和小于 i -> j 则用 i ->k ->j 来替换 i -> j 的路径,否则不变,其中 i -> k 可以是逻辑上的边也可以是实际上的边。怎么理解逻辑上的边呢?就是说 i 和 k没有直接边 ,因此 i -> k 的权值理论上是无穷大。 i->k 中间有个端点a,即a和i、k都有边,满足 i -> a 和 a -> k的权值之和是有限值肯定小于无穷大,因此i -> k 就可以用 i -> a -> k来替换了,因此 i ->j 实际路径就说 i -> a -> k ->j。看到这里我想大家应该都明白了,弗洛伊德算法就是利用中间端点不断找到两个端点间的最短路径。
图:1.2弗洛伊德算法的内在思想示意
实现步骤:
初始化图
初始化权值表和最短路径表,其中最短路径表中默认直连;权值表不存在表的默认为无穷大
以每个端点做为中间端点,循环遍历去更新两个端点的最短路径及权值
遍历打印所有端点到其他端点的最短路径
1.4.2 实现动态规划算法,求解有向带权图中所有顶点之间的最短路径问题的代码示例(基于弗洛伊德算法)
#include<bits/stdc++.h>
#define V 4
#define INF 65535
int P[V][V] = { 0 };
void printMatrix(int matrix[][V]);
void printPath(int i, int j);
void floydWarshall(int graph[][V]);
int main() {
int graph[V][V] = { {0, 3, INF, 5},
{2, 0, INF, 4},
{INF, 1, 0, INF},
{INF, INF, 2, 0} };
floydWarshall(graph);
}
void printPath(int i, int j)
{
int k = P[i][j];
if (k == 0)
return;
printPath(i, k);
printf("%d-", k + 1);
printPath(k, j);
}
void printMatrix(int graph[][V]) {
int i, j;
for (i = 0; i < V; i++) {
for (j = 0; j < V; j++) {
if (j == i) {
continue;
}
printf("%d - %d: 最短路径为:", i + 1, j + 1);
if (graph[i][j] == INF)
printf("%s\n", "INF");
else {
printf("%d", graph[i][j]);
printf(",依次经过:%d-", i + 1);
printPath(i, j);
printf("%d\n", j + 1);
}
}
}
}
void floydWarshall(int graph[][V]) {
int i, j, k;
for (k = 0; k < V; k++) {
for (i = 0; i < V; i++) {
for (j = 0; j < V; j++) {
if (graph[i][k] + graph[k][j] < graph[i][j]) {
graph[i][j] = graph[i][k] + graph[k][j];
P[i][j] = k;
}
}
}
}
printMatrix(graph);
}
1.4.3 实现动态规划算法,求解有向带权图中所有顶点之间的最短路径问题的图形化界面显示输入的图和输出的结果(基于弗洛伊德算法)
1.4.1 实现动态规划算法,求解有向带权图中所有顶点之间的最短路径问题,采取弗洛伊德算法的优点、缺点
优点: 算法简单:弗洛伊德算法是一种基于双重循环的算法,其思想简单易懂,代码实现相对简单; 可以计算出任意两个节点之间的最短路径:弗洛伊德算法可以处理有向带权图中的任意两个节点之间的最短路径问题,这是许多路径规划算法所不具备的优势; 在稠密地图中,其效率较高:由于弗洛根据图中的节点数和边的权重,因此在实际应用中,其效率较高。
缺点:时间复杂度较高:在有向带权图中,节点数和边的权重都会影响算法的时间复杂度。因此,该算法不适合处理大量数据;对于稀疏图,该算法会生成稀疏矩阵,可能存在存储空间浪费的问题。
2 比较三种算法的时间复杂度和空间复杂度
2.1迪杰斯特拉算法的时间复杂度、空间复杂度
当使用优先队列实现Dijkstra算法时,其时间复杂度为O((V + E)logV),其中V是顶点数量,E是边数量。在每次迭代中,需要将当前节点周围的边放入优先队列,并从队列中取出最小权重的边。因此,对于每个节点,需要进行一次插入和一次删除最小值的操作,而这些操作的时间复杂度与优先队列的实现方法有关,一般为logV。总共需要迭代V次,所以时间复杂度为O((V + E)logV)。Dijkstra算法的空间复杂度主要取决于存储图信息的数据结构以及辅助数据结构的使用。通常情况下,需要使用一个额外的数组(或哈希表)来记录每个顶点的最短路径长度,以及一个优先队列来存储待处理的顶点及其对应的最短路径长度。因此,空间复杂度为O(V)。
2.2 贪心算法的时间复杂度、空间复杂度
时间复杂度:O(V^2)
空间复杂度:O(V^2)
2.3弗洛伊德算法的时间复杂度、空间复杂度
弗洛伊德算法的时间复杂度为O(V^3),其中V是图中顶点的数量。算法的核心是三层嵌套循环,以遍历所有顶点之间的组合。对于每一对顶点,算法都会通过考虑是否经过其他顶点来更新最短路径长度。因此,算法需要执行V次外循环,每次外循环又要对所有顶点进行一次内循环,所以总的时间复杂度为O(V^3)。弗洛伊德算法的空间复杂度为O(V^2),其中V是图中顶点的数量。在算法中,需要使用一个二维数组来存储任意两个顶点之间的最短路径长度。这个二维数组的大小为V×V。因此,所需的额外空间与顶点数量的平方成正比,即O(V^2)。
3 展望
经过本次研究发现,最短路径问题一直是图论和算法设计领域的一个重要研究方向。虽然已经存在一些高效的算法用于解决最短路径问题,但仍然存在一些挑战和待解决的问题。目前最短路径算法已经相对成熟,但对于大规模图或动态图来说,算法的时间复杂度和空间复杂度仍然是一个挑战。未来我们可以致力于使用更加高效的数据结构、并行计算和分布式算法等技术来加速最短路径的计算过程,为建设社会主义现代化强国的中国梦添加计科人的梦想。
4 参考文献
CSDN「Unique-You」.(2022).algorithm
祝国明.基于Dijkstra的多源点最短路径求解算法的设计与分析[J].电脑知识与技术,2021,17(16):177-178.
Anany Levitin.Introduction to The Design and Analysis of Algorithms Third Edition,2015
5 致谢
我们要衷心感谢我们的《数据结构与算法课程设计》这门课程的授课老师徐晶老师,他给予耐心的解答,对我们的课程设计起了很大的帮助。在整个课程设计中,徐老师对我们的课程设计提出了宝贵的建议,并给予了无私的支持和激励,并给与了我们的研究的另一个方向,让我们对算法有了更深刻的理解。衷心感谢。
同时还要感谢与我们共同完成本次课程设计的队友们,他们的技术、工具、以及算法思想,使我们能够顺利进行数据分析和报告编写。