pch.h 的路径加入设置的 include_2021大厂秋招题解(1)最短路径

题目

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

思路

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

•创建数组记录图中每个节点的状态 {未访问, 在队列中,已出队},初始化时所有节点均置为“未访问”,创建另一个数组记录起点到各节点的最短距离,全初始化为 -1。•创建以起点到该点的距离为排序关键码(Key)的优先级队列(Priority Queue)并将起点压入其中(起点到自身的距离为0),变更起点状态为“在队列中”。•每次弹出队列头部元素,即取当前除“已出队”节点外离起点最近的 {未访问/在队列中} 节点,将其状态设置为“已出队”并记录距离值 D_start2popped 为起点到该点的最短距离, 若该点即给定终点,则返回该距离并结束程序,否则遍历其邻居,若邻居状态为“未访问”,记刚弹出节点与该邻居距离为 D_popped2current,则目前起点到该节点最短距离为 D_start2popped+D_popped2current,变更其状态并入队。若邻居状态为“在队列中”,有两种处理方法:1) 遍历队列找到当前节点对应记录,对比D_start2popped+D_popped2current 和已有距离值,更新起点到该点的距离为两值中的较小者。2) 将该节点再次入队并记起点到其最短距离为 D_start2popped+D_popped2current,此时队列中可能含有同一节点的多份记录,但得益于优先级队列的优良特性,只有离起点最近的一个会被优先弹出,对应节点状态变更为“已出队”后其他记录值丢弃即可(通过出队时加入判定条件, 若新弹出节点是“已出队”状态,则直接忽略考察下一节点)。•循环往复直至得到起点到终点的最短距离或队列变空。通过访问“记录起点到各节点的最短距离的数组”找到并返回起点到终点的最短距离。

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

c8aaea3e6037bf8ad79dc79e96f0e34e.png

代码

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

#include #include #include #include #include enum status {untouched, in_pq, popped};// use pq for bfsint 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<:pair 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 大讲堂? 数据结构(上)[1], 数据结构(下)[2], THUOJ[3], DSA.WORKSPACE[4],十二道编程题做了不吃亏,做了不上当(不能用 STL, 各种结构全要手动实现 :)。

外加 DPV算法书[5] 及对应教学视频[6]

References

[1] 数据结构(上): https://next.xuetangx.com/course/THU08091000384[2] 数据结构(下): https://next.xuetangx.com/course/THU08091002048[3] THUOJ: https://dsa.cs.tsinghua.edu.cn/oj/[4] DSA.WORKSPACE: https://github.com/pmixer/dsa.workspace[5] Dasgupta S, Papadimitriou C H, Vazirani U V. Algorithms[M]. New York: McGraw-Hill Higher Education, 2008.: http://algorithmics.lsi.upc.edu/docs/Dasgupta-Papadimitriou-Vazirani.pdf[6] Gatech CS6515 Course: https://classroom.udacity.com/courses/ud401

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值