dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)

这篇具有很好参考价值的文章主要介绍了dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

改进迪杰斯特拉算法(dijkstra):输出所有最短路径

对于权值非负的图求解单源最短路径,第一想法是使用dijkstra算法。最短路径问题是满足最优子结构的:父问题一定会使用子问题的最优解。问题在于子问题的计算次序。dijkstra算法思想建立在我们为无负权图定义的子问题计算顺序基础上:即离源点最近点不会变成其他问题的子问题,其他问题只能成为他的子问题。

​本次实验在实现dijkstra算法的基础上:

  • 构建基于邻接表的图类:Graph.class,便于以后实验复用。
  • 此外加入了优先队列进行优化
  • 不仅实现对最优解(最短路径)的记录而且对所有的最优解(所有的最短路径)进行输出

本次实现额外实现部分:

  • 实现基于邻接表的Graph数据结构
  • 堆优化:将时间复杂度优化到 n l g ( n ) nlg(n) nlg(n)
  • 输出【所有的最短路径】:需要花费额外的时间复杂度(一般的dijkstra不需要输出所有的最短路径,只需要输出一条,本实验输出了所有的最短路径)

程序如下:

//
// Created by BlancheSun on 2022/4/10.
//

#include<iostream>
#include<algorithm>
#include <vector>
#include <queue>

using namespace std;

class ArcNode{
public:
    int adjvec;
    double weight;
    ArcNode(int adjvec, double weight) : adjvec(adjvec), weight(weight){};
    ArcNode() {};
    friend bool operator<(const ArcNode& arc1, const ArcNode& arc2) {
        return arc1.weight > arc2.weight;
    }
};

class Graph{
private:
    int vexnum; // 顶点数
    int arcnum; // 边数
    vector<vector<ArcNode>> arcList;   // 矩阵存储结点之间的连接关系
public:
    Graph(int vexnum, int arcnum);
    Graph();
    void add_edge(int start, int end, double weight);
    void dijkstra(int source, int target);
    void recordShortestPath(vector<vector<int>>& pre, int target, vector<vector<int>>& path, vector<int> temp_path);
    void printAllPath(vector<vector<int>>& path, vector<double>& dis, int source, int target);
};

void Graph::add_edge(int start, int end, double weight){
    arcList[start].push_back(ArcNode(end, weight));
    arcList[end].push_back(ArcNode(start, weight));
}

void Graph::dijkstra(int source, int target) {
    // 初始化操作
    int n = this->vexnum;
    int inf = 99999.0;
    vector<double> dis(n, inf);
    dis[source] = 0;
    vector<vector<int>> pre(n,vector<int>(0));
    priority_queue<ArcNode> nodeQueue;
    nodeQueue.push(ArcNode(source, 0));

    // 队列非空时进行检索
    while(!nodeQueue.empty()) {
        int node = nodeQueue.top().adjvec;  // 获得目前队列中距离源点最近的结点
        double weight = nodeQueue.top().weight;  // 或者写成 weight = nodeQueue.top().weight
        nodeQueue.pop();    // 取出队列中的元素

        if(node == 3||node == 5) {
            int m = 1;
        }
        // 从该结点出发,更新该结点能直接相连的结点
        for(int i = 0; i < arcList[node].size(); i++) {
            int v = arcList[node][i].adjvec;
            if (weight + arcList[node][i].weight < dis[v]) {    // 越界了!
                dis[v] = weight + arcList[node][i].weight;
                pre[v] = {node};                          // 记录上一步的结点
                nodeQueue.push(ArcNode(v, dis[v]));
            }else if(weight + arcList[node][i].weight == dis[v]) {
                pre[v].push_back(node);
            }
        }
    }
    vector<vector<int>> path;    // 初始大小的为0,
    vector<int> temp_path;
    recordShortestPath(pre, target, path, temp_path);   // 运行后path_index数值即为路径条数,不用加一,程序中已经加一
    printAllPath(path, dis, source, target);

}

Graph::Graph(int vexnum, int arcnum) : vexnum(vexnum), arcnum(arcnum) {
    // 做容量的初始化
    vector<vector<ArcNode>> V(vexnum, vector<ArcNode>());
    this->arcList = V;
}

Graph::Graph() {}

/** 第k条路径的记录 **/
void Graph::recordShortestPath(vector<vector<int>> &pre, int target, vector<vector<int>>& path, vector<int> temp_path) {
    vector<int> now_pre = pre[target];  // 获得当前节点的前驱结点集合
    temp_path.push_back(target);
    if(now_pre.empty()) {   // 没有前驱结点
        path.push_back(temp_path);
        return;
    }
    // 不为空,那么深搜前驱结点,得到路径
    for(int i = 0; i < now_pre.size(); i++) {
        int target2 = now_pre[i];   // 集合中的一个点
        recordShortestPath(pre, target2, path, temp_path);
    }
}

/** 打印所有的路径 **/
void Graph::printAllPath(vector<vector<int>> &path, vector<double>& dis, int source, int target) {
    cout << "The shortest path(es) from node " << source << " to node " << target << " are/is as follows: " << endl;
    int k = path.size();    // 路径的条数
    for(int i = 0; i<k; i++) {
        cout << "Path" << i << ": " ;
        for(int j = path[i].size()-1; j > 0 ; j--) {
            cout << path[i][j] << " -> ";
        }
        cout << path[i][0] << endl;
    }
    cout << "The length of path(es) is " << dis[target] <<endl;
}

int main() {
	// 多条最短路径的测试
    Graph graph = Graph(9, 14); // 9个顶点,14条边

    // 边的添加
    graph.add_edge(0,1,1);
    graph.add_edge(0,7,8);
    graph.add_edge(1,2,8);
    graph.add_edge(1,7,11);
    graph.add_edge(2,3,3);
    graph.add_edge(2,5,4);
    graph.add_edge(2,8,2);
    graph.add_edge(3,4,9);
    graph.add_edge(3,5,1);
    graph.add_edge(4,5,10);
    graph.add_edge(5,6,2);
    graph.add_edge(6,7,1);
    graph.add_edge(6,8,6);
    graph.add_edge(7,8,7);

    int source = 0, target = 4;
    graph.dijkstra(source, target);
    
//    Graph graph = Graph(16, 30); // 16个顶点,30条边
//
//    // 边的添加
//    graph.add_edge(0,2,3);
//    graph.add_edge(1,3,1);
//    graph.add_edge(1,4,3);
//    graph.add_edge(1,5,6);
//    graph.add_edge(2,4,8);
//    graph.add_edge(2,5,7);
//    graph.add_edge(2,6,6);
//    graph.add_edge(3,7,6);
//    graph.add_edge(3,8,8);
//    graph.add_edge(4,7,3);
//    graph.add_edge(4,8,5);
//    graph.add_edge(5,8,3);
//    graph.add_edge(5,9,3);
//    graph.add_edge(6,8,8);
//    graph.add_edge(6,9,4);
//    graph.add_edge(7,10,2);
//    graph.add_edge(7,11,2);
//    graph.add_edge(8,11,1);
//    graph.add_edge(8,12,2);
//    graph.add_edge(9,11,3);
//    graph.add_edge(9,12,3);
//    graph.add_edge(10,13,3);
//    graph.add_edge(10,14,5);
//    graph.add_edge(11,13,5);
//    graph.add_edge(11,14,2);
//    graph.add_edge(12,13,6);
//    graph.add_edge(12,14,6);
//    graph.add_edge(13,15,4);
//    graph.add_edge(14,15,3);
//
//
//    // 求解最短路径
//    int source = 0, target = 15;
//    graph.dijkstra(source, target);

    return 0;
}

以下图为例进行执行

dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)
结果如下:可见所有的最短路径都被输出
dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)

1. 基于邻接表的Graph

​ 基础的邻接矩阵也能作为dijkstra算法的数据结构,但是对于稀疏图或者结点很多的情况下输入数据较为复杂且容易超出内存。本次实验使用邻接表实现图的数据结构,主要设置如下:

  • ArcNode.class:邻接表的表结点。含有表结点的终点编号adjvec和边权重weight两个属性。

  • vector<vector<ArcNode>> ArcList

    • ArcList[i]表示结点i为起点的所有表结点的集合
  • Graph.class

    含有三个属性:

    • vexnum:结点数
    • arcnum:边数
    • vector<vector<ArcNode>> ArcList:邻接表

    含有四个方法:

    • add_edge(int start, int end):向邻接表中增加表结点

    • dijkstra(int source, int target):计算最短路径和所有结点的前驱结点集合,以求得所有最短路径

    • recordShortestPath(vector<vector<int>> &pre, vector<vector<int>>& path:根据dijksra中的前驱结点集合pre,进行深度优先搜索,得到所有的最短路径,存入path数组中。

    • printAllPath(vector<vector<int>> &path):打印所有的最短路径

2 基本算法思路说明与堆优化
(1)基本思路

dikstra算法的核心过程很简单,主要特点是使用广度优先搜索。算法主要过程如下:

  • 初始化:
    • 集合初始化:设置已松弛集合 T = ∅ T = ∅ T=,和未松弛集合 U = { s 0 , s 1 . . . , s n } U = \{s_0,s_1...,s_n\} U={s0,s1...,sn}全部n个结点。
    • 距离数组初始化:设置有大小为结点个数n的距离数组 v e c t o r < i n t >   d i s ( n , + ∞ ) vector<int>\ dis(n, +\infty) vector<int> dis(n,+),另外将 d i s [ s 0 ] dis[s_0] dis[s0]初始化为0。
  • 循环n轮:每次扩展扩展使用不在集合 T T T中(在集合 U U U中),且距离源点 s 0 s_0 s0最近的结点 s k s_k sk对所有与 s k s_k sk直接相联的结点 s j s_j sj进行松弛,第一轮时即使用 s 0 s_0 s0进行松弛。松弛条件为:

{ v i s i t e d [ s j ] = = f a l s e : s j 为被用来松弛过 d i s [ s k ] + w e i g h t ( s k , s j ) < d i s [ s j ] \begin{cases}visited[s_j]==false:s_j为被用来松弛过\\ dis[s_k] + weight(s_k,s_j) < dis[s_j]\end{cases} {visited[sj]==false:sj为被用来松弛过dis[sk]+weight(sk,sj)<dis[sj]

​ 满足条件即更新 d i s [ s j ] = d i s [ s k ] + w e i g h t ( s k , s j ) dis[s_j] = dis[s_k]+weight(s_k,s_j) dis[sj]=dis[sk]+weight(sk,sj)。更新完后将 s k s_k sk加入集合T,从U中移出 s k s_k sk

​ 从动态规划的角度看,上式也可以理解成动态规划的递推式。

  • 循环结束:每个结点都被加入 T T T,此时的 d i s [ n ] dis[n] dis[n]即为最短距离。
(2)堆优化

在(1)的步骤二循环n轮中,每次循环需要寻找距离源点 s 0 s_0 s0最近的结点。若是采取循环扫描一遍的过程,那么dijkstra算法的时间复杂度将会为 O ( n 2 ) O(n^2) O(n2),仔细分析,该过程寻找最近距离的过程有下面的特点:

  • 集合大小是动态变化的结点
  • 每次从集合中寻找的距离最小的元素,不需要所有的元素有序

符合这样的特点我们可以用堆进行优化。即:

  • ① 将遍历 d i s [ s k ] dis[s_k] dis[sk]寻找最小值的过程使用一个优先队列 n o d e Q u e u e nodeQueue nodeQueuetop()pop()操作代替。同时 v i s t e d visted visted数组变得不再需要,因为该结点被 p o p ( ) pop() pop(),下轮更新队列中将不会存在该结点。
  • ② 每次对松弛对 d i s [ v ] dis[v] dis[v]进行操作时,同时需要向优先队列中减小该结点的 w e i g h t weight weight
3 实现多条最短路径记录
3.5.3.1 路径的记录

如果只需要记录一条最短路径,那么我们在进行松弛操作后用一个数组 p r e [ n ] pre[n] pre[n]记住用于松弛该结点的 s k s_k sk即可。即:
p r e [ s j ] = s k pre[s_j] = s_k pre[sj]=sk
我们这里实现了多条最短路径的输出,一维数组不再能满足我们的要求,我们需要一个二维数组 p r e [ n ] [ m ] pre[n][m] pre[n][m]记录所有的前驱结点。对于一个结点编号为 i i i的结点,其所有的前驱为 p r e [ i ] pre[i] pre[i]集合中的所有元素。当然,如果最短路径只有一条,那么 p r e [ i ] pre[i] pre[i]中只有一个元素。

另外,需要存储多条最短路径后,在松弛时进行的操作和只输出一条路径时不相同:

  • d i s [ s k ] + w e i g h t ( s k , s j ) < d i s [ s j ] dis[s_k] + weight(s_k,s_j) < dis[s_j] dis[sk]+weight(sk,sj)<dis[sj]

    直接将 p r e [ s j ] pre[s_j] pre[sj]置为 s k s_k sk的编号,然后将队列中修改 s j s_j sj的权重为 d i s [ s k ] + w e i g h t ( s k , s j ) dis[s_k] + weight(s_k,s_j) dis[sk]+weight(sk,sj),更新 d i s [ s j ] = d i s [ s k ] + w e i g h t ( s k , s j ) dis[s_j] = dis[s_k] + weight(s_k,s_j) dis[sj]=dis[sk]+weight(sk,sj)

  • d i s [ s k ] + w e i g h t ( s k , s j ) = = d i s [ s j ] dis[s_k] + weight(s_k,s_j) == dis[s_j] dis[sk]+weight(sk,sj)==dis[sj]

    p r e [ s j ] pre[s_j] pre[sj]增加 s k s_k sk的编号

3.1 路径的输出

按照上述的方式进行记录后,我们最终得到的 p r e pre pre数组,为了便于说明,我们给出下面的一个例子:

dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)

图18:pre数组举例

以图中的结点4为例,我们给出他的 p r e pre pre数组树:

dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)

图19:pre数组树

根据这棵 p r e pre pre数组树,我们能够通过深度优先遍历的方式得到从结点4开始到源点0的所有的最短路径。在深搜过程中我们每得到一条路径就应该将他存在一个二维的 p a t h path path数组,每一维存储一条路径。在得到所有最短路径后,遍历 p a t h path path数组的每个元素,每一个元素即是一条路径的路径数组,逐条打印出路径结果即可。文章来源地址https://www.toymoban.com/news/detail-403408.html

到了这里,关于dijkstra算法:堆优化 + 输出所有最短路径(得到所有最优解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 最短路径(Dijkstra算法)

    (1)最短路径: 非网图:两顶点之间经历边数最小的路径 网图:两顶点之间经历的 边上权值之和 最短的路 1.思路 设置一个集合S存放已经找到最短路径的顶点,并设置一个源点,dist[]数组中存放源点距离每个顶点的最短距离,path[]数组中存放的是最短路径,基本过程可以如

    2024年02月09日
    浏览(31)
  • 图算法——求最短路径(Dijkstra算法)

            目录 一、什么是最短路径 二、迪杰斯特拉(Dijkstra)算法  三、应用Dijkstra算法 (1) Dijkstra算法函数分析         求图的最短路径在实际生活中有许多应用,比如说在你在一个景区的某个景点,参观完后,要怎么走最少的路程到你想参观的下个景点,这就利用到

    2023年04月15日
    浏览(28)
  • 【算法】单源最短路径算法——Dijkstra算法

    迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。这是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用 贪心算法 的策略, 每次遍历到始点距离最

    2024年02月05日
    浏览(36)
  • Dijkstra算法求最短路

    Dijkstra算法的流程如下: 1.初始化dist[1] = 0,其余节点的dist值为无穷大。 2.找出一个未被标记的、dist[x]最小的节点x,然后标记节点x。 3.扫描节点x的所有出边(x,y,z),若dist[y] dist[x] + z,则使用dist[x] + z更新dist[y]。 4.重复上述2~3两个步骤,直到所有的节点都被标记。 Dijk

    2024年02月06日
    浏览(33)
  • dijkstra模板及例题(最短路算法)

            大家好,我是永遇乐金枪鱼。图论和树论是算法中占比大且非常重要的内容,而且树论是特殊的图论,而图论中最经典的就是求解最短路,而最短路算法是比较广泛且冗杂的算法,与其相关的有较多的算法,下面我给大家讲讲常用算法之一——dijkstra算法。  📒博客

    2023年04月08日
    浏览(32)
  • 【Dijkstra】最短路算法的一种

    首先,本文默认读者基本熟悉Dijkstra基本原理 DIjkstra是单源最短路的一种算法。使用数组d[i]来储存结点i到源点s的最短路径长度,每次更新d[i]数组后,d[i]中最小的一定是一条最短路径长度。也就是说每次更新后都能找到一条最短路径,以下给出证明: 假设d[]数组中当前最小

    2024年02月03日
    浏览(41)
  • 二、搜索与图论6:Dijkstra 模板题+算法模板(Dijkstra求最短路 I, Dijkstra求最短路 II,1003 Emergency)

    朴素dijkstra算法 对应模板题:Dijkstra求最短路 I 时间复杂是 O(n^2+m):n 表示点数,m 表示边数 堆优化版dijkstra 对应模板题:Dijkstra求最短路 II 时间复杂度 O(mlogn):n 表示点数,m 表示边数 树是一种特殊的图 ,与图的存储方式相同。 对于无向图中的边ab,存储两条有向边a-b, b-a。

    2024年02月14日
    浏览(27)
  • 数据结构--最短路径 Dijkstra算法

    计算  b e g i n  点到各个点的最短路 color{red}计算 begin 点到各个点的最短路 计算   b e g in   点到各个点的最短路 如果是无向图,可以先把无向图转化成有向图 我们需要2个数组 final[] (标记各顶点是否已找到最短路径)与 dis[] (最短路径⻓度)数组 Dijkstra算法是一种用于

    2024年02月12日
    浏览(26)
  • 12.图论1 最短路之dijkstra算法

    二分图 判定:染色法。 性质: 可以二着色。 无奇圈。 树的直径模板 两遍dfs/bfs,证明时反证法的核心是用假设推出矛盾。 设1是一开始随机选的点,s是与其最远的点,证明s是直径的一端。 反证:假设s不是直径的一端,ss是直径的一端。 现在要做的就是证明ss是直径的一端

    2024年02月20日
    浏览(34)
  • 【图论算法】最短路径算法(无权最短路径、Dijkstra算法、带负边值的图、无圈图)

    本篇博客将考察各种最短路径问题。     无权最短路径     Dijkstra 算法     具有负边值的图     无圈图     所有顶点对间的最短路径     最短路径的例子–词梯游戏 输入是一个赋权图:与每条边 (v i , v j ) 相联系的是穿越该边的开销(或称为值

    2023年04月12日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包