Dijkstra算法使用了广度优先搜索 解决了赋权有向图或无向图的单源最短路径问题。算法采用了贪心策略 ,分阶段的求解这个问题,这篇文章,我们进行详细的介绍。
这篇文章中,我们以图1.1中的赋权图作为示例。箭头上的数字为权值,圆圈中的字母为顶点中的数据,这里用一个字母表示。
我们依旧使用邻接表来表示图,那么上图中的有向图可以表示为图1.2的样子,如果不理解,可以看上篇拓扑排序 中的内容。
实现步骤 首先,我们需要对每个顶点进行标记,每个顶点标记为Known(已知)的,或者标记为unKnown(未知的)。同时,每个顶点需要保留一个临时距离 dv 。这个距离实际上是使用已知顶点作为中间顶点从开始顶点 s 到 v 的最短路径的长。最后,我们记录 pv ,它是引起 dv 变化的最后的一个顶点。假如开始顶点 s 是 a ,那么用于Dijkstra算法的表的初始配置应当如图2.1所示。
Dijkstra算法按阶段进行,在每个阶段,Dijkstra算法选择一个顶点 v ,它在所有未知顶点中具有最小的dv,同时算法声明从 s 到 v 的最短路径是已知的。阶段的其余部分由dw值的更新工作组成。
图2.1表示我们初始配置,且开始节点为 s 为 a。
第一个选择的顶点是 a ,因为它在所有未知的顶点中具有最小的 dv ,dv 为0。将 a 标记为已知。既然 a 已知,那么某些表项就需要调整。观察图1.1,发现邻接到 a 的顶点有 b 和 d 。那么这两个顶点的项需要调整,调整后如图2.2所示。
继续选择一个未知的且具有最小 dv 的顶点,也就是 d ,顶点 c,e,f,g 是邻接的顶点,而且它们实际上都需要调整,如图2.3所示。
下一个被选取的顶点是 b ,其值为 2。d 是邻接的点,但已经是已知的,因此不必做对其做工作了。 e 也是邻接的点但不需要调整,因为结果 b 的值为 2+10=12。大于 e 已知的长度为3的路径。图 2.4指出这些顶点被选取以后的表。
下一个被选取的点为 c,其值为3 ,a 是邻接的点,但已知,不需要处理了。f 是邻接的点,因为 3+5<9,所以需要进行调整,如图 2.5所示。
下一个被选取的点是 e ,g 是唯一的邻接顶点,但不需要调整,因为 3+6>5。结果如图 2.6所示。
接着我们选取 g 。邻接的顶点只有 f ,因为 5+1<8,所以对 f 的距离需要下调到 6 。结果如图 2.7所示。
最后,我们选择顶点 f ,但其余所有顶点都已知,算法终止。最后的表在图 2.8中表出。
图3通过图形演示了在Dijkstra算法期间各边是如何标记为已知的以及顶点是如何更新的。顶点染成黄色说明此顶点标记为已知。
在算法结束后,我们最终得到了一张表(图 2.8)。在这张表里面,我们可以得到我们想得到的一切。 例如 a 到 f 的最短路径为6。那么如何得到 a 到 f 的实际路径呢?我们可以编写一个递归来跟踪 pv 留下的痕迹。引起 f 的 dv 变化的最后的一个顶点是 g ,那么查询 g ;引起 g 的 dv 变化的最后的一个顶点是 d ,继续查 d ;引起 d 的 dv 变化的最后的一个顶点是 a ,也就是我们的开始节点,不需要继续查了, a 到 f 的最短路径就是 a->d->g->f 。
代码实现 首先是图的顶点结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 typedef string vertex;struct Node { vertex data; int weight = 0 ; Node *next = NULL ; Node (vertex data,int weight, Node *next = NULL ) { this ->data = data; this ->weight = weight; this ->next = next; } };
其次,我们还需要维护一个像上面的表,有known,dv,和pv。 表 不考虑负边 1 2 3 4 5 6 7 const int INFINITE = -1 ; struct Box { bool known = false ; int dv = INFINITE; vertex path; };
和上一篇拓扑排序中使用的方法一致,还是使用map来存储,比较方便。
直接给出代码,关键地方都已经打好注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 map<vertex, Box> Dijkstra (vertex v,map<vertex, Node> graph) { map<vertex, Box> boxs = initBox (v, graph); while (1 ) { map<vertex, Box>::iterator it = boxs.begin (); int min = 99999999 ; vertex key; int flag = 0 ; while (it != boxs.end ()) { if (it->second->known==false && it->second->dv!= INFINITE &&it->second->dv < min) { min = it->second->dv; key = it->first; flag = 1 ; } it++; } if (flag == 0 ) { return boxs; } boxs[key]->known = true ; Node* n = graph[key]; while (n != NULL ) { if (boxs[n->data]->known == true ) { n = n->next; continue ; } int d_new = boxs[key]->dv + n->weight; int d_old = boxs[n->data]->dv; if (d_old == INFINITE || d_new < d_old) { boxs[n->data]->dv = d_new; boxs[n->data]->path = key; } n = n->next; } } }`</pre> 输出算法的结果 <pre>`string printp (vertex p, map<vertex, Box*> boxs) { if (boxs[p]->dv == 0 ) { return p+"" ; } return printp (boxs[p]->path, boxs) + "->" + p; } map<vertex, string> printfDijkstra (map<vertex, Box*> boxs) { map<vertex, string> outs; vertex aim; map<vertex, Box*>::iterator it = boxs.begin (); while (it != boxs.end ()) { if (boxs[it->first]->dv == 0 ) { aim = it->first; break ; } it++; } it = boxs.begin (); while (it != boxs.end ()) { if (it->first != aim) { string path = printp (boxs[it->first]->path, boxs) + "->" + it->first; outs.insert (pair<vertex, string>(it->first, path)); } it++; } it = boxs.begin (); while (it != boxs.end ()) { if (it->first != aim) { cout << it->first + "\t最短路径:" + to_string (boxs[it->first]->dv) + "\t路径:" + outs[it->first] << endl; } it++; } return outs; }
完整测试代码