[C++]C++ STL Dijkstra算法 带权有向图(邻接表)单源最短路径求解

单源最短路径问题求解

带权有向图(邻接表表示法)

完整源码

#include <iostream>
#include <vector>
#include <tuple>
#include <map>
using namespace std;

map<int , vector<tuple<int, int, double>>> EWD;

int main()
{
    int V, E;
    cin >> V >> E;
    for(int i = 0 ; i < E ;i++)
    {
        int v, w;
        double weight;
        cin >> v >> w >> weight;
        EWD[v].push_back(make_tuple(v, w, weight));
    }

    cout << "EdgeWeightedDigraph : " << endl;
    for(int v = 0; v < V; v++) 
    {
        cout << v << " : ";
        for(vector<tuple<int, int, double>>::iterator ii = EWD[v].begin(); ii != EWD[v].end(); ii++)
            cout << get<0>(*ii) << "->" << get<1>(*ii) << " " << get<2>(*ii) << " ";
        cout << endl;
    }

    system("pause");
}

测试用例

测试数据来源见引用[1]

文件结构:
V //顶点数
E // 边数
v w weight // 顶点 顶点 权重

数据内容:
8
15
4 5 0.35
5 4 0.35
4 7 0.37
5 7 0.28
7 5 0.28
5 1 0.32
0 4 0.38
0 2 0.26
7 3 0.39
1 3 0.29
2 7 0.34
6 2 0.40
3 6 0.52
6 0 0.58
6 4 0.93

运行结果

《[C++]C++ STL Dijkstra算法 带权有向图(邻接表)单源最短路径求解》

EdgeWeightedDigraph :
0 : 0->4 0.38  0->2 0.26
1 : 1->3 0.29
2 : 2->7 0.34
3 : 3->6 0.52
4 : 4->5 0.35  4->7 0.37
5 : 5->4 0.35  5->7 0.28  5->1 0.32
6 : 6->2 0.4  6->0 0.58  6->4 0.93
7 : 7->5 0.28  7->3 0.39

代码说明

  • 带权重边的表示
//引入 C++ STL tuple 类型 
#include <tuple>

//顶点v -> 顶点w 权重weight 
tuple<int, int, double>

//创建tuple对象
make_tuple(v, w, weight)

//获取tuple类型中的数据
get<0>(a_tuple_object)  // v
get<1>(a_tuple_object)  // w
get<2>(a_tuple_object)  // weight
  • 有向图的表示
// 引入 vector 类型 和 map
#include <vector>
#include <map>

// 邻接表表示法 表示 有向图
// int : 顶点编号
// vector : 一个带权有向边的集合
map<int , vector<tuple<int, int, double>>> EWD;

// 向图中添加边
// 邻接表表示法(直观点,看上方运行结果)
EWD[v].push_back(make_tuple(v, w, weight));
  • 关于抽象的理解
    1. 用邻接表表示图,代码实现是一个map(哈希表);
    2. 用每个顶点编号哈希索引出来的都是一个vector列表,列表本质是一些带权重的有向边
    3. 带权重的有向边包括:v顶点(from),w顶点(to)以及权重weight,代码实现是一个tuple,就是把三个不同类型的数据“绑”在一起;

Dijkstra 算法求解单源最短路径问题

使用条件

Dijstra算法求解单源最短路径问题,要求边权重非负。

算法思想

Dijkstra's algorithm initializing dist[s] to 0 and all other distTo[] entries to positive infinity. Then, it repeatedly relaxes and adds to the tree a non-tree vertex with the lowest distTo[] value, continuing until all vertices are on the tree or no non-tree vertex has a finite distTo[] value.(参考[2])

简单翻译

生成一个最短路径树:
1. 首先指定一个 起点 ,将 起点 加入最短路径树,放松 起点 指出的所有边;
2. 选取 最短路径树顶点集合 指出的所有边 中离起点最近的那条,将 那条边 指向的 非树顶点 加入最短路径树,放松 其 指出 所有的边;
3. 重复步骤2;
4. 直到 所有顶点 加入最短路径树,算法终止。

//初始条件
dist[source] =  0; // 起点的距离置为 0
dist[othetrs] = +∞; 

// 说明
distTo[w] // from s to w , sum of weight
edgeTo[w] // last edge to w

Minpq 见[3]

// relax 放松
假设起点 s 到达某个顶点 w 的distTo[w]已知了, 不妨看做是s->w ;
这时候发现如果 s 绕路到 v 再去 w 会更“近”,那么就走s->v->w,更新distTo[w],edgeTo[w] = v ;
直到从s出发可达的全部顶点的最短路径计算完毕,算法终止.

再次强调

  • distTo[] 记录的是,到达w为止的总长度;
  • edgeTo[] 记录的是,路径上的最后一个顶点(在algs4的实现代码中,记录的是最后一条边edge);

完整源码

参考algs4 DijkstraSP.java实现 具体见参考[2]

#include <iostream>
#include <vector>
#include <tuple>
#include <queue>
#include <stack>
#include <map>
using namespace std;

double distTo[100];
int edgeTo[100]; 
int V, E;

const double INF_MAX = 9999999.9;


map<int , vector<tuple<int, int, double>>> EWD;

struct GreaterThanByDist
{
    bool operator()(const int i, const int j) const
    {
        return distTo[i] > distTo[j];
    }
};
priority_queue<int, vector<int>, GreaterThanByDist> Minpq;


void relax(tuple<int, int, double> edge) 
{
    int v = get<0>(edge);
    int w = get<1>(edge);
    double weight = get<2>(edge);
    if (distTo[w] > distTo[v] + weight) {
        distTo[w] = distTo[v] + weight;
        edgeTo[w] = v;
        Minpq.push(w);
    }
}


void DijkstraSP(int s,int V)
{
    for(int v = 0 ; v < V; v++)
        distTo[v] = INF_MAX;
    distTo[s] = 0.0;

    Minpq.push(s);
    while(!Minpq.empty())
    {
        int v = Minpq.top();
        Minpq.pop();
        for(vector<tuple<int, int, double>>::iterator ii = EWD[v].begin(); 
                                                      ii != EWD[v].end(); 
                                                      ii++)
        {   relax(*ii);  }  
    }   
}

void computeSP(int source, int vertex)
{


    cout << "shortest path : ";

    DijkstraSP(source, V);
    cout << source << " to " << vertex << " ( " << distTo[vertex] << " ) " << " : " ;

    stack<int> path;
    for(int i = vertex; i != source; i = edgeTo[i])
        path.push(i);
    path.push(source);

    while(!path.empty())
    {
        cout << path.top() << " ";
        path.pop();
    }

    cout << endl;
}

void showEWD() 
{
    cout << "EdgeWeightedDigraph : " << endl;
    for(int v = 0; v < V; v++) 
    {
        cout << v << " : ";
        for(vector<tuple<int, int, double>>::iterator ii = EWD[v].begin(); 
                                                      ii != EWD[v].end(); 
                                                      ii++)
        {   cout << get<0>(*ii) << "->" << get<1>(*ii) << " " << get<2>(*ii) << " "; }
        cout << endl;
    }
}
int main()
{
    cin >> V >> E;
    for(int i = 0 ; i < E ;i++)
    {
        int v, w;
        double weight;
        cin >> v >> w >> weight;
        EWD[v].push_back(make_tuple(v, w, weight));
    }

    //showEWD(); 

    int source, vertex;
    cout << "source : ";
    cin >> source;
    cout << "vertex : ";
    cin >> vertex;      

    for(int i = 0; i < V; i++)
        computeSP(source, i);   
    system("pause");
}

运行结果

source  :
0
vertex :
7
shortest path : 0 to 0 ( 0 )  : 0
shortest path : 0 to 1 ( 1.05 )  : 0 4 5 1
shortest path : 0 to 2 ( 0.26 )  : 0 2
shortest path : 0 to 3 ( 0.99 )  : 0 2 7 3
shortest path : 0 to 4 ( 0.38 )  : 0 4
shortest path : 0 to 5 ( 0.73 )  : 0 4 5
shortest path : 0 to 6 ( 1.51 )  : 0 2 7 3 6
shortest path : 0 to 7 ( 0.6 )  : 0 2 7

代码心得

  1. 求解单源最短路径问题就是生成一颗单源最短路径树,选定起点,从集合的角度看就可以分成树-顶点(起点为首)非树-顶点两个集合;
  2. 着眼于顶点,下一个要从非树-顶点集合中被挑选加入树-顶点集合的顶点,是距离起点最近的那个点;
  3. 一个点被选中后 (体现在代码是从优先队列被删除 ),马上接着这个点指出的所有边需要被放松 ;
  4. 正因为有上面的放松动作,才为下一个待选顶点创造了比较的依据。

MST 最小生成树问题与SP最短路径简单比较

图例

《[C++]C++ STL Dijkstra算法 带权有向图(邻接表)单源最短路径求解》

对比Prim算法求解MST 和Dijkstra算法求解SP

  1. Prime算法,可以这样理解,首先任意选定顶点{0},距离这个集合最近的边比如0-1边被选入,集合变成{0,1},接着距离这个集合最近的边比如0-3边被选入,集合变成{0,1,3},最后距离这个集合最近的边2-3边被选择,集合成为{0,1,2,3},算法终止。
  2. Dijkstra算法,可以这样理解,首先必须指定起点{0}距离起点最近的顶点1被选入,集合变成{0,1},然后距离起点最近的是顶点3,,集合变成{0,1,3},最后距离起点最近的是顶点2,集合变成{0,1,2,3}
  3. 虽然从集合的角度,加入的顶点顺序都是一样的,但是最后选定的边选取顶点的规则不同 :
    1. MST最小生成树看的是新加入的顶点到集合这个整体的距离;
    2. SP最短路径看的是顶点到起点的距离;
    3. 0直接到2的距离1.5明显比经由0-3-2的走法要距离起点0更近,在本例中就是不会被放松的情况;
    4. 如果从3到2的距离是0.1,那么很明显本次的MST和SP最终的结果选定的边就会相同;
  4. 实际上,Dijkstra只要舍去放松操作,就可以看做是Prim算法,能够拿来求解MST
  5. 总结:
    1. MST最小生成树 并不能保证任意两点之间距离最短 ,MST追求永远的总的权重最小,也就是走遍全部顶点的路径长度最短;
    2. SP最短路径或者更准确的说法是 某个指定的起点到达其他点的路径最短 ,追求的是从某个具体点出发, 这个具体点到达任意点的路径都是最短的;
    3. MSTSP最终的边的集合是有可能相同的,但是这是一种巧合。

资料引用

[1] http://algs4.cs.princeton.edu/44sp/tinyEWD.txt
[2] DijkstraSP.java
http://algs4.cs.princeton.edu/44sp/
http://algs4.cs.princeton.edu/44sp/DijkstraSP.java
[3] [C++]C++ STL priority_queue IndexPriorityQueue 索引优先队列 比较器
http://blog.csdn.net/cook2eat/article/details/52455647

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