开发 gi 的时候,要用到一些图算法,唤起了多年前那段尘封的记忆,重温了胡凡的《算法笔记》。

Dijkstra

Dijkstra(中文译为戴克斯特拉算法) 是由荷兰计算机科学家艾兹赫尔·戴克斯特拉在1956年发现的算法。Dijkstra 算法用来解决单源最短路径问题,即给定图 G 和起点 start,通过算法得到 start 节点到其他每个节点的最短距离。
该过程可用下图来模拟。

Dijkstra_Animation.gif

具体的算法思想可用参考胡凡的《算法笔记》 P369。

感兴趣的大哥麻烦自己找找 pdf,我只有纸质书。

Javascript 实现
来自 antv 旗下的 algorithm 库 ,https://github.com/antvis/algorithm/blob/master/packages/graph/src/dijkstra.ts
值得一提的是,在 G6 中的图既不是用邻接表也不是用邻接矩阵,而是采用了如下结果

  1. interface IGraph {
  2. nodes: any[];
  3. edges: any[];
  4. }

具体算法如下:

  1. const minVertex = (
  2. D: { [key: string]: number },
  3. nodes: NodeConfig[],
  4. marks: { [key: string]: boolean },
  5. ): NodeConfig => {
  6. // 找出最小的点
  7. let minDis = Infinity;
  8. let minNode;
  9. for (let i = 0; i < nodes.length; i++) {
  10. const nodeId = nodes[i].id;
  11. if (!marks[nodeId] && D[nodeId] <= minDis) {
  12. minDis = D[nodeId];
  13. minNode = nodes[i];
  14. }
  15. }
  16. return minNode;
  17. };
  18. const dijkstra = (
  19. graphData: GraphData,
  20. source: string,
  21. directed?: boolean,
  22. weightPropertyName?: string,
  23. ) => {
  24. const { nodes = [], edges = [] } = graphData;
  25. const nodeIds = [];
  26. const marks = {};
  27. const D = {};
  28. const prevs = {}; // key: 顶点, value: 顶点的前驱点数组(可能有多条等长的最短路径)
  29. nodes.forEach((node, i) => {
  30. const id = node.id;
  31. nodeIds.push(id);
  32. D[id] = Infinity;
  33. if (id === source) D[id] = 0;
  34. });
  35. const nodeNum = nodes.length;
  36. for (let i = 0; i < nodeNum; i++) {
  37. // Process the vertices
  38. const minNode = minVertex(D, nodes, marks);
  39. const minNodeId = minNode.id;
  40. marks[minNodeId] = true;
  41. if (D[minNodeId] === Infinity) continue; // Unreachable vertices cannot be the intermediate point
  42. let relatedEdges: EdgeConfig[] = [];
  43. if (directed) relatedEdges = getOutEdgesNodeId(minNodeId, edges);
  44. else relatedEdges = getEdgesByNodeId(minNodeId, edges);
  45. relatedEdges.forEach(edge => {
  46. const edgeTarget = edge.target;
  47. const edgeSource = edge.source;
  48. const w = edgeTarget === minNodeId ? edgeSource : edgeTarget;
  49. const weight = weightPropertyName && edge[weightPropertyName] ? edge[weightPropertyName] : 1;
  50. if (D[w] > D[minNode.id] + weight) {
  51. D[w] = D[minNode.id] + weight;
  52. prevs[w] = [minNode.id];
  53. } else if (D[w] === D[minNode.id] + weight) {
  54. prevs[w].push(minNode.id);
  55. }
  56. });
  57. }
  58. prevs[source] = [source];
  59. // 每个节点存可能存在多条最短路径
  60. const paths = {};
  61. for (const target in D) {
  62. if (D[target] !== Infinity) {
  63. findAllPaths(source, target, prevs, paths);
  64. }
  65. }
  66. // 兼容之前单路径
  67. const path = {};
  68. for (const target in paths) {
  69. path[target] = paths[target][0];
  70. }
  71. return { length: D, path, allPath: paths };
  72. };

c++ 实现
其中的图使用邻接表实现,参考了《算法笔记》的思路自己写了一般,摒弃了原书中 c+STL 的写法,改用 c++ 11 的新语法实现。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. class Edge
  4. {
  5. public:
  6. int to;
  7. int cost;
  8. Edge(int to, int cost)
  9. {
  10. this->to = to;
  11. this->cost = cost;
  12. }
  13. };
  14. void Dijkstra(int start, vector<vector<Edge>> graph)
  15. {
  16. int n = graph.size();
  17. // 距离
  18. vector<int> dis(n, INT_MAX);
  19. // 已经访问过的节点
  20. vector<bool> visited(n, false);
  21. dis[start] = 0;
  22. for (int i = 0; i < n; i++)
  23. {
  24. // 寻找当前已知的最近节点
  25. int nearestNode = -1;
  26. int minDis = INT_MAX;
  27. for (int j = 0; j <= n; j++)
  28. {
  29. if (!visited[j] && dis[j] < minDis)
  30. {
  31. nearestNode = j;
  32. minDis = dis[j];
  33. }
  34. }
  35. visited[nearestNode] = true;
  36. for (Edge &edge : graph[nearestNode])
  37. {
  38. if (!visited[edge.to] && dis[nearestNode] + edge.cost < dis[edge.to])
  39. {
  40. dis[edge.to] = dis[nearestNode] + edge.cost;
  41. }
  42. }
  43. }
  44. }