解决最短路径问题多种方法合集(迪杰斯特拉算法、弗洛伊德算法、Dijkstra算法)

一、迪杰斯特拉算法

1、概述

本实验的目的是掌握迪杰斯特拉算法的原理和实现方法,用C语言编写程序,求解给定赋权图中任意两个顶点之间的最短路径。最短路径问题是图论中的一个经典问题,它在许多领域有着广泛的应用,如交通网络、通信网络、路由算法等。迪杰斯特拉算法是最短路径问题的一种有效解决方案,它能够在有向或无向的赋权图中,找出从源点到其他所有顶点的最短路径。本实验旨在通过编程实践,加深对迪杰斯特拉算法的理解和掌握,提高图论和数据结构的综合运用能力。

2、题目分析

1.定义图的邻接矩阵表示法,用二维数组存储图中各顶点之间的权值,用无穷大表示不相邻的顶点。
2.定义边的结构体,包含边的起点、终点和权重。
3.定义邻接表中表对应的链表的顶点结构体,包含该边所指向的顶点的位置、该边的权和指向下一条弧的指针。
4.定义邻接表中表的顶点结构体,包含顶点信息和指向第一条依附该顶点的弧。
5.定义图的结构体,包含顶点数组和边的数量。
6.定义初始化图、插入顶点、插入边、判断是否存在边等基本操作函数。
7.定义迪杰斯特拉算法函数,用三个数组S、D和P分别记录已求出最短路径的顶点集合、到各个顶点的最短路径长度和各个顶点到源点的前驱。
8.定义创建图、广度遍历、深度遍历和调用迪杰斯特拉算法等功能实现函数。
9.输入图中顶点个数、邻接矩阵和源点、终点。
10.调用创建图函数,根据输入初始化图的数据结构。
11.调用迪杰斯特拉算法函数,求出源点到终点的最短路径长度和前驱数组。
12.根据前驱数组,递归输出最短路径上经过的顶点。
13.输出最短路径长度。

3、实现代码

#include <stdio.h>
#include <stdlib.h>

#define max 2000

typedef struct MGraph{
    int matrix[max][max];//邻接矩阵
    int vernum; //节点数目
}MGraph;

int P[max];

void InitMGraph(MGraph *G){
    
    scanf("%d",&G->vernum);
    int i,j;
    for(i = 0;i < G->vernum;i++)
    {
        for(j = 0;j < G->vernum;j++)
        {
            scanf("%d",&G->matrix[i][j]);
        }
    }
}

void FindShort(MGraph *G,int *D,int vi){
    int i,j,s[max];
    for(i = 0;i < G->vernum;i++)
    {
        if(G->matrix[vi][i] != 10000)
        {
            P[i] = vi;
        }
        D[i] = G->matrix[vi][i];
        s[i] = 0;
    }
    s[vi] = 1;       //源点

    //采用迪杰斯特拉算法
    for(i = 0;i < G->vernum;i++)
    {
        //找到距离最近的点
        int min,n;
        min = 10000;
        n = -1;
        for(j = 0;j < G->vernum;j++)
        {
            if(s[j]==0 && D[j] < min)
            {
                min = D[j];
                n = j;
            }
        }
        if(n == -1)
        {
            break;
        }
        else
        {
            s[n] = 1;
        }
        
        //更新得距离
        for(j = 0;j < G->vernum;j++)
        {
            if(s[j] != 1 && D[j] > D[n] + G->matrix[n][j])
            {
                D[j] = D[n] + G->matrix[n][j];
                P[j] = n;
            }
        }
    }

}

void Print(MGraph *G,int vi,int vj){
    int i,mid=vj,th[max];
    for(i=0;;i++)
    {
        if(mid == vi)
        {
            th[i] = vi;
            break;
        }
        else
        {
            th[i] = mid;
            mid = P[mid];
        }
    }
    for(;i>=0;i--)
    {
        printf("%d\n",th[i]);
    }
}
int main(){
    MGraph *g;
    int i,*dist,vi,vj;

    g = (MGraph*)malloc(sizeof(MGraph));
    g->vernum = 0;
    InitMGraph(g);
    dist = (int*)malloc(g->vernum * sizeof(int));
    scanf("%d %d",&vi,&vj);
    
    FindShort(g,dist,vi);

    Print(g,vi,vj);

    return 0;
}

4、收获和总结

通过本实验,我学习了迪杰斯特拉算法的基本思想和步骤,掌握了用C语言实现该算法的方法。我了解了邻接矩阵和邻接表两种图的存储方式,以及如何用结构体定义图中顶点和边的属性。我还复习了广度遍历和深度遍历两种图的遍历方法,以及如何用递归输出最短路径。
迪杰斯特拉算法是一种典型的最短路径算法,它利用贪心策略,从源点开始,每次选择距离源点最近且未加入结果集合的顶点,并更新其他顶点到源点的距离。该算法适用于没有负权边的赋权图 。
本实验让我体会到了迪杰斯特拉算法的优雅和高效,它能够在多项式时间内求解最短路径问题,而不需要枚举所有可能的路径。它也展示了贪心策略在算法设计中的威力,它能够在每一步做出局部最优的选择,从而达到全局最优的结果。同时,我也认识到了迪杰斯特拉算法的局限性,它不能处理存在负权边或者负环路的图,因为这样会导致距离数组无法收敛或者出现错误。对于这种情况,需要使用其他算法来求解最短路径问题,如贝尔曼-福特算法或者弗洛伊德算法等。
本实验还有一些可以改进和拓展的地方,例如:
优化迪杰斯特拉算法的时间复杂度,可以使用优先队列或者斐波那契堆等数据结构来存储和更新距离数组。
扩展迪杰斯特拉算法的应用范围,可以考虑有向图、负权边或者多源点的情况,以及如何处理图中的环路或者不连通的顶点。
对比迪杰斯特拉算法和其他最短路径算法,如弗洛伊德算法、贝尔曼-福特算法等,分析各自的优缺点和适用场景。
根据实验结果,接受或拒绝假设,并给出理由。我们接受我们的假设,即如果给定一个无向图的邻接矩阵,那么可以用迪杰斯特拉算法计算出从源节点到其他所有节点的最短路径长度。这是因为我们通过编写和运行一个用C语言编写的程序,成功地实现了迪杰斯特拉算法,并得到了正确和符合预期的结果。
总结实验的主要发现和收获。通过本实验,我们发现了迪杰斯特拉算法是一种有效和高效的求解无向图中单源最短路径问题的算法,它可以应用于各种无向图中。我们也收获了编写和运行C语言程序的能力和经验,以及分析和解释实验结果的技巧和方法。
提出实验的局限性和改进方向。本实验还存在一些局限性和不足之处,例如:我们没有考虑有负权边或负环存在的情况,这可能会导致迪杰斯特拉算法失效或出错;我们没有比较不同数据结构对迪杰斯特拉算法效率的影响,这可能会影响程序运行时间和空间复杂度;我们没有测试不同规模和类型的图对迪杰斯特拉算法性能的影响,这可能会导致不同的结果和分析。为了改进实验,我们可以在未来尝试以下几个方面:我们可以使用其他求解最短路径问题的算法(如Floyd算法或Bellman-Ford算法),并与迪杰斯特拉算法进行比较和分析;我们可以使用不同的数据结构(如堆、栈、队列等),并观察它们对迪杰斯特拉算法效率的影响;我们可以使用不同规模和类型的图(如稀疏图、稠密图、随机图等),并探究它们对迪杰斯特拉算法性能的影响。

二、弗洛伊德算法

1、概述

本实验的目的是用弗洛伊德算法求赋权图的两点间的最短路径长度。
弗洛伊德算法是一种动态规划算法,它可以在O(n^3)的时间复杂度内求出任意两点间的最短路径长度,其中n是图的顶点数。
弗洛伊德算法的核心思想是利用中间顶点来更新两点间的最短路径长度,即如果a到b的最短路径长度大于a到m加上m到b的路径长度,那么就用后者替换前者,并记录下中间顶点m。

2、题目分析

1.本实验使用C语言编写了一个Graph结构体,用来存储图的顶点数、邻接矩阵和路径矩阵。
2.邻接矩阵arc[i][j]表示从顶点i到顶点j的路径长度,如果不存在这样的路径,则用一个很大的数INF表示。
3.路径矩阵path[i][j]表示从顶点i到顶点j的最短路径上的中间顶点,如果没有中间顶点,则用-1表示。
4.实验中首先从标准输入读入图的顶点数和邻接矩阵,然后调用floyd函数执行弗洛伊德算法,更新邻接矩阵和路径矩阵。
5.floyd函数中使用了三重循环,分别遍历所有可能的中间顶点m、起始顶点a和终止顶点b,如果发现可以通过m更新a到b的最短路径长度,则进行更新,并记录下m为path[a][b]。
6.最后从标准输入读入若干组查询,每组查询包含两个顶点a和b,然后输出a到b的最短路径长度。

3、实现代码

//010用弗洛伊德算法求赋权图的两点间的最短路径长度
#include <bits/stdc++.h>
#define MAXSIZE 105
#define INF 10000
using namespace std;
 
typedef struct Graph
{
    int vnum;
    int arc[MAXSIZE][MAXSIZE];
    int path[MAXSIZE][MAXSIZE];
} Graph;
 
void init_Graph(Graph *G)
{
    scanf("%d", &G->vnum);
    for (int i = 0; i < G->vnum; i++)
    {
        for (int j = 0; j < G->vnum; j++)
        {
            scanf("%d", &G->arc[i][j]);
            G->path[i][j] = -1;
        }
    }
}
 
void floyd(Graph *G)
{
    for (int m = 0; m < G->vnum; m++)
        for (int a = 0; a < G->vnum; a++)
            for (int b = 0; b < G->vnum; b++)
            {
                if (G->arc[a][b] > G->arc[a][m] + G->arc[m][b])
                {
                    G->arc[a][b] = G->arc[a][m] + G->arc[m][b];
                    G->path[a][b] = m;
                }
            }
}
 
void print_result(Graph *G)
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%d\n", G->arc[a][b]);
    }
}
 
int main()
{
    Graph G;
    init_Graph(&G);
    floyd(&G);
    print_result(&G);
    return 0;
}
 
/*
4
0 2 10 10000
2 0 7 3
10 7 0 6
10000 3 6 0
2
0 2
3 0
*/

4、收获和总结

通过本实验,我学习了弗洛伊德算法的原理和实现方法,掌握了动态规划算法的基本思想和技巧。
我也了解了如何使用C++语言编写结构体和函数,以及如何使用数组和循环处理图论问题。
本实验还提高了我的编程能力和调试能力,让我更加熟悉C++语言的语法和特性。
本实验还有一些可以改进和拓展的地方,例如:
①可以输出最短路径上的具体顶点序列,而不仅仅是最短路径长度。这样可以让读者更清楚地了解最短路径的具体形式和含义。
②可以使用更高效的数据结构和算法来存储和处理图,例如邻接表和堆优化迪杰斯特拉算法等。这样可以减少空间复杂度和时间复杂度,提高程序的性能和效率。
③可以对输入数据进行合法性检查和异常处理,避免程序出错或崩溃。例如检查输入数据是否符合格式要求,是否存在负权回路或者孤立顶点等情况,如果有则给出相应的提示或者处理方法。
本实验还有一些可以改进和拓展的地方,例如:
优化弗洛伊德算法的时间复杂度,可以使用矩阵乘法或者快速傅里叶变换等方法来加速计算。
扩展弗洛伊德算法的应用范围,可以考虑求解最小生成树、最大流、最小费用流等图论问题,以及如何处理稀疏图或者稠密图的情况。
对比弗洛伊德算法和其他最短路径算法,如迪杰斯特拉算法、贝尔曼-福特算法、约翰逊算法等,分析各自的优缺点和适用场景。

三、Dijkstra算法

1、概述

本实验的目的是通过编写和运行一个用C语言编写的程序,来理解和掌握Dijkstra算法的原理和步骤。Dijkstra算法是一种求解有向图中单源最短路径问题的算法,它可以有效地找出从一个节点到其他所有节点的最短路径长度。
假设:如果给定一个有向图的邻接矩阵,那么可以用Dijkstra算法计算出从源节点到其他所有节点的最短路径长度。我们假设给定的有向图是连通的,即任意两个节点之间都存在一条路径。

2、过程分析

Ⅰ、分析结果是否符合假设和预期,说明原因。根据结果表格和图形,我们可以看出,程序正确地输出了从源节点到其他所有节点的最短路径长度。这些结果与我们的假设一致,说明Dijkstra算法是有效的。

Ⅱ、解释Dijkstra算法的原理和步骤,以及它是如何在程序中实现的。Dijkstra算法的基本思想是:从源节点开始,每次选择一个当前未完成最短路径计算的节点,并更新它到其他未完成节点的距离。重复这个过程直到所有节点都完成最短路径计算。Dijkstra算法在程序中主要通过以下几个函数实现:
InitPath函数:用于初始化路径数组,将源节点的状态设为DONE,长度设为0;将其他节点的状态设为DOING或UNDO(根据是否有边相连),长度设为源节点到该节点的权重或无穷大。
IsFinished函数:用于判断是否完成所有节点的最短路径计算,如果有任何一个节点的状态是DOING,说明还没有完成,返回0;否则返回1。
SearchMinPath函数:用于寻找当前最小路径的节点,即从源节点到该节点距离最短且状态为DOING的节点,并返回其编号。
Updata函数:用于更新路径数组,首先调用SearchMinPath函数找到当前最小路径的节点,并将其状态设为DONE;然后遍历其他节点,如果从源节点经过当前最小路径的节点到该节点的距离小于已知的最短路径长度,并且该节点的状态不是DONE,就更新该节点的最短路径长度,并将其状态设为DOING(如果原来是UNDO)。
Dijkstra函数:用于执行Dijkstra算法,当还有未完成的节点时,循环调用Updata函数更新路径数组。
Printer函数:用于打印结果,遍历所有节点,打印从源节点到该节点的最短路径长度。

Ⅲ、比较Dijkstra算法和其他求最短路径的算法(如Floyd算法)的优缺点。Dijkstra算法和Floyd算法都是求解有向图中最短路径问题的经典算法,它们各有优缺点。
Dijkstra算法的优点是:它可以快速地找出从一个源节点到其他所有节点的最短路径长度,而不需要计算所有节点之间的距离;它可以适用于有向图和无向图,只要边的权重都是非负的;它可以利用优先队列等数据结构进行优化,提高效率。
Dijkstra算法的缺点是:它不能处理有负权边的图,因为这会导致负环的出现,使得最短路径不存在或无法确定;它不能处理动态变化的图,因为每次图中有边或权重发生变化时,都需要重新运行算法;它不能同时求出从多个源节点到其他所有节点的最短路径长度,因为每次只能指定一个源节点。
Floyd算法的优点是:它可以求出任意两个节点之间的最短路径长度,而不需要指定源节点;它可以处理有负权边的图,只要没有负环;它可以处理动态变化的图,因为每次图中有边或权重发生变化时,只需要更新相关的距离即可;它可以同时求出从多个源节点到其他所有节点的最短路径长度,因为它会计算所有节点之间的距离。
Floyd算法的缺点是:它需要存储所有节点之间的距离,占用较大的空间;它需要进行三重循环,耗费较多的时间;它不能适用于无向图,因为这会导致重复计算。

3、实现代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 10000

typedef int VexType;
typedef int  AdjType;

typedef struct
{
    VexType  vexs[MAX]; /* 顶点信息 */
    AdjType  arcs[MAX][MAX]; /* 邻接矩阵信息 */
    int   arcCount, vexCount;   /* 图的顶点个数 */
} Graph, *PGraph;

typedef struct
{
    VexType vertex; /* 顶点信息 */
    AdjType length; /* 最短路径长度 */
    int  prevex;        /* 从v0到达vi(i=1,2,…n-1)的最短路径上vi的前驱顶点 */
} Path;

int CreateGraph (PGraph pG,int n) {
    int  i, j;
    AdjType w;
    n = pG->vexCount;
    for (i = 0; i < pG->vexCount; i++)  /* 初始化邻接矩阵 */
        for (j = 0; j<pG->vexCount; j++)
        {
            /* 初始假设所有顶点都互不邻接 */
            pG->arcs[i][j]  = MAX;
        }
    for ( i=0;i<n;i++)
    {
     for(j=0;j<n;j++)
        {
        scanf("%d",&w);
        if(w!=MAX)
        pG->arcs[i][j]=w;
        }
    }
    return 1;
}

void Dijkstra (PGraph graph, Path dist[])
{
    int  i, j, minvex;
    AdjType  min;
    dist[0].length=0;  dist[0].prevex=0;  dist[0].vertex=graph->vexs[0];
    graph->arcs[0][0]=1;    /* 表示顶点v0在集合U中 */

    //初始化
    for (i=1; i<graph->vexCount; i++) {
    /* 初始化集合V-U中顶点的距离值 */
        dist[i].length=graph->arcs[0][i];
        dist[i].vertex=graph->vexs[i];
        if (dist[i].length!=MAX)
            dist[i].prevex=0;
        else  dist[i].prevex= -1;
    }
    for (i=1; i<graph->vexCount; i++) {
        min=MAX; minvex=0;

        //筛选最小值
        for (j=1; j<graph->vexCount; j++)
        /*在V-U中选出距离值最小顶点*/
            if ( (graph->arcs[j][j]==0) && (dist[j].length<min) ) {
                min=dist[j].length;
                minvex=j;
            }
                    if (minvex==0)  /* 从v0没有路径可以通往集合V-U中的顶点 */
             break;
        graph->arcs[minvex][minvex]=1;  /* 集合V-U中路径
                    最小的顶点为minvex */

        //更新
        for (j=1; j<graph->vexCount; j++) {
        /* 调整集合V-U中的顶点的最短路径 */
            if (graph->arcs[j][j]==1)
                continue;
            if (dist[j].length>dist[minvex].length+graph->arcs[minvex][j]) {
                dist[j].length=dist[minvex].length+graph->arcs[minvex][j];
                dist[j].prevex=minvex;
            }
        }
    }
}

//查找某个顶点在图中的存储位置(下标),找不到返回-1
int LocateVex (PGraph pG, VexType vert) {
    int  i;
    for (i=0; i<pG->vexCount; i++)
        if (pG->vexs[i] == vert)  return i;
    return -1;
}


void PrintDist(PGraph A, Path dist[])
{   //打印距离
    for (int i = 0; i < A->arcCount; i++) {
        printf("%d\n", dist[i].length);
    }
}

int main(){
    int n;
    Graph s;
    Path dist[1000];
    s = (PGraph)malloc(sizeof(Graph));
    scanf("%d", &n);
    CreateGraph(s,n);
    Dijkstra (s,dist);
    PrintDist(s,dist);

    return 0;
}

结果
在这里插入图片描述
在这里插入图片描述

4、收获和总结

根据结果和讨论,接受假设,并给出理由。我们接受我们的假设,即如果给定一个有向图的邻接矩阵,那么可以用Dijkstra算法计算出从源节点到其他所有节点的最短路径长度。这是因为我们通过编写和运行一个用C语言编写的程序,成功地实现了Dijkstra算法,并得到了正确和符合预期的结果。

总结实验的主要发现和收获。通过本实验,我们发现了Dijkstra算法是一种有效和高效的求解单源最短路径问题的算法,它可以应用于各种有向图中。我们也收获了编写和运行C语言程序的能力和经验,以及分析和解释实验结果的技巧和方法。

提出实验的局限性和改进方向。本实验还存在一些局限性和不足之处,例如:我们没有考虑有负权边或负环存在的情况,这可能会导致Dijkstra算法失效或出错;我们没有比较不同数据结构对Dijkstra算法效率的影响,这可能会影响程序运行时间和空间复杂度;我们没有测试不同规模和类型的图对Dijkstra算法性能的影响,这可能会导致不同的结果和分析。为了改进实验,我们可以在未来尝试以下几个方面:我们可以使用其他求解最短路径问题的算法(如Floyd算法或Bellman-Ford算法),并与Dijkstra算法进行比较和分析;我们可以使用不同的数据结构(如堆、栈、队列等),并观察它们对Dijkstra算法效率的影响;我们可以使用不同规模和类型的图(如稀疏图、稠密图、随机图等),并探究它们对Dijkstra算法性能的影响。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值