BFS和DFS ( 3 ) —— 从BFS到Dijkstra
参考:https://www.bilibili.com/video/av25829980?t=520
一,从 BFS 到 Dijkstra 算法
1,Dijkstra 算法原理
保证边是非负的,那么长度长的最短路径一定是在长度短的最短路径的基础上延伸出来的。
2,优先队列
如果将 BFS 中的队列改成优先队列,并将所有点到源点的距离作为优先级的比较依据,距离较短的距离有较高的优先级。
这样就可以在遍历的时候,实现从距离近的点开始遍历。
3,从 BFS 到 Dijkstra 算法
优先队列的遍历顺序正好符合 Dijkstra 的算法原理,即两者是等效的。于是,根据 BFS 生成树,就可以求出根节点到其余所有点的最短路径。
二,代码实现
1,记录 BFS 生成树
p[ ]:
p[ i ] 代表 点i 到源点的最短路径上的前一个点的编号。
用来记录 BFS 生成树的边。
dist[ ]:
dis[ i ] 代表 点i 到达源点的最短路径的距离。
用来记录 BFS 生成树的点的值。
2,注意点
① 因为比较的是点到源点的距离,所以优先队列里面对于每个点都要存的信息是:点的编号与点到源点的最短距离。
② 需要在 next 的入队列条件加上 “next.dis < dis[next.id]” 的原因。
因为源点到达 点next 的路径可能不止一条。所以要比较新的路径与原来的路径的距离,保证 dis[] 和 p[] 记录的是源点到达 点next 的所有路径中最短的一条。
③ 为什么要在出队列的时候才能标记为成功遍历了该点呢?不带权的 BFS 又能否在出队列的时候再标记为该点成功遍历呢?
Ⅰ,因为源点到达 点next 的路径可能不止一条,所以如果后面遍历的路径到达 点next 更短,则 点next 则会多次入队列。根据上述 Dijkstra 算法的原理,只有从短的路径开始遍历,等到 点next 成为队列中最短的时候,此时 点next 的距离才是真正源点到 点next 的最短距离,此时才能算是真正的遍历了该点,所以需要在某个点出队列的时候才能标记为成功遍历了该点。
Ⅱ,不能。因为正常的 BFS 没有优先队列的自动排序功能,而且也无法在入队列的时候比较距离。所以,如果同一个点多次入队列的话,那么 p[] 记录的必然是最后一个入队列的点,很明显,最先入队列的才是最短距离,所以会发生错误。
3,代码
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<vector> #include<queue> #include<stack> #include<map> using namespace std; #define N 110 #define inf 0x3f3f3f3f struct Node // 存点的信息,并将 < 号的重载 { char id; // 点的编号 int dis; // 点到源点的最短距离 Node(int a, int b) :id(a), dis(b) {} friend bool operator < (const Node &a, const Node &b) { return a.dis > b.dis; } }; map<char, vector<Node>>v; // 邻接表 map<char, char>p; // 记录 BFS 树 map<char, int>dis; // 记录 点i 到源点的最短距离 vector<char>vis; // 记录在 BFS 过程中 点i 有无被遍历过 void BFS(char start) { // 初始化 vis.clear(); p.clear(); for (auto i = v.begin(); i != v.end(); i++) dis[(*i).first] = inf; // 创建队列,并添加起点 priority_queue<Node>q; q.push(Node(start, 0)); dis[start] = 0; p[start] = 0; while (q.size()) { // 队首出队列 Node vertex = q.top(); q.pop(); vis.push_back(vertex.id); // 取 vertex 的所有邻接点 vector<Node>vv = v[vertex.id]; for (int i = 0; i < vv.size(); i++) { Node next = { vv[i].id, vertex.dis + vv[i].dis }; // 注意:队列里面放的是点到源点的距离 auto ret = find(vis.begin(), vis.end(), next.id); // 如果 next 没有遍历过的话, 并且该次遍历顺序的 点next 到源点的最短距离比原先还短。 if (ret == vis.end() && next.dis < dis[next.id]) { p[next.id] = vertex.id; // 记录路径 dis[next.id] = next.dis; // 记录距离 q.push(next); } } } } void find(char i) { if (p[i] != 0) { find(p[i]); printf("->%c", i); } else printf("%c", i); } int main(void) { int n, m; while (scanf("%d%d", &n, &m) != EOF) { v.clear(); // 清空邻接表 char s, s1, s2; scanf("%c%c%c", &s1, &s, &s2); // 起点 for (int i = 0; i < m; i++) { char t1, t2, t3, t4; int w; scanf("%c%c%c%d%c", &t1, &t3, &t2, &w, &t4); vector<Node>vv = v[t1]; // 取出 t1 原来的所有邻接点 vv.push_back(Node(t2, w)); // 增加一个邻接点 v[t1] = vv; // 覆盖掉原来的邻接点 if (t1 != t2) // 无向图 { vv = v[t2]; vv.push_back(Node(t1, w)); v[t2] = vv; } } BFS(s); // 输出最短路径 for (auto i = v.begin(); i != v.end(); i++) { if ((*i).first == s) continue; printf("%c 到 %c 的最短距离为:%2d,最短路径为:", s, (*i).first, dis[(*i).first]); find((*i).first); puts(""); } } system("pause"); return 0; } /* 测试数据: 6 8 A A B 5 A C 1 B C 2 B D 1 C D 4 C E 8 D E 3 D F 6 测试结果: A 到 B 的最短距离为: 3,最短路径为:A->C->B A 到 C 的最短距离为: 1,最短路径为:A->C A 到 D 的最短距离为: 4,最短路径为:A->C->B->D A 到 E 的最短距离为: 7,最短路径为:A->C->B->D->E A 到 F 的最短距离为:10,最短路径为:A->C->B->D->F */
========== ========= ===== ======= ====== ===== ==== === == =
木玉成约 叶迷