数据结构学习记录——图-最短路径问题(无权图单源最短路径算法、有权图单源最短路径算法、多源最短路径算法、Dijkstra(迪杰斯特拉)算法、Floyd算法)

news2024/12/24 21:36:45

目录

问题分类 

无权图单源最短路径算法

思路

伪代码

时间复杂度

代码实现(C语言)

有权图单源最短路径算法

Dijkstra(迪杰斯特拉)算法

伪代码 

时间复杂度 

代码实现(C语言)

多源最短路径算法

两种方法

Floyd算法

代码实现(C语言)


问题分类 

最短路径问题的抽象

在网络中,求两个不同顶点之间的所有路径中,边的权值之和最小的那一条路径

  • 这条路径就是两点之间的最短路径(Shortest Path)
  • 第一个顶点为源点(Source)
  • 最后一个顶点为终点(Destination)

单源最短路径问题

从某固定源点出发,求其到所有其他顶点的最短路径。

  • (有向)无权图
  • (有向)有权图

多源最短路径问题

求任意两顶点间的最短路径

  

无权图单源最短路径算法

思路

按照递增的顺序找出源点到各个顶点的最短路

在这样的一个图中,先访问与源点距离为1的顶点:

\begin{matrix} 0:\rightarrow v_{3}\, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, & \\ 1:\rightarrow v_{1} and\: v_{6}& \end{matrix} 

接着去访问与源点距离为2的顶点:

 \begin{matrix} 0:\rightarrow v_{3}\, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, & \\ 1:\rightarrow v_{1}\, and\: v_{6}& \\ 2:\rightarrow v_{2}\, and\, v_{4}& \end{matrix}

最后访问与源点距离为3的顶点,就发现所有顶点都已经被访问过了:

 \begin{matrix} 0:\rightarrow v_{3}\, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, \, & \\ 1:\rightarrow v_{1}\, and\: v_{6}& \\ 2:\rightarrow v_{2}\, and\, v_{4}& \\ 3:\rightarrow v_{5}\, and\, v_{7} & \end{matrix}

这样的方式我们发现与BFS(广度优先搜索)类似,所以通过改动原本BFS的算法来实现。

伪代码

 与之前学习的BFS算法不一样的是,删去了visited数组,不再用这个来表示顶点是否被访问了;

而是添加了dist数组和path数组,

dist数组用于存储从起始顶点到每个顶点的最短路径长度。

在算法开始时,将所有顶点的初始距离设为-1(或者无穷大,一个足够大的值)来表示尚未计算出最短路径。

在算法执行过程中,每当找到一个更短的路径时,就会更新dist数组中对应顶点的值。

path数组用于存储从起始顶点到每个顶点的最短路径的前驱节点(即上一个节点)。

通过path数组,我们可以在搜索结束后从目标顶点回溯并恢复整个最短路径。(因为是反序所以可以利用堆栈来输出整个最短路径)

将顶点W的距离(即最短路径长度)更新为顶点V的距离加1。

这意味着通过顶点V可以到达顶点W,且路径长度比当前已知的最短路径长度更短。

更新顶点W的前驱节点为顶点V。这就帮助我们在找到最短路径后回溯并恢复整个路径。

时间复杂度

O(V + E)

其中 V 是顶点数,E 是边数。

  • 遍历每个顶点:O(V)
  • 对于每个顶点,访问其所有邻接顶点:O(E)

我们遍历了每个顶点一次,并且对于每个顶点,我们只访问了它的邻接顶点一次。

因此,该算法的时间复杂度为 O(V + E)。

注意:这个时间复杂度假设图的表示方式为邻接表,其中访问每个顶点的邻接顶点的时间复杂度为 O(1)。如果使用邻接矩阵表示图,那么访问每个顶点的邻接顶点的时间复杂度将变为 O(V),总的时间复杂度将变为 O(V^2)。

代码实现(C语言)

/* 
   邻接表存储 - 无权图的单源最短路算法
   输入:图Graph,距离数组dist[],路径数组path[],源点S
   输出:dist[]中记录了源点S到各顶点的最短距离,path[]中记录了最短路径的前驱顶点
   dist[]和path[]全部初始化为-1
*/

void Unweighted(LGraph Graph, int dist[], int path[], Vertex S)
{
    Queue Q;  // 定义队列Q,用于广度优先搜索
    Vertex V;  // 当前顶点V
    PtrToAdjVNode W;  // 指向当前顶点V的邻接点的指针W
    
    Q = CreateQueue(Graph->Nv);  // 创建空队列Q,最大容量为图的顶点数Nv
    dist[S] = 0;  // 初始化源点S的距离为0
    AddQ(Q, S);  // 将源点S入队

    while (!IsEmpty(Q))
    {
        V = DeleteQ(Q);  // 从队列Q中删除一个顶点V,作为当前顶点
        for (W = Graph->G[V].FirstEdge; W; W = W->Next)
        {
            /* 对V的每个邻接点W->AdjV */
            if (dist[W->AdjV] == -1)
            {
                /* 若W->AdjV未被访问过 */
                dist[W->AdjV] = dist[V] + 1;  // 更新W->AdjV到源点S的距离
                path[W->AdjV] = V;  // 将当前顶点V记录在S到W->AdjV的路径上
                AddQ(Q, W->AdjV);  // 将W->AdjV入队,继续广度优先搜索
            }
        }
    } /* while结束*/
}

有权图单源最短路径算法

Dijkstra(迪杰斯特拉)算法

  • 令S = {源点s + 已经确定了最短路径的顶点v_{i}}
  • 对任一未收录的顶点v,定义dist[v]为s到v的最短路径长度,但该路径仅经过S中的顶点。即路径\left \{ s\rightarrow (v_{i}\in S)\rightarrow v\right \}的最小长度
  • 该路径是按照递增的顺序生成的,则

1.真正的最短路必须只经过S中的顶点。

2.每次从未收录的顶点中选一个dist最小的收录(贪心算法)。

3.增加一个v进入S,可能影响另外一个w的dist值

对于第1点:

在算法的每一步,我们将一个顶点加入集合S,该顶点是在当前阶段中距离源点s的最短路径长度最小的顶点。由于我们每次只选择当前最短路径的顶点,所以可以确保这些顶点经过的路径是当前阶段中最短的路径。

对于第3点:

如果收录v使得s到w的路径变短,则s到w的路径一定经过v,并且v到w有一条边。

在每一步中,选择的顶点v是当前阶段中距离源点s最近的顶点,而且该距离是经过已确定最短路径顶点集合S的路径长度。

当将顶点v加入集合S后,我们更新其他顶点的dist值,以反映新的最短路径长度。如果从v到w之间不存在直接的边,那么根据Dijkstra算法的贪心策略,顶点w将不会被更新为新的最短路径。

所以是一定存在一条从v到顶点w的边,并且通过v的路径长度加上边的权重小于w当前的dist值,那么说明我们可以通过顶点v找到一条更短的路径来达到顶点w。因此,为了保证选择的是下一个最近的顶点,我们要先进行比较。即当前的dist值通过v的路径长度加上边的权重比较。

dist[w] = min\left \{ dist[w],dist[v]\, +\, <v,w>_{weight} \right \}

伪代码 

E<V,W>表示V到W的边上的权重。

时间复杂度 

邻接矩阵表示法

在使用邻接矩阵表示图的情况下,每次查找未收录顶点中dist最小者的时间复杂度为O(V),并且需要进行V次这样的查找。

在每次查找中,还需要遍历当前顶点的所有邻接顶点,即总共需要进行V次遍历。

因此,总的时间复杂度为O(V^2 + E)。

邻接表表示法

而在使用邻接表表示图的情况下,可以使用最小堆来查找未收录顶点中dist最小者,可以将时间复杂度降低到O(logV)。

在每次查找中,仅需遍历当前顶点的邻接表,即遍历的次数不会超过图中的边数E。因此,总的时间复杂度为O((V + E)logV)。

注意:在稀疏图(边的数量相对较少)的情况下,邻接表表示图的效率更高。而在稠密图(边的数量接近顶点数量的平方)的情况下,邻接矩阵表示图的效率更高。

代码实现(C语言)

/* 
   邻接矩阵存储 - 有权图的单源最短路算法
   输入:图Graph,距离数组dist[],路径数组path[],源点S
   输出:dist[]中记录了源点S到各顶点的最短距离,path[]中记录了最短路径的前驱顶点
   使用邻接矩阵表示图,INFINITY表示不存在的边
*/

Vertex FindMinDist(MGraph Graph, int dist[], int collected[])
{ 
    /* 返回未被收录顶点中dist最小者 */
    Vertex MinV, V;
    int MinDist = INFINITY;

    for (V = 0; V < Graph->Nv; V++)
    {
        if (collected[V] == false && dist[V] < MinDist)
        {
            /* 若V未被收录,且dist[V]更小 */
            MinDist = dist[V]; /* 更新最小距离 */
            MinV = V; /* 更新对应顶点 */
        }
    }
    if (MinDist < INFINITY) /* 若找到最小dist */
        return MinV; /* 返回对应的顶点下标 */
    else
        return ERROR;  /* 若这样的顶点不存在,返回错误标记 */
}

bool Dijkstra(MGraph Graph, int dist[], int path[], Vertex S)
{
    int collected[MaxVertexNum];
    Vertex V, W;

    /* 初始化:此处默认邻接矩阵中不存在的边用INFINITY表示 */
    for (V = 0; V < Graph->Nv; V++)
    {
        dist[V] = Graph->G[S][V];
        if (dist[V] < INFINITY)
            path[V] = S;
        else
            path[V] = -1;
        collected[V] = false;
    }
    /* 先将起点收入集合 */
    dist[S] = 0;
    collected[S] = true;

    while (1)
    {
        /* V = 未被收录顶点中dist最小者 */
        V = FindMinDist(Graph, dist, collected);
        if (V == ERROR) /* 若这样的V不存在 */
            break;      /* 算法结束 */
        collected[V] = true;  /* 收录V */
        for (W = 0; W < Graph->Nv; W++) /* 对图中的每个顶点W */
        {
            /* 若W是V的邻接点并且未被收录 */
            if (collected[W] == false && Graph->G[V][W] < INFINITY)
            {
                if (Graph->G[V][W] < 0) /* 若有负边 */
                    return false; /* 不能正确解决,返回错误标记 */
                /* 若收录V使得dist[W]变小 */
                if (dist[V] + Graph->G[V][W] < dist[W])
                {
                    dist[W] = dist[V] + Graph->G[V][W]; /* 更新dist[W] */
                    path[W] = V; /* 更新S到W的路径 */
                }
            }
        }
    } /* while结束*/
    return true; /* 算法执行完毕,返回正确标记 */
}

多源最短路径算法

两种方法

  • 方法1:直接将单元最短路算法调用V遍

在稀疏图的情况下,单源最短路径算法时间复杂度为O(V^2 + E);

现在是多源,要调用V遍,故而时间复杂度T为:O(V^3 + E*V)

  • 方法2:Floyd算法

这个算法用于稠密图,时间复杂度T直接就为:O(V^3)

在下面进行解释:

Floyd算法

对于稠密图的话,我们通常是可以使用邻接矩阵来表示图的,Floyd算法也是基于邻接矩阵的一个算法。

  • D^{k}[i][j] = 路径\left \{ i\rightarrow \left \{ l\leq k \right \}\rightarrow j \right \}的最小长度
  • D^{0},D^{1},......,D^{\left | V \right |-1}[i][j],即给出了i到j的真正最短距离
  • D^{-1},即初始的矩阵定义为:带权的邻接矩阵,对角元为0.表示结点到自身的距离为0
  • D^{k-1}已经完成,递推到D^{k}时:

k\notin最短路径\left \{ i\rightarrow \left \{ l\leq k \right \}\rightarrow j \right \}时,D^{k} = D^{k-1}

k\in最短路径\left \{ i\rightarrow \left \{ l\leq k \right \}\rightarrow j \right \}时,该路径必定由两段最短路径组成D^{k}[i][j]= D^{k-1}[i][k]+D^{k-1}[k][j]

Floyd算法也是逐步地求出最短距离的,通过遍历所有可能的中间节点k,并不断尝试更新D[i][j]的值,Floyd算法逐步优化路径长度,最终找到图中所有节点对之间的最短路径。

假设我们有一个图,其中有节点i和节点j需要连接,并且我们已经知道了节点i到节点j的当前最短路径长度(存储在D[i][j]中)。

现在,我们引入一个中间节点k,想要通过节点k来尝试找到更短的路径,即尝试在节点i和节点j之间建立一条路径,其中包括节点k。

我们首先考虑从节点i到节点k的路径长度,这个距离我们可以在D数组中找到,即D[i][k]。接着,我们考虑从节点k到节点j的路径长度,同样可以在D数组中找到,即D[k][j]。

现在,我们将这两段路径长度加在一起,得到D[i][k] + D[k][j]。这个值表示通过节点k连接的从节点i到节点j的路径长度。

接下来,我们将这个通过节点k的路径长度与当前已知的最短路径长度(即D[i][j])进行比较。

  • 如果通过节点k的路径长度更短,那么我们更新D[i][j]的值为D[i][k] + D[k][j],表示我们找到了一条经过节点k的更短路径。
  • 如果通过节点k的路径长度并不比当前已知的最短路径长度更短,那么我们不做任何更改,保持D[i][j]不变。

代码实现(C语言)

/* 邻接矩阵存储 - 多源最短路算法 */

bool Floyd(MGraph Graph, WeightType D[][MaxVertexNum], Vertex path[][MaxVertexNum]) 
{
    Vertex i, j, k;

    /* 初始化 */
    for (i = 0; i < Graph->Nv; i++)
    {
        for (j = 0; j < Graph->Nv; j++) 
        {
            D[i][j] = Graph->G[i][j];  // 初始化最短路径数组D为图的邻接矩阵
            path[i][j] = -1;           // 初始化路径数组path为-1,表示当前节点间没有中间节点
        }
    }

    for (k = 0; k < Graph->Nv; k++) 
    {
        for (i = 0; i < Graph->Nv; i++) 
        {
            for (j = 0; j < Graph->Nv; j++) 
            {
                if (D[i][k] + D[k][j] < D[i][j]) 
                {  // 如果通过中间节点k可以获得更短的路径
                    D[i][j] = D[i][k] + D[k][j];    // 更新节点i到节点j的最短路径长度
                    if (i == j && D[i][j] < 0) 
                    {    // 若发现负值圈,即存在权重之和为负的回路
                        return false;               // 不能正确解决,返回错误标记
                    }
                    path[i][j] = k;                  // 更新节点i到节点j的路径经过的中间节点为k
                }
            }
        }
    }
    return true;  // 算法执行完毕,返回正确标记
}

 


end


学习自:MOOC数据结构——陈越、何钦铭

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/609407.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Apollo 智能驾驶进阶课程》四、感知

1. 感知概貌 2. 传感器和标定 激光雷达&#xff1a;主动式&#xff0c;发射功率限制 Camera: 被动式&#xff0c;受到光照影响大 Radar : 多普勒效率 相对速度 超声波: 感知距离有限&#xff0c;倒车时使用。 … 最后设备还在研发过程中。 PnP问题&#xff0c;解决标定。 IC…

chatgpt赋能python:Python实现字符串匹配的SEO优化

Python实现字符串匹配的SEO优化 在现代网络中&#xff0c;SEO&#xff08;搜索引擎优化&#xff09;已成为一项必不可少的技能。它涉及到网站的排名、用户的流量和营销策略等方面。关键字匹配是一种常见的SEO技术&#xff0c;它可以帮助你的网站在搜索引擎中排名更高。 本篇文…

Java 实现判定顺序表中是否包含某个元素的方法

一、思路 1.定义一个toFind变量来传入要查找的元素 2.遍历整个顺序表并判定当前下标的元素等不等于toFind 3.如果等于就返回一个true&#xff0c;否则返回false。 二、图解 首先调用以下的方法求出顺序表的长度&#xff0c;再使用 for 循环遍历每一个元素。 // 求顺序表的长…

《嵌入式系统》知识总结9:使用STM32固件库操纵GPIO

STM32固件库&#xff08;函数库&#xff09; “STM32 标准函数库”它是由 ST 公司针对 STM32 提供的函数接口&#xff0c;即 API (Application Program Interface)&#xff0c;开发者可调用这些函数接口来配置 STM32的寄存器&#xff0c;使开发人员得以脱离最底层的寄存器操作&…

《阿里大数据之路》研读笔记(1)

首先先看到OLAP和OLTP的区别&#xff1a; OLTP(Online transaction processing):在线/联机事务处理。典型的OLTP类操作都比较简单&#xff0c;主要是对数据库中的数据进行增删改查&#xff0c;操作主体一般是产品的用户或者是操作人员。 OLAP(Online analytical processing):…

libVLC 抓取视频帧并渲染(QGraphicsView)

作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 在《libVLC 抓取视频帧并渲染(QWidget)》介绍完 QWidget 对视频帧的渲染之后,是时候介绍第二种方式了 - QGraphicsView/QGraphicsScene/QGraphicsItem 图形视图框架。 基本步骤:自定义一个 QGraphicsIte…

RK3588平台开发系列讲解(驱动基础篇)等待队列

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、等待队列二、等待队列头三、等待队列项四、添加/删除队列五、等待唤醒六、等待事件沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 Linux 内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧…

KeepChatGPT: chatGPT增强插件,解决报错、保持活跃,让AI更丝滑

KeepChatGPT&#xff1a; chatGPT增强插件&#xff0c;解决报错、保持活跃&#xff0c;让AI更丝滑 这是一个ChatGPT的畅聊与增强插件。开源免费。不仅能解决所有报错不再刷新&#xff0c;还有保持活跃、取消审计、克隆对话、净化首页、展示大屏、展示全屏、言无不尽、拦截跟踪…

周赛348(模拟、反向思维、数位DP)

文章目录 [6462. 最小化字符串长度](https://leetcode.cn/problems/minimize-string-length/)阅读理解 [6424. 半有序排列](https://leetcode.cn/problems/semi-ordered-permutation/)模拟 [6472. 查询后矩阵的和](https://leetcode.cn/problems/sum-of-matrix-after-queries/)…

java并发编程:volatile关键字详解

文章目录 内存可见性禁止重排序什么是重排序?重排序的类型有哪些呢&#xff1f; 内存屏障volatile的用途 在Java中&#xff0c;volatile关键字有特殊的内存语义。volatile主要有以下两个功能&#xff1a; 保证变量的内存可见性禁止volatile变量与普通变量重排序 内存可见性 …

RK3588平台开发系列讲解(驱动基础篇)中断下文之 tasklet

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、中断下文之 tasklet二、tasklet相关函数介绍三、tasklet使用示例四、中断视频介绍沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 介绍中断下文之 tasklet 的基础理论知识。 一、中断下文之 tasklet 中断…

C C++ 的内存管理(C++)

目录 C / C 的内存分布 C / C 程序内存区域划分&#xff1a;​ C语言内存管理 C中动态内存管理方式&#xff1a; C内存管理 C内存管理的方式&#xff1a; new / delete 操作内置类型 new 和 delete 操作自定义类型 new 和 delete 与 malloc 和 free 的区别&#xff1a; operato…

基于Springboot的漫画之家系统设计实现

&#x1f49e;文末获取源码联系&#x1f649; &#x1f447;&#x1f3fb; 精选专栏推荐收藏订阅&#x1f447;&#x1f3fb; &#x1f380;Java项目精选实战案例《600套》&#x1f618; https://blog.csdn.net/rucoding/category_12319634.html 文章目录 1、演示视频2、课题背…

QSS盒子模型入门指南:了解和应用基础知识

目录 1. QSS盒子模型的组成部分2. QSS盒子模型的属性3. QSS盒子模型的布局4. QSS盒子模型的调试工具结论 #概述 QSS&#xff08;Qt Style Sheets&#xff09;是一种用于美化和定制化Qt应用程序的样式表语言。了解和掌握QSS盒子模型的基本概念对于创建漂亮的用户界面布局至关重要…

javascript基础二十七:说说 JavaScript 数字精度丢失的问题,解决方案?

一、场景复现 一个经典的面试题 0.1 0.2 0.3 // false 为什么是false呢? 先看下面这个比喻 比如一个数 130.33333333… 这是一个除不尽的运算&#xff0c;3会一直无限循环&#xff0c;数学可以表示&#xff0c;但是计算机要存储&#xff0c;方便下次再使用&#xff0c;但…

IMX6ULL裸机篇之I2C实验-硬件原理图

一. I2C 实验简介 I2C实验&#xff0c;我们就来学习如何使用 I.MX6U 的 I2C 接口来驱动 AP3216C&#xff0c;读取 AP3216C 的传感器数据。 AP3216C是一个三合一的环境光传感器&#xff0c;ALSPSIRLED&#xff0c;ALS是环境光&#xff0c;PS是接近传感器&#xff0c;IR是红外L…

2023 华为 Datacom-HCIE 真题题库 12/12(完结)--含解析

单项选择题 1.[试题编号&#xff1a;190728] &#xff08;单选题&#xff09;以下哪种工具不能用来匹配BGP路由条目&#xff1f; A、基本ACL B、高级ACL C、IP PREFIX LIST D、Community Filter 答案&#xff1a;B 解析&#xff1a;高级ACL是一种用于过滤IPv4报文的ACL&#…

多层级table联动

elementui 多层级table联动&#xff1a; 引用&#xff1a; https://blog.csdn.net/weixin_44780971/article/details/130054925 https://blog.csdn.net/qq_42581563/article/details/114325920 需要了解的属性&#xff1a; select-all 全选的时候执行select &#xff1a; 选择…

MySQL 连接查询

文章目录 一&#xff0c;等值连接二&#xff0c;表别名三&#xff0c;多表等值连接四&#xff0c;自然连接五&#xff0c;自连接六&#xff0c;非等值内连接七&#xff0c;外连接&#xff08;一&#xff09;左外连接&#xff08;二&#xff09;右外连接&#xff08;三&#xff…

Cookie与Session的工作流程

文章目录 Cookiecookie的工作流程1.cookie从哪里来2.cookie到哪里去3.cookie是做什么的 SessionSession工作流程 Cookie与Session都是http协议中的机制,都是用来追踪浏览器用户身份的会话方式.但是又有各自的工作流程. Cookie cookie是浏览器在本地存储数据的一种机制。 cookie…