zoukankan      html  css  js  c++  java
  • 【数据结构-图】最短路算法

    这篇文章介绍了 2 种图的最短路算法,一种是 Dijkstra 算法,另一种是 Floyd 算法。其中,Dijkstra 算法用来求图中一个节点到其余节点的最短路径(单源最短路径),而 Floyd 算法求的是图中任意两节点间的最短路径(多源最短路径)。

    Dijkstra算法

    Dijkstra 算法用来求图中一个节点到其余节点的最短路径(单源最短路径)。所以,我们要设置一个起始点 start,我们还需要一个 dist 数组用来存储起始点 start 到其余各点的最短路径,dist[i] 表示起始点 start 到节点 i 的最短路径。我们将已经找到最短路径的节点加入到集合 P,未找到最短路径的节点加入到集合 Q。在代码方面,我们使用 visit 数组来实现,visit[i]=true 表示节点 i 已经找到了 start 到它的最短路径(节点 i 在 P 中),visit[j]=false 表示没有找到 start 到 j 的最短路径(节点 j 在 Q 中)。

    首先,我们令 dist[i]=graph[start][i](这里使用的是邻接矩阵表示的图),有dist[start]=0。我们将 start 加入到集合 P 中,也就是 visit[start]=true。然后,我们在集合 Q 中寻找距离节点 start 路径最短的节点 minNode,此时 start 到 minNode 的最短路径已经确定,就是 graph[start][minNode],也就是 dist[minNode],所以我们将 visit[minNode] 设为 true,然后我们使用 minNode 来尝试更新 start 到其他节点的距离,更新方法如下:

    • 比较 dist[j](start 到 j 的距离)、 dist[minNode](start 到 minNode 之间的距离)加 graph[minNode][j](minNode 到 j 的距离)的大小;
    • 如果 dist[j]>dist[minNode]+graph[minNode][j],则将 dist[minNode] 更新为 dist[minNode]+graph[minNode][j];

    所以,dijkstra 的算法步骤如下:

    • 在集合 Q 中寻找距离起始点 start 最短的节点 minNode;
    • 将 minNode 加入到集合 P;
    • 尝试使用 minNode 更新 dist 数组;
    • 重复上面的过程直到 Q 为空;

    代码如下:

    /**
    * 输入:邻接矩阵表示的图 graph;起始点 start
    * 输出:start 到图中其他点的最短距离
    */
    vector<int> dijkstra(vector<vector<int>> graph, int start)
    {
        int n = graph.size(); // 节点个数
        vector<bool> visit(n, false);   // 标记节点是否已经被加入到已知最短路径的集合P,true表示已加入,false表示未加入
        vector<int> dist(n, INT_MAX);   // 起始点 start 到其余各点的距离
        for(int i=0; i<n; i++)
            dist[i] = graph[start][i];  // dist初始化
        visit[start] = true;
    
        for(int i=0; i<n-1; i++) // 还有n-1个节点的最短路径没有确定
        {
            int minNode = 0; // 未加入已知最短路径集合中Q到起始点start距离最短的节点编号
            int minDist = INT_MAX;  //
            for(int j=0; j<n; j++)  // 寻找minNode
            {
                if(!visit[j] && dist[j]<minDist)
                {
                    minDist = dist[j];
                    minNode = j;
                }
            }
    
            visit[minNode] = true;
            for(int j=0; j<n; j++)  // 根据minNode更新距离
            {
                if(!visit[j] && graph[minNode][j]<INT_MAX)
                {
                    if(dist[j]>dist[minNode]+graph[minNode][j])
                        dist[j] = dist[minNode] + graph[minNode][j];
                }
            }
        }
        return dist;
    }
    

    注意,上面代码中节点的编号是从 0 开始的,也就是如果有 n 个节点,则节点的编号在 [0, n-1] 之间。

    • 时间复杂度:O(n^2)
      n 为节点个数。
    • 空间复杂度:O(n)

    Floyd算法

    Floyd 算法用来求图中任意两节点间的最短路径(多源最短路径)。如果我们想缩短节点 i 和 j 之间的路径 dist[i][j],我们可以借助第 3 个节点 k,如果 dist[i][j]>dist[i][k]+dist[k][j],则说明通过 k 来进行中转会使得 i 和 j 之间的路径更短。例如,我们假设 k=1,则:

    for(int i=0; i<n; i++)
    {
        for(int j=0; j<n; j++)
        {
            if(dist[i][j]<dist[i][1]+dist[1][j])
                dist[i][j]=dist[i][1]+dist[1][j];
        }
    }
    

    上面的代码就是通过节点 1 中转来尝试缩小节点 i 到 节点 j 之间的路径。其实,不单单可以通过节点 1 来进行中转,还可以通过其他节点来进行中转,而且,不止可以通过一个节点来进行中转(i->k->j),还可以通过多个节点来进行中转(i->k1->...->kn->j)。为了实现这一思路,我们可以写 3 层循环,其中最外面一层循环代表中转节点的选择:

    /**
    * 输入:邻接矩阵表示的图 graph
    * 输出:图中任意两节点间的最短距离构成的矩阵
    */
    vector<vector<int>> floyd(vector<vector<int>> graph)
    {
        vector<vector<int>> dist = graph;
        int n = graph.size();
        for(int k=0; k<n; k++)
        {
            for(int i=0; i<n; i++)
            {
                for(int j=0; j<n; j++)
                {
                    if(dist[i][j]>dist[i][k]+dist[k][j] && dist[i][k]<INT_MAX && dist[k][j]<INT_MAX)
                        dist[i][j]=dist[i][k]+dist[k][j];
                }
            }
        }
        return dist;
    }
    
    • 时间复杂度:O(n^3)
    • 空间复杂度:O(1)

    完整代码

    使用邻接矩阵表示图,如果两个节点之间没有边,则将两节点之间的距离设为 INT_MAX。节点的编号从 0 开始,也就是如果图中有 n 个节点,节点的编号在 [0, n-1] 之间。

    #include <iostream>
    #include <vector>
    using namespace std;
    
    /**
    * 输入:邻接矩阵表示的图 graph;起始点 start
    * 输出:start 到图中其他点的最短距离
    */
    vector<int> dijkstra(vector<vector<int>> graph, int start)
    {
        int n = graph.size(); // 节点个数
        vector<bool> visit(n, false);   // 标记节点是否已经被加入到已知最短路径的集合P,true表示已加入,false表示未加入
        vector<int> dist(n, INT_MAX);   // 起始点 start 到其余各点的距离
        for(int i=0; i<n; i++)
            dist[i] = graph[start][i];  // dist初始化
        visit[start] = true;
    
        for(int i=0; i<n-1; i++) // 还有n-1个节点的最短路径没有确定
        {
            int minNode = 0; // 未加入已知最短路径集合中Q到起始点start距离最短的节点编号
            int minDist = INT_MAX;  //
            for(int j=0; j<n; j++)  // 寻找minNode
            {
                if(!visit[j] && dist[j]<minDist)
                {
                    minDist = dist[j];
                    minNode = j;
                }
            }
    
            visit[minNode] = true;
            for(int j=0; j<n; j++)  // 根据minNode更新距离
            {
                if(!visit[j] && graph[minNode][j]<INT_MAX)
                {
                    if(dist[j]>dist[minNode]+graph[minNode][j])
                        dist[j] = dist[minNode] + graph[minNode][j];
                }
            }
        }
        return dist;
    }
    
    /**
    * 输入:邻接矩阵表示的图 graph
    * 输出:图中任意两节点间的最短距离构成的矩阵
    */
    vector<vector<int>> floyd(vector<vector<int>> graph)
    {
        vector<vector<int>> dist = graph;
        int n = graph.size();
        for(int k=0; k<n; k++)
        {
            for(int i=0; i<n; i++)
            {
                for(int j=0; j<n; j++)
                {
                    if(dist[i][j]>dist[i][k]+dist[k][j] && dist[i][k]<INT_MAX && dist[k][j]<INT_MAX)
                        dist[i][j]=dist[i][k]+dist[k][j];
                }
            }
        }
        return dist;
    }
    
    int main()
    {
        int n, m;   // n:节点数,m:边数
        cin>>n>>m;
        vector<vector<int>> graph(n, vector<int>(n, INT_MAX));
        for(int i=0; i<n; i++) graph[i][i]=0;
        for(int i=0; i<m; i++)
        {
            int s, e, val;  // 起始点、终点、边的权重
            cin>>s>>e>>val;
            graph[s][e] = val;
        }
        vector<int> dist = dijkstra(graph, 0);
        cout<<"dijkstra:"<<endl;
        for(auto d:dist) cout<<d<<" ";
        cout<<endl<<endl;
    
        cout<<"floyd:"<<endl;
        vector<vector<int>> distFloyd = floyd(graph);
        for(int i=0; i<distFloyd.size(); i++)
        {
            for(int j=0; j<distFloyd[i].size(); j++)
            {
                if(distFloyd[i][j]!=INT_MAX && i!=j) cout<<i<<"->"<<j<<"="<<distFloyd[i][j]<<endl;
            }
        }
        return 0;
    }
    

    输入:

    6 9
    0 1 1
    0 2 12
    1 2 9
    1 3 3
    2 4 5
    3 2 4
    3 4 13
    3 5 15
    4 5 4
    

    输出:

    参考

    1、https://wiki.jikexueyuan.com/project/easy-learn-algorithm/dijkstra.html
    2、https://wiki.jikexueyuan.com/project/easy-learn-algorithm/floyd.html
    3、https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/
    4、https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-using-priority_queue-stl/
    5、Floyd算法为什么把k放在最外层?

  • 相关阅读:
    javascript构造函数
    闭包
    跨域资源共享之CORS详解
    浮动元素 —— 怎么使用它们、它们有什么问题以及怎么解决这些问题。
    javascript 简单的入门学习笔记(5月4日)
    CSS样式命名规则
    01-05 isKindOfClass与isMemberOfClass
    setValue和setObject的区别
    00-03 内存泄漏、内存溢出
    00-02 运算符
  • 原文地址:https://www.cnblogs.com/flix/p/13177146.html
Copyright © 2011-2022 走看看