十二、图的算法入门--(4)最短路问题---Dijkstra算法实现

摘自计蒜客:http://www.jisuanke.com/course/35/7557

先来看这样一个问题:有n座城市,已知任意两个座城市之间的距离,现在要分别求出城市A到其他n-1座城市的最短路径,

也就是求所经过的距离和的最小值。

这是一个经典的单源最短路问题,即求一起点到其余各个顶点的最短路径问题。

首先,我们可以把该场景看成是一个带权图,把n个城市看成n个顶点,把两座城市之间的距离看成是两个顶点之间的边权值,

这样问题就转化成了求顶点A到其余n-1个顶点的最短路径。

Dijkstra算法是最常见的求解单源最短路问题的算法。

先来看看 Dijkstra 算法的具体过程:

定义带权图 GG 所有顶点的集合为 VV,接着我们再定义已确定最短路径的顶点集合为 UU,初始集合 UU 为空。接着执行以下操作:

  1. 首先我们将起点 xx 加入集合 UU,并在数组 AA 中记录起点 xx 到各个点的最短路径(如果顶点到起点 xx 有直接相连的边,则最短路径为边权值,否则为一个极大值)。

  2. 从数组 AA 中选择一个距离起点 xx 最近的、且不属于集合 UU 的顶点 vv(如果有多个顶点 vv,任选其一即可),将顶点 vv 加入集合 UU,并更新所有与顶点 vv 相连的顶点到起点xx 的最短路径。

  3. 重复第二步操作,直至集合 UU 等于集合 VV

算法结束,数组 AA 记录了起点 xx 到其余 n – 1n1 个点的最短路径。

下面我们用一个小例子来模拟下 Dijkstra 算法吧。

《十二、图的算法入门--(4)最短路问题---Dijkstra算法实现》

通过模拟,最终我们可以得到起点 aa 到各点的最短路径分别为:

l: 5 (a -> l)l:5(a>l)

e: 8 (a -> l -> e)e:8(a>l>e)

r: 7 (a -> l -> r)r:7(a>l>r)

b: 9 (a -> l -> e -> b)b:9(a>l>e>b)

仔细分析算法,我们可以发现,Dijkstra 算法和前面讲解的 Prim 算法很相像,都是从一个点开始,每次确定一个点并完成更新,重复操作直至 nn 个点都确定为止。Dijkstra 算法的时间复杂度为 O(V^2+E)O(V2+E)VV 为顶点总个数,EE 为总边数。如果利用堆进行优化,可以将时间复杂度优化 O(VlogV+E)O(VlogV+E),是最坏情况下最优的单源最短路算法。

需要注意的是,Dijkstra 不适用于有边权为负数的情况哦,否则会影响算法的正确性。

算法实现:计算一个带权无向图中从顶点0出发,到所有顶点的最短路径长度。

比如下图:

《十二、图的算法入门--(4)最短路问题---Dijkstra算法实现》

在这个图中,从a出发到e的最短路a->l->e,长度是8;到b的最短路是a->l->e->b,长度为9;

代码如下:

#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

const int INF = 0x3f3f3f3f;

struct Edge {
	int vertex, weight;
};

class Graph {
private:
	int n;
	vector<Edge> * edges;
    bool * visited;
public:
	int * dist;
	Graph (int input_n) {
		n = input_n;
		edges = new vector<Edge>[n];
		dist = new int[n];
        visited = new bool[n];
        memset(visited, 0, n);
		memset(dist, 0x3f, n * sizeof(int));
	}
	~Graph() {
		delete[] dist;
		delete[] edges;
        delete[] visited;
	}
    void insert(int x, int y, int weight) {
        edges[x].push_back(Edge{y, weight});
        edges[y].push_back(Edge{x, weight});
    }
    void dijkstra(int v) {
        //[1]初始化
        dist[v] = 0;
        //[2]n次循环,每次找出从源点出发最短路径最小的未访问的顶点,标记为已访问,之后更新
        //和这个顶点相邻的所有顶点的最短路。
        for(int i=0; i<n; i++) {
            int min_dist = INF, min_vertex;
            //[3]找出所有未访问顶点中从源点出发最短路径最小的,并将其保存。
            for(int j=0; j<n; ++j) {
            //[4]如果顶点j未访问且最短路径比min_dist记录的更小,那么就用源点到顶点j的最短路径更新
            //min_dist,然后将顶点编号保存到min_vertex中。
                if(!visited[j] && dist[j] < min_dist) {
                    min_dist = dist[j];
                    min_vertex = j;
                }
            }
            //[5]将min_vertex点的访问标志置为1
            visited[min_vertex] = 1;
            //[6]松弛操作:对于已经找到的当前最近顶点min_vertex,如果源点到它的最短路径长度
            //min_dist加上和min_vertex相邻边的边权小于源点到该边另一端的最短路径,那么就
            //更新另一端的最短路径,也就是dist数组中对应的值。
            for(Edge &j:edges[min_vertex]) {
                if(min_dist+j.weight < dist[j.vertex]) {
                    dist[j.vertex] = min_dist + j.weight;
                }
            }
        }
    }
};

int main() {
	int n, m;
	cin >> n >> m;
	Graph g(n);
	for (int i = 0; i < m; i++) {
		int a, b, c;
		cin >> a >> b >> c;
		g.insert(a, b, c);
	}
	g.dijkstra(0);
	for (int i = 0; i < n; i++) {
		cout << i << ": " << g.dist[i] << endl;
	}
	return 0;
}

    原文作者:Dijkstra算法
    原文地址: https://blog.csdn.net/firetreesf/article/details/51354034
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞