最短路径


September 29, 2020

题目

给定有向图 G,每条边记 E=(A->B, D)表示从 A 点到 B 点距离为 D(非负值), 求从指定起点到终点的最短路径,若无法到达则输出 -1。

思路

依然是动态规划(DP)+贪心策略(Greedy):

关于对“在队列中”节点的两种处理方式,据个人经验,由于法 2) 避免了在队列中寻找特定元素的线性查找开销,总体速度更快(严格数学证明还没推导出来…), 且对 BFS, A* Search 均有效,三年前通过该技巧跑出了 A* search 作业的最快搜索记录:

代码

附一道此类题目(引入了哆啦A梦“任意门”的版本)的解题代码,用以演示 C++ 中优先级队列(通过堆(heap) 实现)的使用方法和上面提到的技巧的具体实现,建议使用微信电脑客户端阅读:

#include <iostream>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>

enum status {untouched, in_pq, popped};

// use pq for bfs
int main() {
    // really hope they could just provide 0 indexed graph nodes next time
    int node_num, edge_num, gate_num;
    std::cin >> node_num >> edge_num >> gate_num;
    std::vector<std::vector<std::pair<int, int> > > neighbors(node_num);

    int n1, n2, dist;
    for (int i = 0; i < edge_num; i++) {
        std::cin >> n1 >> n2 >> dist;
        n1--;
        n2--;
        neighbors[n1].push_back(std::make_pair(n2, dist));
        neighbors[n2].push_back(std::make_pair(n1, dist));
    }

    for (int i = 0; i < gate_num; i++) {
        std::cin >> n1 >> n2;
        n1--;
        n2--;
        neighbors[n1].push_back(std::make_pair(n2, 0));
    }
    // std::cout << "Got all params" << std::endl;
    // std::priority_queue<std::pair<int, int> > pq;
    std::vector<std::pair<int, int> > pq;
    int target = node_num-1;

    status *nodes_status = new status[node_num];
    memset(nodes_status, 0, node_num * sizeof(status)); // untouched

    int *shortest_dists = new int[node_num];
    // memset(nodes_status, -1, node_num * sizeof(int));
    for (int i = 0; i < node_num; i++) {
        shortest_dists[i] = -1;
    }

    pq.push_back(std::make_pair(0, 0)); //  dist to 0-th & node id
    std::push_heap(pq.begin(), pq.end());
    nodes_status[0] = in_pq;
    while (!pq.empty()) {
        int dist2start = pq[0].first, node = pq[0].second;
        std::pop_heap(pq.begin(), pq.end());
        pq.pop_back();
        if (nodes_status[node] == popped) { // duplication
            continue;
        } else {
            shortest_dists[node] = dist2start;
            nodes_status[node] = popped;
/*
            std::cout << "shortest dist to 0th node:";
            for (int i = 0; i < node_num; i++) {
                std::cout << " " << shortest_dists[i];
            }
            std::cout << std::endl;

            std::cout << "pq after pop: ";
            for (auto it = pq.begin(); it != pq.end(); it++) {
                std::cout << " (" << it->second << "," << it->first << ")";
            }
            std::cout << std::endl;
*/        
            for (auto it = neighbors[node].begin(); it != neighbors[node].end(); it++) {
                int neighbor = it->first, dist2neighbor = it->second;
                if (nodes_status[neighbor] == untouched || nodes_status[neighbor] == in_pq) {
                    pq.push_back(std::make_pair(dist2start+dist2neighbor, neighbor));
                    std::push_heap(pq.begin(), pq.end());
                    nodes_status[neighbor] = in_pq;
                } // trace back to comp and update in queue entry vs just add duplicated one
            }
/*
            std::cout << "pq after push: ";
            for (auto it = pq.begin(); it != pq.end(); it++) {
                std::cout << " (" << it->second << "," << it->first << ")";
            }
            std::cout << std::endl;
*/
        }
    }

    std::cout << shortest_dists[target] << std::endl;

    delete [] nodes_status;
    delete [] shortest_dists;

    return 0;
}

秘籍

继续推荐邓公的数据结构与算法 MOOC 大讲堂👍 数据结构(上), 数据结构(下), THUOJ, DSA.WORKSPACE,十二道编程题做了不吃亏,做了不上当(不能用 STL, 各种结构全要手动实现 :)。

外加 DPV算法书 及对应教学视频