【高阶数据结构】图的应用--最短路径算法

news2025/1/18 17:05:26

文章目录

    • 一、最短路径
    • 二、单源最短路径--Dijkstra算法
    • 三、单源最短路径--Bellman-Ford算法
    • 四、多源最短路径--Floyd-Warshall算法

一、最短路径

最短路径问题:从在带权有向图G中的某一顶点出发,找出一条通往另一顶点的最短路径,最短也就是沿路径各边的权值总和达到最小。

二、单源最短路径–Dijkstra算法

单源最短路径问题:给定一个图G = ( V , E ) G=(V,E)G=(V,E),求源结点s ∈ V s∈Vs∈V到图中每个结点v ∈ V v∈Vv∈V的最短路径。Dijkstra算法就适用于解决带权重的有向图上的单源最短路径问题,同时算法要求图中所有边的权重非负。一般在求解最短路径的时候都是已知一个起点和一个终点,所以使用Dijkstra算法求解过后也就得到了所需起点到终点的最短路径。

针对一个带权有向图G,将所有结点分为两组S和Q,S是已经确定最短路径的结点集合,在初始时为空(初始时就可以将源节点s放入,毕竟源节点到自己的代价是0),Q 为其余未确定最短路径的结点集合,每次从Q 中找出一个起点到该结点代价最小的结点u ,将u 从Q 中移出,并放入S 中,对u 的每一个相邻结点v 进行松弛操作。松弛即对每一个相邻结点v ,判断源节点s到结点u 的代价与u 到v 的代价之和是否比原来s 到v 的代价更小,若代价比原来小则要将s 到v 的代价更新为s 到u 与u 到v 的代价之和,否则维持原样。如此一直循环直至集合Q 为空,即所有节点都已经查找过一遍并确定了最短路径,至于一些起点到达不了的结点在算法循环后其代价仍为初始设定的值,不发生变化。Dijkstra算法每次都是选择V-S中最小的路径节点来进行更新,并加入S中,所以该算法使用的是贪心策略。

Dijkstra算法存在的问题是不支持图中带负权路径,如果带有负权路径,则可能会找不到一些路径的最短路径。

在这里插入图片描述

代码实现:

// 临接矩阵
namespace Matrix
{
    template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>
    class Graph
    {
        typedef Graph<V, W, MAX_W, Direction> Self;

    private:
        std::vector<V> _vertexs;             // 顶点集合
        std::map<V, size_t> _vIndexMap;      // 顶点的下标映射关系
        std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵
        void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath)
        {
            size_t srci = GetVertexIndex(src);
            size_t n = _vertexs.size();
            for (size_t i = 0; i < n; ++i)
            {
                if (i != srci)
                {
                    // 找出i顶点的路径
                    std::vector<int> path;
                    size_t parenti = i;
                    while (parenti != srci)
                    {
                        path.push_back(parenti);
                        parenti = pPath[parenti];
                    }
                    path.push_back(srci);
                    reverse(path.begin(), path.end());

                    for (auto index : path)
                    {
                        std::cout << _vertexs[index] << "->";
                    }
                    std::cout << "权值和:" << dist[i] << std::endl;
                }
            }
        }
        // 顶点个数是N  -> 时间复杂度:O(N^2)空间复杂度:O(N)
        void Dijkstra(const V &src, std::vector<W> &dist, std::vector<int> &pPath)
        {
            size_t srci = GetVertexIndex(src);
            int n = _vertexs.size();

            dist.resize(n, MAX_W);
            pPath.resize(n, -1);

            dist[srci] = 0;
            pPath[srci] = srci;

            // 已经确定最短路径的顶点集合
            std::vector<bool> S(n, false);

            // n个节点每个节点都要作为起点
            for (int i = 0; i < n; i++)
            {
                // 选最短路径顶点且不在S更新其他路径
                int u = 0;
                W min = MAX_W;

                for (int j = 0; j < n; j++)
                {
                    if (S[i] == false && dist[i] < min)
                    {
                        u = i;
                        min = dist[i];
                    }
                }

                S[u] = true;
                // 松弛更新u连接顶点v  srci->u + u->v <  srci->v  更新
                for (int v = 0; v < n; v++)
                {
                    if (S[v] == false && _matrix[u][v] != MAX_W && dist[u] + _matrix[u][v] < dist[v])
                    {
                        dist[v] = dist[u] + _matrix[u][v];
                        pPath[v] = u;
                    }
                }
            }
        }
    };
}

测试代码:

void TestGraphDijkstra()
{
    const char *str = "syztx";
    Graph<char, int, INT_MAX, true> g(str, strlen(str));
    g.AddEdge('s', 't', 10);
    g.AddEdge('s', 'y', 5);
    g.AddEdge('y', 't', 3);
    g.AddEdge('y', 'x', 9);
    g.AddEdge('y', 'z', 2);
    g.AddEdge('z', 's', 7);
    g.AddEdge('z', 'x', 6);
    g.AddEdge('t', 'y', 2);
    g.AddEdge('t', 'x', 1);
    g.AddEdge('x', 'z', 4);

    std::vector<int> dist;
    std::vector<int> parentPath;
    g.Dijkstra('s', dist, parentPath);
    g.PrintShortPath('s', dist, parentPath);

    // // 图中带有负权路径时,贪心策略则失效了。
    // // 测试结果可以看到s->t->y之间的最短路径没更新出来
    // const char *str = "sytx";
    // Graph<char, int, INT_MAX, true> g(str, strlen(str));
    // g.AddEdge('s', 't', 10);
    // g.AddEdge('s', 'y', 5);
    // g.AddEdge('t', 'y', -7);
    // g.AddEdge('y', 'x', 3);
    // std::vector<int> dist;
    // std::vector<int> parentPath;
    // g.Dijkstra('s', dist, parentPath);
    // g.PrintShortPath('s', dist, parentPath);
}

三、单源最短路径–Bellman-Ford算法

Dijkstra算法只能用来解决正权图的单源最短路径问题,但有些题目会出现负权图。这时这个算法就不能帮助我们解决问题了,而bellman—ford算法可以解决负权图的单源最短路径问题。它的优点是可以解决有负权边的单源最短路径问题,而且可以用来判断是否有负权回路。它也有明显的缺点,它的时间复杂度 O(NE) (N是点数,E是边数)普遍是要高于Dijkstra算法O(N²)的。像这里如果我们使用邻接矩阵实现,那么遍历所有边的数量的时间复杂度就是O(N^3),这里也可以看出来Bellman-Ford就是一种暴力求解更新。

在这里插入图片描述

代码实现:

// 临接矩阵
namespace Matrix
{
    template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>
    class Graph
    {
        typedef Graph<V, W, MAX_W, Direction> Self;

    private:
        std::vector<V> _vertexs;             // 顶点集合
        std::map<V, size_t> _vIndexMap;      // 顶点的下标映射关系
        std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵
        void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath)
        {
            size_t srci = GetVertexIndex(src);
            size_t n = _vertexs.size();
            for (size_t i = 0; i < n; ++i)
            {
                if (i != srci)
                {
                    // 找出i顶点的路径
                    std::vector<int> path;
                    size_t parenti = i;
                    while (parenti != srci)
                    {
                        path.push_back(parenti);
                        parenti = pPath[parenti];
                    }
                    path.push_back(srci);
                    reverse(path.begin(), path.end());

                    for (auto index : path)
                    {
                        std::cout << _vertexs[index] << "->";
                    }
                    std::cout << "权值和:" << dist[i] << std::endl;
                }
            }
        }
        // 时间复杂度:O(N^3) 空间复杂度:O(N)
        bool BellmanFord(const V &src, std::vector<W> &dist, std::vector<int> &pPath)
        {
            size_t n = _vertexs.size();
            size_t srci = GetVertexIndex(src);

            // vector<W> dist,记录srci-其他顶点最短路径权值数组
            dist.resize(n, MAX_W);

            // vector<int> pPath 记录srci-其他顶点最短路径父顶点数组
            pPath.resize(n, -1);

            // 先更新srci->srci为缺省值
            dist[srci] = W();

            // 总体最多更新n轮
            for (int k = 0; k < n; k++)
            {
                bool update = false;
                std::cout << "更新第:" << k << "轮" << std::endl;
                for (int i = 0; i < n; i++)
                {
                    for (int j = 0; j < n; j++)
                    {
                        // srci -> i i -> j
                        if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
                        {
                            update = true;
                            std::cout << _vertexs[i] << "->" << _vertexs[j] << ":" << _matrix[i][j] << std::endl;
                            dist[j] = dist[i] + _matrix[i][j];
                            pPath[j] = i;
                        }
                    }
                }
                // 如果这个轮次中没有更新出更短路径,那么后续轮次就不需要再走了
                if (update == false)
                    break;
            }

            // 还能更新就是带负权回路
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (_matrix[i][j] != MAX_W && dist[i] + _matrix[i][j] < dist[j])
                    {
                        return false;
                    }
                }
            }

            return true;
        }
    };
}

测试代码:

void TestGraphBellmanFord()
{
    // const char *str = "syztx";
    // Graph<char, int, INT_MAX, true> g(str, strlen(str));
    // g.AddEdge('s', 't', 6);
    // g.AddEdge('s', 'y', 7);
    // g.AddEdge('y', 'z', 9);
    // g.AddEdge('y', 'x', -3);
    // g.AddEdge('z', 's', 2);
    // g.AddEdge('z', 'x', 7);
    // g.AddEdge('t', 'x', 5);
    // g.AddEdge('t', 'y', 8);
    // g.AddEdge('t', 'z', -4);
    // g.AddEdge('x', 't', -2);
    // std::vector<int> dist;
    // std::vector<int> parentPath;
    // g.BellmanFord('s', dist, parentPath);
    // g.PrintShortPath('s', dist, parentPath);

    // 微调图结构,带有负权回路的测试
    const char *str = "syztx";
    Graph<char, int, INT_MAX, true> g(str, strlen(str));
    g.AddEdge('s', 't', 6);
    g.AddEdge('s', 'y', 7);
    g.AddEdge('y', 'x', -3);
    g.AddEdge('y', 'z', 9);
    g.AddEdge('y', 'x', -3);
    g.AddEdge('y', 's', 1); // 新增
    g.AddEdge('z', 's', 2);
    g.AddEdge('z', 'x', 7);
    g.AddEdge('t', 'x', 5);
    g.AddEdge('t', 'y', -8); // 更改
    g.AddEdge('t', 'z', -4);
    g.AddEdge('x', 't', -2);
    std::vector<int> dist;
    std::vector<int> parentPath;
    if (g.BellmanFord('s', dist, parentPath))
        g.PrintShortPath('s', dist, parentPath);
    else
        std::cout << "带负权回路" << std::endl;
}

四、多源最短路径–Floyd-Warshall算法

Floyd-Warshall算法是解决任意两点间的最短路径的一种算法。

Floyd算法考虑的是一条最短路径的中间节点,即简单路径p={v1,v2,…,vn}上除v1和vn的任意节点。

设k是p的一个中间节点,那么从i到j的最短路径p就被分成i到k和k到j的两段最短路径p1,p2。p1是从i到k且中间节点属于{1,2,…,k-1}取得的一条最短路径。p2是从k到j且中间节点属于{1,2,…,k-1}取得的一条最短路径。

在这里插入图片描述

在这里插入图片描述

即Floyd算法本质是三维动态规划,D[i][j][k]表示从点i到点j只经过0到k个点最短路径,然后建立起转移方程,然后通过空间优化,优化掉最后一维度,变成一个最短路径的迭代算法,最后即得到所以点的最短路。

在这里插入图片描述

代码实现:

// 临接矩阵
namespace Matrix
{
    template <class V, class W, W MAX_W = INT_MAX, bool Direction = false>
    class Graph
    {
        typedef Graph<V, W, MAX_W, Direction> Self;

    private:
        std::vector<V> _vertexs;             // 顶点集合
        std::map<V, size_t> _vIndexMap;      // 顶点的下标映射关系
        std::vector<std::vector<W>> _matrix; // 存储边集合的矩阵
		void PrintShortPath(const V &src, const std::vector<W> &dist, const std::vector<int> &pPath)
        {
            size_t srci = GetVertexIndex(src);
            size_t n = _vertexs.size();
            for (size_t i = 0; i < n; ++i)
            {
                if (i != srci)
                {
                    // 找出i顶点的路径
                    std::vector<int> path;
                    size_t parenti = i;
                    while (parenti != srci)
                    {
                        path.push_back(parenti);
                        parenti = pPath[parenti];
                    }
                    path.push_back(srci);
                    reverse(path.begin(), path.end());

                    for (auto index : path)
                    {
                        std::cout << _vertexs[index] << "->";
                    }
                    std::cout << "权值和:" << dist[i] << std::endl;
                }
            }
        }
        void FloydWarshall(std::vector<std::vector<W>> &vvDist, std::vector<std::vector<int>> &vvpPath)
        {
            size_t n = _vertexs.size();

            vvDist.resize(n);
            vvpPath.resize(n);

            // 初始化权值和路径矩阵
            for (int i = 0; i < n; i++)
            {
                vvDist[i].resize(n, MAX_W);
                vvpPath[i].resize(n, -1);
            }

            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (_matrix[i][j] != MAX_W)
                    {
                        vvDist[i][j] = _matrix[i][j];
                        vvpPath[i][j] = i;
                    }
                    if (i == j)
                    {
                        vvDist[i][j] = W();
                    }
                }
            }

            // abcdef  a {} f ||  b {} c
            // 最短路径的更新i-> {其他顶点} ->j
            for (int k = 0; k < n; k++)
            {
                for (int i = 0; i < n; i++)
                {
                    for (int j = 0; j < n; j++)
                    {
                        // i -> j  i -> k  + k -> j
                        // k 作为的中间点尝试去更新i->j的路径
                        if (vvDist[i][k] != MAX_W && vvDist[k][j] != MAX_W && vvDist[i][k] + vvDist[k][j] < vvDist[i][j])
                        {
                            vvDist[i][j] = vvDist[i][k] + vvDist[k][j];

                            // 找跟j相连的上一个邻接顶点
                            // 如果k->j 直接相连,上一个点就k,vvpPath[k][j]存就是k
                            // 如果k->j 没有直接相连,k->...->x->j,vvpPath[k][j]存就是x
                            vvpPath[i][j] = vvpPath[k][j];
                        }
                    }
                }
            }

            // 打印权值和路径矩阵观察数据
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (vvDist[i][j] == MAX_W)
                    {
                        printf("%3c", '*');
                    }
                    else
                    {
                        printf("%3d", vvDist[i][j]);
                    }
                }
                std::cout << std::endl;
            }
            std::cout << std::endl;

            for (size_t i = 0; i < n; ++i)
            {
                for (size_t j = 0; j < n; ++j)
                {
                    printf("%3d", vvpPath[i][j]);
                }
                std::cout << std::endl;
            }
            std::cout << "=================================" << std::endl;
        }
    };
}

测试代码:

void TestFloydWarShall()
{
    const char *str = "12345";
    Graph<char, int, INT_MAX, true> g(str, strlen(str));
    g.AddEdge('1', '2', 3);
    g.AddEdge('1', '3', 8);
    g.AddEdge('1', '5', -4);
    g.AddEdge('2', '4', 1);
    g.AddEdge('2', '5', 7);
    g.AddEdge('3', '2', 4);
    g.AddEdge('4', '1', 2);
    g.AddEdge('4', '3', -5);
    g.AddEdge('5', '4', 6);
    std::vector<std::vector<int>> vvDist;
    std::vector<std::vector<int>> vvParentPath;
    g.FloydWarshall(vvDist, vvParentPath);

    // 打印任意两点之间的最短路径
    for (size_t i = 0; i < strlen(str); ++i)
    {
        g.PrintShortPath(str[i], vvDist[i], vvParentPath[i]);
    }
}

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

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

相关文章

把前端打包放到Eladmin框架中运行

再resuorces目录创建static文件夹&#xff0c;然后把前端文件放进来 然后修改 ConfigurerAdapter文件&#xff0c;如下图所示 这样就可以通过ip端口/index.html 这样访问啦&#xff01;

vue3 滚动条滑动到元素位置时,元素加载

水个文 效果 要实现的思路就是&#xff0c;使用IntersectionObserver 检测元素是否在视口中显示&#xff0c;然后在通过css来进行动画载入。 1.监控元素是否视口中显示 const observer new IntersectionObserver((entries) > {entries.forEach((entry) > {if (entry.i…

【网络安全学习】漏洞利用:BurpSuite的使用-03-枚举攻击案例

如何使用BurpSuite进行枚举攻击 1.靶场选择 BurpSuite官方也是有渗透的教学与靶场的&#xff0c;这次就使用BurpSuite的靶场进行练习。 靶场地址&#xff1a;https://portswigger.net/web-security 登录后如下图所示&#xff0c;选择**【VIEW ALL PATHS】**&#xff1a; 找…

树状数组基础知识

lowbit: lowbit(x)x&(-x) 树状数组&#xff1a; 树状数组的功能&#xff1a; 数组 在O(1)的时间复杂度实现单点加&#xff1a; 在O(lng n)的时间复杂度实现查询前缀和&#xff1a; 树状数组的定义&#xff1a; 查询前x项的和操作&#xff1a; ll query(int x){ll s0;f…

单例模式详解:概念与实用技巧

目录 单例模式单例模式结构单例模式适用场景单例模式优缺点练手题目题目描述输入描述输出描述输入示例输出示例提示信息题解 单例模式 单例模式是一种创建型设计模式&#xff0c; 让你能够保证一个类只有一个实例&#xff0c; 并提供一个访问该实例的全局节点。 只有一个实例的…

LLM - 词表示和语言模型

一. 词的相似度表示 (1): 用一系列与该词相关的词来表示 (2): 把每个词表示一个独立的符号(one hot) (3): 利用该词上下文的词来表示该词 (3): 建立一个低维度的向量空间&#xff0c;用深度学习方法将该词映射到这个空间里(Word Embedding) 二&#xff1a;语言模型 (1): 根…

jsqlparse工具拦截sql处理和拓展

前置知识 访问者模式 &#xff08;Visitor Pattern&#xff09;是一种行为设计模式&#xff0c;它允许你定义在不改变被访问元素的类的前提下&#xff0c;扩展其功能。通过将操作&#xff08;操作或算法&#xff09;从对象结构中提取出来&#xff0c;可以在不修改这些对象的前…

MCU中如何利用串口通信,增加AT指令框架

第一步&#xff0c;通过串口与PC端建立通信第二步&#xff0c;根据PC端发来的AT指令&#xff0c;MCU执行相应代码 主要是解析PC端发来的字符串&#xff0c;也就是获取字符串、处理字符串、以及分析字符串。 1. 串口通信 用到的是DMA串口通信&#xff0c;收发字符串数据时&…

AGI系列(7)Reflection 在 AI agent 中的应用实例

斯坦福大学教授吴恩达一直非常推崇AI Agent,之前他提出过AI Agent的四种工作模式,分别是Reflection(反思)、Tool use(工具使用)、Planning(规划)和Multi-agent collaboration(多智能体协同)。 近日,他又开源了一个翻译 AI Agent, 他认为 AI 智能体机器翻译对改进传…

spring6框架解析(by尚硅谷)

文章目录 spring61. 一些基本的概念、优势2. 入门案例实现maven聚合工程创建步骤分析实现过程 3. IoC&#xff08;Inversion of Control&#xff09;基于xml的bean环境搭建获取bean获取接口创建实现类依赖注入 setter注入 和 构造器注入原生方式的setter注入原生方式的构造器注…

electron-vue自定义标题

1.在主进程background.js或者main.js中主窗口配置frame: false async function createWindow() {Menu.setApplicationMenu(null);// Create the browser window.const win new BrowserWindow({width: 1000,height: 600,resizable: false,frame: false,webPreferences: {nodeI…

Python基础语法(与C++对比)(持续更新ing)

代码块 Python在统一缩进体系内&#xff0c;为同一代码块C{...}内部的为同一代码块 注释 Python 单行注释&#xff1a;#... 多行注释&#xff1a;... C 单行注释&#xff1a;//... 多行注释: /*...*/ 数据类型 1. Python数据类型 Python中支持数字之间使用下划线 _ 分割…

docker容器技术、k8s的原理和常见命令、用k8s部署应用步骤

容器技术 容器借鉴了集装箱的概念&#xff0c;集装箱解决了什么问题呢&#xff1f;无论形状各异的货物&#xff0c;都可以装入集装箱&#xff0c;集装箱与集装箱之间不会互相影响。由于集装箱是标准化的&#xff0c;就可以把集装箱整齐摆放起来&#xff0c;装在一艘大船把他们…

昇思学习打卡-5-基于Mindspore实现BERT对话情绪识别

本章节学习一个基本实践–基于Mindspore实现BERT对话情绪识别 自然语言处理任务的应用很广泛&#xff0c;如预训练语言模型例如问答、自然语言推理、命名实体识别与文本分类、搜索引擎优化、机器翻译、语音识别与合成、情感分析、聊天机器人与虚拟助手、文本摘要与生成、信息抽…

通过 Power Automate 以提升的权限运行 Power Apps 连接

使用Power Apps在Sharepoint列表中新建或编辑项比较简单&#xff0c;就是创建窗体&#xff0c;连接Sharepoint列表&#xff0c;添加个按钮&#xff0c;触发条件为Submit(form)。 填写信息&#xff0c;点击按钮即可新建项 但使用过程中&#xff0c;发现运行此应用的用户&#xf…

朗新天霁eHR GetFunc_code.asmx SQL注入致RCE漏洞复现

0x01 产品简介 朗新天霁人力资源管理系统(LongShine eHR)是一款由北京朗新天霁软件技术有限公司研发的人力资源管理系统,该产品融合了国外先进的人力资源管理理念和国内大量人力资源管理实践经验,是国内功能较为全面、性价比较高的人力资源管理系统之一,系统凭借其集成化…

如何通过IP地址查询地理位置及运营商信息

在数字时代&#xff0c;IP地址&#xff08;Internet Protocol Address&#xff0c;互联网协议地址&#xff09;已经成为我们日常网络活动的重要组成部分。每台连接到互联网的设备都被分配了一个唯一的IP地址&#xff0c;它不仅可以识别设备&#xff0c;还可以揭示设备的地理位置…

以太网协议介绍——UDP

注&#xff1a;需要先了解一些以太网的背景知识&#xff0c;方便更好理解UDP协议、 以太网基础知识一 以太网基础知识二 UDP协议 UDP即用户数据报协议&#xff0c;是一种面向无连接的传输层协议&#xff0c;属于 TCP/IP 协议簇的一种。UDP具有消耗资源少、通信效率高等优点&a…

MySQL 9.0 GA 来了!

2024 年 7 月 2 日&#xff0c;MySQL 9.0 GA 版本正式发布。还记得 MySQL 8.0 版本正式发布于 2018 年 4 月 19 日&#xff0c;中间经过了 6 年之久&#xff0c;MySQL 官方终于发布了大版本号变更得 9.0 版本&#xff0c;接下来由我给大家介绍 MySQL 在 9.0 版本中有哪些新的变…

经典低功耗四通道运算放大器LM324

前言&#xff1a; SOP14封装LM324 这个LM324运放有几十年的历史了吧&#xff1f;很普通&#xff0c;很常用&#xff0c;搞电路的避免不了接触运放&#xff0c;怎么选择运放&#xff0c;是工程师关心的问题吧&#xff1f; 从本文开始&#xff0c;将陆续发一些常用的运放&#xf…