1. 图

  1. 由点的集合和边的集合构成
  2. 虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达
  3. 边上可能带有权值

2. BFS

宽度优先遍历,使用队列来进行层的遍历。

  1. 利用队列实现
  2. 从源节点开始依次按宽度进队列,然后弹出
  3. 每弹出一个点,把该节点所有没有进过队列的邻接电放入队列
  4. 直到队列变空
  1. public static void bfs(Node node) {
  2. if (node == null) {
  3. return;
  4. }
  5. LinkedList<Node> queue = new LinkedList<>();
  6. HashSet<Node> set = new HashSet<>(); // 防止元素多次进入队列
  7. queue.add(node);
  8. set.add(node);
  9. while (!queue.isEmpty()) {
  10. Node cur = queue.poll();
  11. System.out.println(cur);
  12. for (Node next : cur.nexts) {
  13. // 如果当前元素没有进入过队列
  14. if (!set.contains(node)) {
  15. set.add(next);
  16. queue.add(next);
  17. }
  18. }
  19. }
  20. }

3. DFS

深度优先遍历,使用栈来进行深度遍历,我们可以使用递归来进行编码

  1. 利用栈实现
  2. 从源节点开始把节点按照深度放入栈,然后弹出
  3. 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
  4. 直到栈变空
  1. public static void dfs(Node node) {
  2. if (node == null) {
  3. return;
  4. }
  5. Stack<Node> stack = new Stack<>();
  6. HashSet<Node> set = new HashSet<>();
  7. stack.add(node);
  8. set.add(node);
  9. System.out.println(node.vaule);
  10. while (!stack.isEmpty()) {
  11. Node cur = stack.pop();
  12. for (Node next : cur.nexts) {
  13. // 当前子元素没有入过栈
  14. if (!set.contains(next)) {
  15. stack.push(cur); // 将根元素重新入栈
  16. stack.push(next); // 子元素同样入栈
  17. set.add(next); // 标记为入过栈
  18. System.out.println(next.vaule); // 输出当前子元素值
  19. break; // 结束当前根节点的子元素遍历
  20. }
  21. }
  22. }
  23. }

4. 图的拓扑排序

拓扑排序是对一个有向图构造拓扑序列,解决工程是否能顺利进行的问题。构造时有 2 种结果:

  1. 此图全部顶点被输出:说明说明图中无「环」存在, 是 AOV 网
  2. 没有输出全部顶点:说明图中有「环」存在,不是 AOV 网

AOV(Activity On Vertex Network) :一种 有向 无回路 的图

19. 图 - 图1

如下面的DAG图

19. 图 - 图2

  1. 从 DAG 图中选择一个 没有前驱(即入度为0)的顶点并输出。
  2. 从图中删除该顶点和所有以它为起点的有向边。
  3. 重复 1 和 2 直到当前的 DAG 图为空或当前图中不存在无前驱的顶点为止。后一种情况说明有向图中必然存在环。

19. 图 - 图3

于是,得到拓扑排序后的结果是 { 1, 2, 4, 3, 5 }。

通常,一个有向无环图可以有一个或多个拓扑排序序列。

拓扑排序常用来确定一个依赖关系集中,事物发生的顺序。例如,在日常工作中,可能会将项目拆分成A、B、C、D四个子部分来完成,但A依赖于B和D,C依赖于D。为了计算这个项目进行的顺序,可对这个关系集进行拓扑排序,得出一个线性的序列,则排在前面的任务就是需要先完成的任务。
注意:这里得到的排序并不是唯一的!就好像你早上穿衣服可以先穿上衣也可以先穿裤子,只要里面的衣服在外面的衣服之前穿就行。

19. 图 - 图4

如果当前节点入度为则说明此节点是首次读取,入栈并存储到结果集合中

  1. public class Node {
  2. public int value;
  3. public int in; // 入度
  4. public int out; // 出度
  5. public ArrayList<Node> nexts; // 当前节点出发有哪些邻居节点
  6. public ArrayList<Edge> edges; // 从当前节点出发有哪些边
  7. public Node(int value) {
  8. this.value = value;
  9. in = 0;
  10. out = 0;
  11. nexts = new ArrayList<>();
  12. edges = new ArrayList<>();
  13. }
  14. }
  15. public class Edge {
  16. public int weight; // 权重
  17. public Node from; // 父节点
  18. public Node to; // 子节点
  19. public Edge(int weight, Node from, Node to) {
  20. this.weight = weight;
  21. this.from = from;
  22. this.to = to;
  23. }
  24. }
  25. public class Graph {
  26. public HashMap<Integer, Node> nodes;// key存储为value value存储为Node节点
  27. public HashSet<Edge> edges;
  28. public Graph() {
  29. nodes = new HashMap<>();
  30. edges = new HashSet<>();
  31. }
  32. }
  33. public static List<Node> sortedTopology(Graph graph) {
  34. // key为节点 value为剩下的入度
  35. HashMap<Node, Integer> inMap = new HashMap<>();
  36. // 只有剩下入度为0的点,才进入队列中
  37. LinkedList<Node> queuq = new LinkedList<>();
  38. for (Node node : graph.nodes.values()) {
  39. inMap.put(node, node.in); // 存储每个节点的入度
  40. if (node.in == 0) {
  41. // 入队列
  42. queuq.add(node);
  43. }
  44. }
  45. List<Node> result = new ArrayList<>();
  46. while (!queuq.isEmpty()) {
  47. Node cur = queuq.poll();
  48. result.add(cur);
  49. for (Node node : cur.nexts) {
  50. // 更新他的邻居的入度
  51. inMap.put(node, inMap.get(node.value) - 1);
  52. if (inMap.get(node) == 0) {
  53. // 更新后为0 入队列
  54. queuq.add(node);
  55. }
  56. }
  57. }
  58. return result;
  59. }

4.1. 127 · 拓扑排序

4.1.1. DFS解法1

统计每个节点后面共有多少连通的节点,包括邻接节点的数量,如果两节点作比较数量较大的应该拓扑序在前,数量较小的拓扑序在后。

  1. public static class DirectedGraphNode {
  2. public int label;
  3. public ArrayList<DirectedGraphNode> neighbors;
  4. public DirectedGraphNode(int x) {
  5. label = x;
  6. neighbors = new ArrayList<DirectedGraphNode>();
  7. }
  8. }
  9. public static class info {
  10. DirectedGraphNode node; //当前节点
  11. long nodes; //当前节点连同节点的数量
  12. public info(DirectedGraphNode node, long nodes) {
  13. super();
  14. this.node = node;
  15. this.nodes = nodes;
  16. }
  17. }
  18. public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
  19. // key为节点 value为节点和节点数包装类
  20. HashMap<DirectedGraphNode, info> map = new HashMap<>();
  21. //遍历拓扑结构 得到info信息存储到map中
  22. for (DirectedGraphNode directedGraphNode : graph) {
  23. f(directedGraphNode, map);
  24. }
  25. //存储所有info信息放入集合
  26. ArrayList<info> recordArr = new ArrayList<>();
  27. for (info info : map.values()) {
  28. recordArr.add(info);
  29. }
  30. //进行排序 节点数量多放前 少放后
  31. Collections.sort(recordArr, new Comparator<info>() {
  32. @Override
  33. public int compare(info o1, info o2) {
  34. // 无法强制成int 强转后出错
  35. return o1.nodes == o2.nodes ? 0 : (o1.nodes > o2.nodes ? -1 : 1);
  36. }
  37. });
  38. //结果集合
  39. ArrayList<DirectedGraphNode> res = new ArrayList<>();
  40. for (info info2 : recordArr) {
  41. res.add(info2.node);
  42. }
  43. return res;
  44. }
  45. // 返回当前节点 所有连同节点的个数的map
  46. private info f(DirectedGraphNode directedGraphNode, HashMap<DirectedGraphNode, info> map) {
  47. if (map.containsKey(directedGraphNode)) {
  48. // 如果之前有数据 则直接取出
  49. return map.get(directedGraphNode);
  50. }
  51. // 之前没算过
  52. long nodes = 0;
  53. for (DirectedGraphNode next : directedGraphNode.neighbors) {
  54. nodes += f(next, map).nodes; // 递归
  55. }
  56. info ans = new info(directedGraphNode, nodes + 1);
  57. // 更新存储数据
  58. map.put(directedGraphNode, ans);
  59. return ans;
  60. }

4.1.2. DFS解法2

我们求出每个节点的最大深度,根据每个节点的深度进行排序,深度大的放前,深度小的放后。

  1. public static class DirectedGraphNode {
  2. public int label;
  3. public ArrayList<DirectedGraphNode> neighbors;
  4. public DirectedGraphNode(int x) {
  5. label = x;
  6. neighbors = new ArrayList<DirectedGraphNode>();
  7. }
  8. }
  9. public static class info {
  10. DirectedGraphNode node; // 当前节点
  11. int deep; // 当前节点的最大深度
  12. public info(DirectedGraphNode node, int deep) {
  13. super();
  14. this.node = node;
  15. this.deep = deep;
  16. }
  17. }
  18. public ArrayList<DirectedGraphNode> topSort(ArrayList<DirectedGraphNode> graph) {
  19. HashMap<DirectedGraphNode, info> map = new HashMap<>();
  20. for (DirectedGraphNode node : graph) {
  21. f(node, map); // 求每个节点的深度
  22. }
  23. ArrayList<info> recordArr = new ArrayList<>();
  24. for (info info : map.values()) {
  25. recordArr.add(info);
  26. }
  27. Collections.sort(recordArr, (o1, o2) -> (o2.deep - o1.deep));
  28. ArrayList<DirectedGraphNode> ans = new ArrayList<>();
  29. for (info info : recordArr) {
  30. ans.add(info.node);
  31. }
  32. return ans;
  33. }
  34. private info f(DirectedGraphNode node, HashMap<DirectedGraphNode, info> map) {
  35. if (map.containsKey(node)) {
  36. // 有记录直接返回
  37. return map.get(node);
  38. }
  39. int follow = 0;
  40. for (DirectedGraphNode next : node.neighbors) {
  41. follow = Math.max(follow, f(next, map).deep);
  42. }
  43. info ans = new info(node, follow + 1); // 深度加上自身
  44. map.put(node, ans);
  45. return ans;
  46. }

5. 最小生成树

5.1. Kruskal

克鲁斯卡尔算法查找最小生成树的方法是:将连通网中所有的边按照权值大小做升序排序,从权值最小的边开始选择,只要此边不和已选择的边一起构成环路,就可以选择它组成最小生成树。对于 N 个顶点的连通网,挑选出 N-1 条符合条件的边,这些边组成的生成树就是最小生成树。

举个例子,图 1 是一个连通网,克鲁斯卡尔算法查找图 1 对应的最小生成树,需要经历以下几个步骤:

19. 图 - 图5

  1. 将连通网中的所有边按照权值大小做升序排序:
    19. 图 - 图6

  2. 从 B-D 边开始挑选,由于尚未选择任何边组成最小生成树,且 B-D 自身不会构成环路,所以 B-D 边可以组成最小生成树。
    19. 图 - 图7

  3. D-T 边不会和已选 B-D 边构成环路,可以组成最小生成树:
    19. 图 - 图8

  4. C-B 边会和已选 C-D、B-D 边构成环路,因此不能组成最小生成树:

19. 图 - 图9

  1. 直到生成所有点 或者 权值大小集合遍历完则生成完成最小生成树
    19. 图 - 图10
  • 总是从取权值最小的边开始考虑
  • 当前边要么进入最小生成树的集合,要么丢弃
  • 如果当前的边进入最小生成树的集合中不会形成环,就要当前边
  • 如果当前的边进入最小生成树的集合中会形成环,就不要当前边
  • 考察完所有边之后,最小生成树的集合也得到了

模板写法:

  1. 并查集
  2. 权值集合最小堆/进行排序
  3. 查询当前权值的两节点是否连同如无连同则连同,并合并并查集
  1. public class Node {
  2. public int value;
  3. public int in; // 入度
  4. public int out; // 出度
  5. public ArrayList<Node> nexts; // 当前节点出发有哪些邻居节点
  6. public ArrayList<Edge> edges; // 从当前节点出发有哪些边
  7. public Node(int value) {
  8. this.value = value;
  9. in = 0;
  10. out = 0;
  11. nexts = new ArrayList<>();
  12. edges = new ArrayList<>();
  13. }
  14. }
  15. public class Edge {
  16. public int weight; // 权重
  17. public Node from; // 父节点
  18. public Node to; // 子节点
  19. public Edge(int weight, Node from, Node to) {
  20. this.weight = weight;
  21. this.from = from;
  22. this.to = to;
  23. }
  24. }
  25. public class Graph {
  26. public HashMap<Integer, Node> nodes;// key存储为value value存储为Node节点
  27. public HashSet<Edge> edges;
  28. public Graph() {
  29. nodes = new HashMap<>();
  30. edges = new HashSet<>();
  31. }
  32. }
  33. public static class UnionFind {
  34. // 每个节点当前所在集合的父节点
  35. HashMap<Node, Node> fatherMap;
  36. // 每个节点当前所在集合的size
  37. HashMap<Node, Integer> sizeMap;
  38. public UnionFind() {
  39. fatherMap = new HashMap<>();
  40. sizeMap = new HashMap<>();
  41. }
  42. // 初始化
  43. public void makeSet(Collection<Node> nodes) {
  44. fatherMap.clear();
  45. sizeMap.clear();
  46. for (Node node : nodes) {
  47. fatherMap.put(node, node);
  48. sizeMap.put(node, 1);
  49. }
  50. }
  51. // 查找父节点+路径压缩
  52. public Node find(Node node) {
  53. Stack<Node> path = new Stack<>();
  54. while (node != fatherMap.get(node)) {
  55. path.add(node);
  56. node = fatherMap.get(node);
  57. }
  58. while (!path.isEmpty()) {
  59. fatherMap.put(path.pop(), node);
  60. }
  61. return node;
  62. }
  63. public boolean isSameSet(Node a, Node b) {
  64. return find(a) == find(b);
  65. }
  66. public void union(Node a, Node b) {
  67. if (a == null || b == null) {
  68. return;
  69. }
  70. Node f1 = find(a);
  71. Node f2 = find(b);
  72. if (f1 != f2) {
  73. Integer f1Size = sizeMap.get(f1);
  74. Integer f2Size = sizeMap.get(f2);
  75. if (f1Size >= f2Size) {
  76. fatherMap.put(b, a);//更新父节点
  77. sizeMap.put(a, f1Size+f2Size); //合并大小
  78. fatherMap.remove(b); //从并查集集合中移除合并后的节点
  79. } else {
  80. fatherMap.put(b, a);
  81. sizeMap.put(b, f2Size+f1Size);
  82. fatherMap.remove(a);
  83. }
  84. }
  85. }
  86. }
  87. public static Set<Edge> kruskalMST(Graph graph){
  88. UnionFind unionFind = new UnionFind();
  89. unionFind.makeSet(graph.nodes.values()); //初始化并查集
  90. //最小堆 以权值排序
  91. PriorityQueue<Edge> priorityQueue = new PriorityQueue<>((o1,o2) -> (o1.weight - o2.weight));
  92. for (Edge edge : graph.edges) {
  93. priorityQueue.add(edge); //加入堆中
  94. }
  95. Set<Edge> result = new HashSet<>();
  96. while(!priorityQueue.isEmpty()) {
  97. Edge edge = priorityQueue.poll();
  98. //查看 from 和 to 两节点是否在一个集合中 如之前合并过则不进行操作 从堆中弹出
  99. if(!unionFind.isSameSet(edge.from, edge.to)) {
  100. result.add(edge); //添加到最小生成树结果集合中
  101. unionFind.union(edge.from, edge.to); //合并两节点
  102. }
  103. }
  104. return result;
  105. }

5.2. Prim

  1. 寻找图中任意点,以它为起点,它的所有边V加入集合(优先队列)q1,设置一个boolean数组bool[]标记该位置已经确定。
  2. 从集合q1找到距离最小的那个边v1判断边另一点p是否被标记(访问),如果p被标记说明已经确定那么跳过,如果未被标(访问)记那么标记该点p,并且与p相连的未知点(未被标记)构成的边加入集合q1边v1(可以进行计算距离之类,该边构成最小生成树) .
  3. 重复1,2直到q1为空,构成最小生成树 !

19. 图 - 图11

19. 图 - 图12

因为prim从开始到结束一直是一个整体在扩散,所以不需要考虑两棵树合并的问题,在这一点实现上稍微方便了一点。

当然,要注意的是最小生成树并不唯一,甚至同一种算法生成的最小生成树都可能有所不同,但是相同的是无论生成怎样的最小生成树:

  • 能够保证所有节点连通(能够满足要求和条件)
  • 能够保证所有路径之和最小(结果和目的相同)
  • 最小生成树不唯一,可能多样的如下

19. 图 - 图13

  1. public class Node {
  2. public int value;
  3. public int in; // 入度
  4. public int out; // 出度
  5. public ArrayList<Node> nexts; // 当前节点出发有哪些邻居节点
  6. public ArrayList<Edge> edges; // 从当前节点出发有哪些边
  7. public Node(int value) {
  8. this.value = value;
  9. in = 0;
  10. out = 0;
  11. nexts = new ArrayList<>();
  12. edges = new ArrayList<>();
  13. }
  14. }
  15. public class Edge {
  16. public int weight; // 权重
  17. public Node from; // 父节点
  18. public Node to; // 子节点
  19. public Edge(int weight, Node from, Node to) {
  20. this.weight = weight;
  21. this.from = from;
  22. this.to = to;
  23. }
  24. }
  25. public class Graph {
  26. public HashMap<Integer, Node> nodes;// key存储为value value存储为Node节点
  27. public HashSet<Edge> edges;
  28. public Graph() {
  29. nodes = new HashMap<>();
  30. edges = new HashSet<>();
  31. }
  32. }
  33. public static Set<Edge> primMST(Graph graph) {
  34. // 解锁的边 放人小根堆中
  35. PriorityQueue<Edge> queue = new PriorityQueue<>((o1, o2) -> (o1.weight - o2.weight));
  36. // 有哪些点被解锁过 已经被连同过的
  37. HashSet<Node> nodeSet = new HashSet<>();
  38. // 最小生成树节点顺序集合
  39. HashSet<Edge> reslut = new HashSet<>();
  40. // 从任意点开始
  41. for (Node node : graph.nodes.values()) {
  42. // 如果当前点未被使用过
  43. if (!nodeSet.contains(node)) {
  44. nodeSet.add(node); // 添加到使用过的集合
  45. // 遍历它的邻接点 加入到小根堆中
  46. for (Edge edge : node.edges) {
  47. queue.add(edge);
  48. }
  49. while (!queue.isEmpty()) {
  50. Edge edge = queue.poll(); // 弹出解锁边中 最小
  51. Node toNode = edge.to; // 最小权值的 to节点
  52. if (!nodeSet.contains(toNode)) { // 该节点没有被解锁过 即使用当前最小边的节点 不会形成环
  53. nodeSet.add(toNode); // 加到解锁节点的集合
  54. reslut.add(edge); // 结果集合
  55. for (Edge nextEdge : toNode.edges) {
  56. queue.add(nextEdge); // 将当前的新解锁节点的邻接节点放入小根堆中
  57. }
  58. }
  59. }
  60. }
  61. break;
  62. }
  63. return reslut;
  64. }

6. 单元最短路径算法Dijkstra

狄克斯特拉算法是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。

狄克斯特拉算法默认为无负值权值边,如果多个节点成环,并且多个权值的和为负值的会出现无法得出最短路径。

假设v1源点,找从v1到其它节点的最短路径

19. 图 - 图14

  • 集合S用来存储已经找到的最短路径
  • v1到自己显然最短,故为初始最短路径

19. 图 - 图15

第一轮:从v1出发,计算v1到其它节点的距离(无法连接则用“无穷”符号)

  • 全表找最小值,发现v1到v6最短为3
  • S中添加一条最短路径:v1——v6
  • v6列标红不再考虑

19. 图 - 图16

第二轮:从v1——v6出发,计算v1通过v6到其它节点的距离

已知v1到v6为3;v6可以到v2,v4,v5;因此,v1通过v6到其它节点的距离为3+n,n为6到各节点的距离

19. 图 - 图17

  • 全表中找最小值(v6列已经删除),此时4为最小值,对应路径v1——v6——v5
  • 添加最短路径v1——v6——v5
  • v5列不再考虑

第三轮:从v1——v6——v5出发,计算v1通过v6及v5到其它节点的距离

19. 图 - 图18

第四轮:从v1——v6——v2出发,计算v1通过v6及v2到其它节点的距离

19. 图 - 图19

第五轮:从v1——v6——v4出发,计算v1通过v6及v4到其它节点的距离

19. 图 - 图20

  1. public class Node {
  2. public int value;
  3. public int in; // 入度
  4. public int out; // 出度
  5. public ArrayList<Node> nexts; // 当前节点出发有哪些邻居节点
  6. public ArrayList<Edge> edges; // 从当前节点出发有哪些边
  7. public Node(int value) {
  8. this.value = value;
  9. in = 0;
  10. out = 0;
  11. nexts = new ArrayList<>();
  12. edges = new ArrayList<>();
  13. }
  14. }
  15. public class Edge {
  16. public int weight; // 权重
  17. public Node from; // 父节点
  18. public Node to; // 子节点
  19. public Edge(int weight, Node from, Node to) {
  20. this.weight = weight;
  21. this.from = from;
  22. this.to = to;
  23. }
  24. }
  25. public class Graph {
  26. public HashMap<Integer, Node> nodes;// key存储为value value存储为Node节点
  27. public HashSet<Edge> edges;
  28. public Graph() {
  29. nodes = new HashMap<>();
  30. edges = new HashSet<>();
  31. }
  32. }
  33. public static HashMap<Node, Integer> dijkstra1(Node node) {
  34. // key为节点 value为从头节点出单源最短路径值
  35. HashMap<Node, Integer> map = new HashMap<>();
  36. map.put(node, 0); // 自身到自身为0
  37. // 存储已经是最短路径的节点
  38. HashSet<Node> selectNode = new HashSet<>();
  39. Node minNode = getMinDistanceAndUnselectedNode(map, selectNode); // 获取没有被标记过 并且是当前单源最短路径的最小值 目前为node自身 为权值0
  40. while (minNode != null) {
  41. // 获取原始点 到 minNode 的最小权值距离
  42. Integer distance = map.get(minNode);
  43. // 遍历当前minNode所有边
  44. for (Edge edge : minNode.edges) {
  45. Node toNode = edge.to; // 边指向的节点
  46. if (!map.containsKey(toNode)) {
  47. // 说明当前指向的节点没有来过 更新为当前minNode的权值 + 当前边权值
  48. map.put(toNode, distance + edge.weight);
  49. } else {
  50. // 之前有记录 查看是否能更新为最小值
  51. map.put(toNode, Math.min(map.get(toNode), distance + edge.weight));
  52. }
  53. }
  54. selectNode.add(minNode); // 查找到去往下一个节点的最短路径 将当前的出发节点 打上标记 存储到已经是最短路径的节点集合
  55. minNode = getMinDistanceAndUnselectedNode(map, selectNode); // 获取没有被标记过 并且是当前单源最短路径的最小值的节点
  56. }
  57. return map;
  58. }
  59. public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> map, HashSet<Node> touchedNodes) {
  60. Node minNode = null;
  61. int minDistance = Integer.MAX_VALUE;
  62. // 遍历单源最短路径节点和值 散列集合
  63. for (Entry<Node, Integer> entry : map.entrySet()) {
  64. Node node = entry.getKey();
  65. Integer distance = entry.getValue();
  66. // 如果当前节点没有被标记为过已经是最短路径 并且当前权值是最小
  67. if (!touchedNodes.contains(node) && distance < minDistance) {
  68. minNode = node;
  69. minDistance = distance;
  70. }
  71. }
  72. return minNode;
  73. }

6.1. 加强堆优化

  1. public class Node {
  2. public int value;
  3. public int in; // 入度
  4. public int out; // 出度
  5. public ArrayList<Node> nexts; // 当前节点出发有哪些邻居节点
  6. public ArrayList<Edge> edges; // 从当前节点出发有哪些边
  7. public Node(int value) {
  8. this.value = value;
  9. in = 0;
  10. out = 0;
  11. nexts = new ArrayList<>();
  12. edges = new ArrayList<>();
  13. }
  14. }
  15. public class Edge {
  16. public int weight; // 权重
  17. public Node from; // 父节点
  18. public Node to; // 子节点
  19. public Edge(int weight, Node from, Node to) {
  20. this.weight = weight;
  21. this.from = from;
  22. this.to = to;
  23. }
  24. }
  25. public class Graph {
  26. public HashMap<Integer, Node> nodes;// key存储为value value存储为Node节点
  27. public HashSet<Edge> edges;
  28. public Graph() {
  29. nodes = new HashMap<>();
  30. edges = new HashSet<>();
  31. }
  32. }
  33. public static class NodeRecord {
  34. Node node; // 节点
  35. int distance; // 开始源节点到当前节点最短路径的取值和
  36. public NodeRecord(Node node, int distance) {
  37. this.node = node;
  38. this.distance = distance;
  39. }
  40. }
  41. public static class NodeHeap {
  42. // 堆
  43. Node[] nodes;
  44. HashMap<Node, Integer> heapIndexMap; // 当前节点在小根堆数组的位置
  45. HashMap<Node, Integer> distanceMap; // 开始源节点到当前节点最短路径的取值和
  46. int size;
  47. public NodeHeap(int size) {
  48. this.size = size;
  49. nodes = new Node[size]; // 堆数组
  50. heapIndexMap = new HashMap<>();
  51. distanceMap = new HashMap<>();
  52. }
  53. public boolean isEmpty() {
  54. return size == 0;
  55. }
  56. // 判断当前节点是否在堆中 从索引表中查找
  57. public boolean isEnterd(Node node) {
  58. return heapIndexMap.containsKey(node);
  59. }
  60. // 判断当前节点是否被标记过为最短路径了 即之前成为过堆顶并被弹出过 如被弹过索引表中记录值应为-1
  61. public boolean inHeap(Node node) {
  62. // 在堆中 并且没有被标记为最短路径
  63. return isEnterd(node) && heapIndexMap.get(node) != -1;
  64. }
  65. // 加入堆中
  66. public void addOrUpdateOrigonre(Node node, int dijkstra) {
  67. // 更新节点 即存在与堆中 并且没有为标记为最短路径
  68. if (inHeap(node)) {
  69. // 更新为更小的权值点
  70. distanceMap.put(node, Math.min(distanceMap.get(node), dijkstra));
  71. }
  72. }
  73. // 往上移动
  74. public void insertHeapify(int index) {
  75. // 当前节点的权值 与 它的根节点比较大小 是否能上浮节点
  76. while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
  77. swap(index, (index - 1) / 2);
  78. index = (index - 1) / 2;
  79. }
  80. }
  81. // 下沉
  82. public void heapify(int index, int size) {
  83. int left = index * 2 + 1;
  84. while (left < size) {
  85. // 查找当前节点左右孩子的最小节点 判断是否能下沉
  86. int small = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
  87. ? left + 1
  88. : left;
  89. // 左右孩子的最小节点 小于 当前下标
  90. small = distanceMap.get(nodes[small]) < distanceMap.get(index) ? small : index;
  91. if (small == index) {
  92. break; // 当前自身已经是最小的 无法下沉
  93. }
  94. swap(index, small);
  95. index = small;
  96. left = index * 2 + 1;
  97. }
  98. }
  99. // 弹出堆顶 最小值
  100. public NodeRecord pop() {
  101. // 封装节点 堆顶节点 最小路径和
  102. NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
  103. swap(0, size - 1); // 将堆顶交换到堆数组尾部
  104. heapIndexMap.put(nodes[size - 1], -1); // 将节点的索引表更新为-1
  105. distanceMap.remove(nodes[size - 1]); // 不再保存 当前节点的最短路径和
  106. nodes[size - 1] = null; // 释放节点
  107. heapify(0, --size); // 下沉交换的元素 即当前堆顶
  108. return nodeRecord;
  109. }
  110. // 交换元素
  111. private void swap(int index1, int index2) {
  112. heapIndexMap.put(nodes[index1], index2); // 更新索引表
  113. heapIndexMap.put(nodes[index2], index1);
  114. Node temp = nodes[index1];
  115. nodes[index1] = nodes[index2];
  116. nodes[index2] = temp;
  117. }
  118. }
  119. /**
  120. * 加强堆实现的迪杰斯克拉算法
  121. *
  122. * @param head 开始源节点
  123. * @param size 一共有多少节点
  124. * @return 返回为散列集合 key为节点 value为从开始源节点到当前节点最短路径的取值和
  125. */
  126. public static HashMap<Node, Integer> dijkstra(Node head, int size) {
  127. NodeHeap nodeHeap = new NodeHeap(size);
  128. nodeHeap.addOrUpdateOrigonre(head, 0); // 源节点入加强堆 并且最短路径权值为0 自身到自身为0
  129. HashMap<Node, Integer> result = new HashMap<>(); // key为节点 value为从源节点到自身节点的最小路径权值和
  130. while (!nodeHeap.isEmpty()) {
  131. NodeRecord record = nodeHeap.pop(); // 弹出堆顶
  132. Node cur = record.node; // 当前节点
  133. int distance = record.distance; // 当前节点的最小路径权值和
  134. // 遍历当前节点所有邻接节点
  135. for (Edge edge : cur.edges) {
  136. // edge.to去往的节点 edge.weight为去往路径边的权值 要加上当前节点的最小路径和
  137. nodeHeap.addOrUpdateOrigonre(edge.to, edge.weight + distance);
  138. }
  139. result.put(cur, distance);
  140. }
  141. return result;
  142. }