C++算法:单源最短路径Dijkstra

文章目录

  • 前言
  • 一、Dijkstra算法思想
  • 二、算法实现
    • 1、建立图
    • 2、代码实现
  • 总结


前言

如果你有一份北京地图,想从中关村走到三元桥,那么怎样能找出实现这一目的的最短路径呢?一种可能的方法就是将这两点之间所有的路线都找出来,然后求出每条路线的距离,找出最短的路线。但是仔细想想我们就会发现这种办法几乎是不可行的,因为这样的路线太多了,而且有些路线是完全不值得考虑的,比如你从中关村到昌平再到三元桥。那这样的问题有没有更科学的解决办法呢?答案是肯定的。

我们把从中关村到三元桥之间所有路线抽象成一个加权有向图,所有路段的交点就是
图的顶点,那么这个问题就可以抽象成在加权有向图G中,求出起始点中关村,到终点
三元桥之间的最短路径。这就是我们将要介绍的最短路径问题。最短路径问题有多种变体,本文只讨论有向无环图上的单源最短路径问题。


一、Dijkstra算法思想

求最短路径实际上就是求两个顶点之间权值和最小的那条路径。在前面的找路问题中,我们的权值就是距离,所以距离和最短的就是最短路径,这是一个很形象的例子。当然,这个权值也可是其它如费用、时间等的单位。记住这只是一个抽象的概念。
我们用一张图来表示这个例子:
C++算法:单源最短路径Dijkstra_第1张图片
在上图所示的带权有向无环图中,共有10个顶点,我们可以将其理解成路口,有19条带权边,我们可以理解成各段路及距离。现在假设起点是节点0,终点是节点9。我们要找出这两点之间的最短路径应该怎么办?

根据贪心算法的思想,假设我们从起点开始,取每个与起点最近的顶点来看,那结果是0–4–7–8–6–9。距离和是21显然这样得到的路径不是最优的,这个路径还不如0–3–2–9(距离和19)事实上简单的贪心算法思想往往能得到一个不算最差的解。

dijkstra算法也是贪心算法思想,但不能简单的直接每个顶点取最小权值的边。它的思想是:起点到终点的权值和最小,那么起点到最短路径的终点间的任一顶点的权值和也是最小的。这就有点加入动态规划的子问题最优解的思想了。那么我们只要找出这条最短路径上的每个顶点就可以了。

核心思想就是从起点开始不断往外查找,直到查找完所有顶点。初始时,我们只知道起点,以及与起点直接相连的边,我们不断的根据这个边的权值访问找到的最小权值和的边,并在程序运行时不断更新这个边的末端顶点到起点的距离,保证它一定是已知的最短距离。这样当我们访问完所有顶点,自然就得到了最短路径。

二、算法实现

1、建立图

无论如何,我们首先要用代码来表示上图,很明显地二维数组是一个表示各边权值的好方法。
代码如下(示例):

#include 
#include 
#include 

using namespace std;

class Graph{
    private:
        int vertex;      //顶点数
        int** matrix;    //有向图关系矩阵
        bool* visited;    //存储是否已访问
        int* dist;        //存储到起点的权值和
        int* pre;         //存储上一个节点

    public:
        const int maxium = 10000;                //最大值,表示不存在的边
        Graph(const int edges, const int nodes, int arr[][3]){
            vertex = nodes;
            visited = new bool[vertex];    
            dist = new int[vertex];       
            pre = new int[vertex];
            matrix = new int* [vertex];          //生成有向图关系矩阵
            for (int i=0; i<vertex; ++i){
                pre[i] = -1;
                dist[i] = maxium;
                visited[i] = false;
                matrix[i] = new int[9];
                for (int j=0; j<vertex; j++){
                    matrix[i][j] = maxium;
                }
            }
            for (int i=0; i<edges; ++i){          //生成有向图关系,maxium为不连接
                matrix[arr[i][0]][arr[i][1]] = arr[i][2];
            }
        }

        ~Graph(){
            delete[] visited;
            delete[] matrix;
            delete[] pre;
            delete[] dist;
        }

看上去和以前关于图论的文章矩阵表示法的代码很像,嗯~ 事实上我就是复制的。所以充满了个人风格。这里我们定义了 maxium=10000 来表示无穷大的权值,也就是没路。int vertex; 表示顶点数。 int** matrix; 有来存储有向图的各边及权值,同样是以下标表示边。 bool* visited; 用来存储顶点是否已访问。 int* dist; 用来存储顶点到起点的权值和。int* pre; 存储从起点到本顶点的最短路径中的上一个顶点,最终可以根据这个数组得到任两个顶点间的最短路径。

2、代码实现

代码如下(示例):

        void dijkstra(int s, int end){
            dist[s] = 0;
            visited[s] = true;
            for (int i=0; i<vertex; i++){   //与起点相连的节点到起点的距离
                if (matrix[s][i] < maxium){
                    dist[i] = matrix[s][i];
                    pre[i] = s;
                }
            }
            int curr = s;
            for (int i=0; i<vertex; ++i){  //开始寻找
                int tmp = maxium;
                for (int j=0; j<vertex; j++){   //找出离起点最小权值的点,命名为curr
                    if (!visited[j] && dist[j]<tmp){
                        tmp = dist[j];
                        curr = j;
                    }
                }
                visited[curr] = true;  //开始访问前面找到的离起点最近点curr
                for (int k=0; k<vertex; ++k){
                    int new_dist = maxium;   //更新权值
                    if (!visited[k] && matrix[curr][k] < maxium){
                        new_dist = dist[curr] + matrix[curr][k];
                        if (new_dist < dist[k]){
                            dist[k] = new_dist;
                            pre[k] = curr;    //记录当前顶点的前驱
                        }
                    }
                }
            }
            show(s, end);
        }

        void show(int start, int end){  //显示路径
            stack<int> out;
            int n = end;
            while (n != start){   //将各顶点前驱压入栈
                out.push(n);
                n = pre[n];
            }
            out.push(start);
            vector<int> path;   //倒入向量,使顺序正常
            while (out.size()){
                path.push_back(out.top());
                out.pop();
            }
            for (int i=0; i<path.size(); ++i){
                cout << path[i] << " ";
            }
            cout << endl;
        }
};

代码中Dijkstra函数就是实现算法的函数。函数有两个参数s和end,分别表示起始节点和结束节点。函数将所有节点从起始节点的距离初始化为无穷大,除了起始节点,它被初始化为0。然后它找到距离起始节点最近的节点并更新其邻居的距离。这个过程重复进行,直到所有节点都被访问过。matrix 变量表示图的邻接矩阵,其中每个元素表示两个节点之间的边的权重。dist 数组存储每个节点到起始节点的距离,而 pre存储最短路径中每个节点的前一个节点。

show 函数用于打印从起始节点到结束节点的最短路径。


总结

本文实现了任两点间的有向无环图的最短路径计算。Dijkstra算法是一种用于在图中查找两个节点之间的最短路径的算法。该算法使用贪心策略,每次找到距离起始节点最近的节点并更新其邻居的距离。这个过程重复进行,直到所有节点都被访问过。

该算法的时间复杂度为O(V^2),其中V是节点数。如果使用堆优化,时间复杂度可以降低到O(ElogV),其中E是边数。

在实现中,我们使用邻接矩阵来表示图,其中每个元素表示两个节点之间的边的权重。以下是测试代码:

int main(){
    int arr[][3] = {{0,1,8},{0,3,16,},{0,4,7},{1,3,9},{1,5,5},{2,9,2},
                   {3,2,1},{3,6,10},{3,8,12},{4,7,5},{4,3,9},{4,8,7},{5,3,2},
                   {5,2,11},{6,2,13},{6,9,2},{7,6,8},{8,7,1},{8,6,6}};

    Graph t(19, 10, arr);
    t.dijkstra(0, 9);
    return 0;
}

最终结果是:0 1 5 3 2 9

你可能感兴趣的:(数据结构与算法,算法,c++,贪心算法)