Dijkstra 演算法說明與實現
阿新 • • 發佈:2022-12-10
Dijkstra 演算法說明與實現
作者:Grey
原文地址:
問題描述
問題:給定出發點,出發點到所有點的距離之和最小是多少?
注:Dijkstra 演算法必須指定一個源點,每個邊的權值均為非負數,求這個點到其他所有點的最短距離,到不了則為正無窮, 不能有累加和為負數的環。
題目連結見:LeetCode 743. Network Delay Time
主要思路
-
生成一個源點到各個點的最小距離表,一開始只有一條記錄,即原點到自己的最小距離為0, 源點到其他所有點的最小距離都為正無窮大
-
從距離表中拿出沒拿過記錄裡的最小記錄,通過這個點發出的邊,更新源 點到各個點的最小距離表,不斷重複這一步
-
源點到所有的點記錄如果都被拿過一遍,過程停止,最小距離表得到了。
關鍵優化:加強堆結構說明
完整程式碼見:
class Solution { public static int networkDelayTime(int[][] times, int N, int K) { Graph graph = generate(times); Node from = null; for (Node n : graph.nodes.values()) { if (n.value == K) { from = n; } } HashMap<Node, Integer> map = dijkstra2(from, N); int sum = -1; for (Map.Entry<Node, Integer> entry : map.entrySet()) { if (entry.getValue() == 0) { N--; continue; } N--; if (entry.getValue() == Integer.MAX_VALUE) { return -1; } else { sum = Math.max(entry.getValue(), sum); } } // 防止出現環的形狀 // int[][] times = new int[][]{{1, 2, 1}, {2, 3, 2}, {1, 3, 1}}; // int N = 3; // int K = 2; if (N != 0) { return -1; } return sum; } public static Graph generate(int[][] times) { Graph graph = new Graph(); for (int[] time : times) { int from = time[0]; int to = time[1]; int weight = time[2]; if (!graph.nodes.containsKey(from)) { graph.nodes.put(from, new Node(from)); } if (!graph.nodes.containsKey(to)) { graph.nodes.put(to, new Node(to)); } Node fromNode = graph.nodes.get(from); Node toNode = graph.nodes.get(to); Edge fromToEdge = new Edge(weight, fromNode, toNode); //Edge toFromEdge = new Edge(weight, toNode, fromNode); fromNode.nexts.add(toNode); fromNode.out++; //fromNode.in++; //toNode.out++; toNode.in++; fromNode.edges.add(fromToEdge); //toNode.edges.add(toFromEdge); graph.edges.add(fromToEdge); //graph.edges.add(toFromEdge); } return graph; } public static class Graph { public HashMap<Integer, Node> nodes; public HashSet<Edge> edges; public Graph() { nodes = new HashMap<>(); edges = new HashSet<>(); } } public static class Edge { public int weight; public Node from; public Node to; public Edge(int weight, Node from, Node to) { this.weight = weight; this.from = from; this.to = to; } } public static class Node { public int value; public int in; public int out; public ArrayList<Node> nexts; public ArrayList<Edge> edges; public Node(int value) { this.value = value; in = 0; out = 0; nexts = new ArrayList<>(); edges = new ArrayList<>(); } } public static Node getMinNode(HashMap<Node, Integer> distanceMap, HashSet<Node> selectedNodes) { int minDistance = Integer.MAX_VALUE; Node minNode = null; for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) { Node n = entry.getKey(); int distance = entry.getValue(); if (!selectedNodes.contains(n) && distance < minDistance) { minDistance = distance; minNode = n; } } return minNode; } public static class NodeRecord { public Node node; public int distance; public NodeRecord(Node node, int distance) { this.node = node; this.distance = distance; } } public static class NodeHeap { private Node[] nodes; // 實際的堆結構 // key 某一個node, value 上面堆中的位置 private HashMap<Node, Integer> heapIndexMap; // key 某一個節點, value 從源節點出發到該節點的目前最小距離 private HashMap<Node, Integer> distanceMap; private int size; // 堆上有多少個點 public NodeHeap(int size) { nodes = new Node[size]; heapIndexMap = new HashMap<>(); distanceMap = new HashMap<>(); size = 0; } public boolean isEmpty() { return size == 0; } // 有一個點叫node,現在發現了一個從源節點出發到達node的距離為distance // 判斷要不要更新,如果需要的話,就更新 public void addOrUpdateOrIgnore(Node node, int distance) { if (inHeap(node)) { distanceMap.put(node, Math.min(distanceMap.get(node), distance)); insertHeapify(node, heapIndexMap.get(node)); } if (!isEntered(node)) { nodes[size] = node; heapIndexMap.put(node, size); distanceMap.put(node, distance); insertHeapify(node, size++); } } public NodeRecord pop() { NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0])); swap(0, size - 1); heapIndexMap.put(nodes[size - 1], -1); distanceMap.remove(nodes[size - 1]); // free C++同學還要把原本堆頂節點析構,對java同學不必 nodes[size - 1] = null; heapify(0, --size); return nodeRecord; } private void insertHeapify(Node node, int index) { while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) { swap(index, (index - 1) / 2); index = (index - 1) / 2; } } private void heapify(int index, int size) { int left = index * 2 + 1; while (left < size) { int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left]) ? left + 1 : left; smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index; if (smallest == index) { break; } swap(smallest, index); index = smallest; left = index * 2 + 1; } } private boolean isEntered(Node node) { return heapIndexMap.containsKey(node); } private boolean inHeap(Node node) { return isEntered(node) && heapIndexMap.get(node) != -1; } private void swap(int index1, int index2) { heapIndexMap.put(nodes[index1], index2); heapIndexMap.put(nodes[index2], index1); Node tmp = nodes[index1]; nodes[index1] = nodes[index2]; nodes[index2] = tmp; } } // 改進後的dijkstra演算法 // 從head出發,所有head能到達的節點,生成到達每個節點的最小路徑記錄並返回 public static HashMap<Node, Integer> dijkstra2(Node head, int size) { NodeHeap nodeHeap = new NodeHeap(size); nodeHeap.addOrUpdateOrIgnore(head, 0); HashMap<Node, Integer> result = new HashMap<>(); while (!nodeHeap.isEmpty()) { NodeRecord record = nodeHeap.pop(); Node cur = record.node; int distance = record.distance; for (Edge edge : cur.edges) { nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance); } result.put(cur, distance); } return result; } }
程式碼說明:本題未採用題目給的二維陣列的圖結構,而是把二維陣列轉換成自己熟悉的圖結構,再進行dijkstra演算法。