dijkstra应用场景 深度优先和广度优先,主要针对无权图的搜索。
针对有向有权图,也就是图中的每条边都有一个权重,使用的是dijkstra算法。
dijkstra建模 点(GraphNode) 注意dist属性,它是距离开始节点的最短路径距离。
1 2 3 4 5 6 7 class GraphNode { constructor (key ) { this .key = key; this .edges = []; this .dist = 0 ; } }
边(GraphEdge) 1 2 3 4 5 6 7 8 class GraphEdge { constructor (skey, tkey, weight ) { this .skey = skey; this .tkey = tkey; this .weight = weight; } }
图(Graph) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Graph { constructor ( ) { this .nodes = {}; } addEdge (skey, tkey, w ) { this .nodes [skey].edges .push (new GraphEdge (skey, tkey, w)); } }
最小堆 这里的最小堆主要用于实现优先队列,它能在O(1) 时间内,快速得到当前距离源点最近的节点。
堆使用heap.js
实现:
1 2 3 4 5 6 7 8 9 10 11 const { Heap } = require ("heap-js" );class DistPriorityQueue { constructor ( ) { this .minHeap = new Heap (this .heapComparator ); } heapComparator (a, b ) { return a.dist - b.dist ; } }
dijkstra算法实现
实现思路 这里简单记录大体的思路,具体可见上图。
初始化所有的节点,除了开始节点本身dist设置为0,其他节点的dist均为最大值(方便之后比较)。同时将开始节点入优先队列。
从优先队列中取出 距离开始节点最近的节点,检查此节点是否是目标节点,如果是,那么说明已经到达目的地,此时为最短距离,结束算法。
否则,开始尝试往下继续走,访问此节点的邻居节点:
当前节点的dist +邻边长度 < 邻边节点的dist,那么说明从当前节点过来距离更短,更新邻边节点的dist值。
如果队列中存在邻居节点,那么就更新update(其实就是更新节点的dist之后,对堆重新排序);如果不存在,那么就将其放入队列中。
当前节点的dist +邻边长度 ≥ 邻边节点的dist,那么说明这条路更长,没有必要走,不做任何处理。
一直持续,直到走到目的节点。
🌟🌟🌟优先队列的作用 能够快速拿到目前距离开始节点最近的节点(堆顶元素),从而每次都从最近的节点出发,一步步遍历到目的节点。
实现代码 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 function dijkstra (graph, skey, tkey ) { const pathNodes = {}; for (const key in graph.nodes ) { const node = graph.nodes [key]; node.dist = key === skey ? 0 : Number .MAX_VALUE ; } const queue = new DistPriorityQueue (); queue.minHeap .push (graph.nodes [skey]); const visited = {}; visited[skey] = true ; while (queue.minHeap .size ()) { const minDistNode = queue.minHeap .pop (); if (minDistNode.key === tkey) { break ; } for (const edge of minDistNode.edges ) { const linkedNode = graph.nodes [edge.tkey ]; if (minDistNode.dist + edge.weight < linkedNode.dist ) { linkedNode.dist = minDistNode.dist + edge.weight ; pathNodes[linkedNode.key ] = minDistNode.key ; if (visited[linkedNode.key ]) { queue.minHeap .remove (linkedNode); queue.minHeap .push (linkedNode); } else { queue.minHeap .push (linkedNode); visited[linkedNode.key ] = true ; } } } } console .log (">>> 路径是" , pathNodes); }
测试代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const a = new GraphNode ("a" );const b = new GraphNode ("b" );const c = new GraphNode ("c" );const d = new GraphNode ("d" );const e = new GraphNode ("e" );const graph = new Graph ();graph.nodes = { a, b, c, d, e, }; graph.addEdge ("a" , "b" , 3 ); graph.addEdge ("a" , "c" , 1 ); graph.addEdge ("b" , "e" , 2 ); graph.addEdge ("c" , "d" , 1 ); graph.addEdge ("d" , "e" , 2 ); dijkstra (graph, "a" , "e" );
实际地图项目-近似最短路径 实际工程应用中的取舍 对于实际地图,考虑全部的点和边固然可以得到最有解,但是计算复杂度高O(E*logV)
一般在实际工程中,只要取个近似解即可。
如何取近似解? 我们发现,最优路径一般在两个点之间的连线,不会向“外”绕。
分2种情况:
两个点距离比较近(比如5公里),那么根据2个点做直线,并且点是直线中点。 然后勾出一个矩形,根据规律只考虑矩形内的路径和点即可
两个点距离比较远(比如500公里),那么按照方法1,也会有很多的路径和点。 此时,只考虑国道、省道等主干道即可。 类似高德地图,当地图缩小到一定比例,无关紧要的点和路径,都不再显示。当行驶过程中,动态加载计算附近的路径和点即可(详细地)。
这两种处理思路的主旨是忽略一些不重要的路径和点。
参考 极客时间-数据结构与算法之美