zoukankan      html  css  js  c++  java
  • 基于STL优先队列和邻接表的dijkstra算法

    首先说下STL优先队列的局限性,那就是只提供入队、出队、取得队首元素的值的功能,而dijkstra算法的堆优化需要能够随机访问队列中某个节点(来更新源点节点的最短距离)。

    看似可以用vector配合make_heap/push_heap/pop_heap来实现这个功能,实际上手动实现就会发现问题所在。比如在dist[v] > dist[u] + cost(u,v)时,需要更新dist[v],然后重新确定v在vector的位置,需要使用push_heap,这样问题就出现了。

    v又在vector的哪个位置呢?只有在vector中一个个查找,除非在之前维护最小(距离)堆的时候,每次交换元素,记录元素的位置变化,也就是用int pos[V];(V为顶点数,下面不再重复说明)来记录,每次push_heap和pop_heap使堆的元素交换的时候(swap(heap[i], heap[j];)还要顺便交换位置(swap(pos[i], pos[j]);)

    而仅仅是用STL提供的接口是无法实现的,只有从头造轮子。

    于是有个折中的方法,那就是仍然使用优先队列。只是在更新点v的最短距离时,把点v重新加入队列中,而队列中已经存在的v无法访问就继续搁着。

    也就是说队列中有2个点v,一个是用更新后的距离进行堆操作的,一个是用更新前的距离进行堆操作的。

    首先我不是用while (!q.empty())判断终止条件的,而是照着书上的for (int i = 0; i < V; i++)判断,这样问题就在于,可能点v已经出队了(代表着已经确定源点到点v的最短路径),此时若点v出队则需要跳过。

    书上之所以只循环V-1次是因为书上用的堆优化,不会像我这样重复添加某元素到堆中,而是更新堆中元素的权值并移动位置。由于每次循环都能确定源点到某个点的最短路径,所以只需要V-1次足矣。

    而退而求其次的直接用优先队列的做法也可以直接循环V-1次,只不过每次循环开头要判断队首元素是否已经确定了最短距离,若是则弹出,一直到队首元素是未确定最短距离。不如while (!q.empty())加continue简洁(见下面核心代码)

    auto comp = [](int v1, int v2) { return dist[v1] > dist[v2]; };
    priority_queue<int, vector<int>, decltype(comp)> q(comp);
    dist[v0] = 0;
    q.push(v0);
     
    while (!q.empty()) {
        int u = q.top();
        q.pop();
        if (vis[u])  // 已经求过v0到u的最短路径
            continue;
     
        vis[u] = true;
        for (auto& e : adjList[u]) {
            int v = e.adjID;
            if (!vis[v] && dist[v] - dist[u] > e.len) {
                dist[v] = dist[u] + e.len;
                pre[v] = u;
                q.push(v);
            }
        }
    }   

    其他代码就不贴了,对其中用到的一些全局变量做个说明。

    注意如果dist是定义在dijkstra函数体内的,lambda表达式要捕获dist的引用,即auto comp = [&dist](后面不变)

    vector<AdjList> adjList;  // 邻接表, 预先读取了数据
    // AdjList是STL容器Container<T>的别名(Container可以是vector或list或deque),T是AdjEdge(邻接边), 定义如下(省略了构造函数)
    struct AdjEdge {
        int adjID;  // 邻接点的ID
        int len;    // 邻接边的长度
    };
    vector<int> dist(V, INT_MAX);  // 最短距离
    vector<int> pre(V, -1);        // 最短路径上的前1个节点号
    deque<bool> vis(V, false);     // 若求出了最短距离则置为true
  • 相关阅读:
    LeeCode(两数相加)
    Linux vim中移动显示横线
    JAVA各版本的区别
    LNMP一键包安装完成后的目录结构
    tp6打开和关闭调试的方式
    windows安装Thinkphp6的过程
    Composer 的安装方法(一)
    解决:libsodium-1.0.17安装失败
    有些国内的安卓APP下载不了的解决办法
    Linux 安装时不能下载的问题处理办法
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/6658092.html
Copyright © 2011-2022 走看看