迪杰斯特拉算法:一个很神奇的算法,这一算法解决了有向加权图的最短路径问题,该算法的条件是该图所有边的权值非负,即对于每条边(u,v),w(u,v)>=0.
算法中设置了一节点集合S,从源节点r到集合S中节点的最终最短路径的权均已确定,并设置了最小优先队列,该队列包含所有属于V-S的节点(即这些节点尚未确定最短路径的权),且以d值为关键字排列各节点。
class Dijkstra{ public static HashMap<Node, Integer> dijkstra1(Node head) { HashMap<Node, Integer> distanceMap = new HashMap<>(); distanceMap.put(head, 0); HashSet<Node> selectedNodes = new HashSet<>(); Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); while (minNode != null) { int distance = distanceMap.get(minNode); for (Edge edge : minNode.edges) { Node toNode = edge.to; if (!distanceMap.containsKey(toNode)) { distanceMap.put(toNode, distance + edge.weight); } distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight)); } selectedNodes.add(minNode); minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes); } return distanceMap; } //获得最小距离和未被选取的点 public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> touchedNodes) { Node minNode = null; int minDistance = Integer.MAX_VALUE; for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) { Node node = entry.getKey(); int distance = entry.getValue(); if (!touchedNodes.contains(node) && distance < minDistance) { minNode = node; minDistance = distance; } } 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; private HashMap<Node, Integer> heapIndexMap; private HashMap<Node, Integer> distanceMap; private int size; public NodeHeap(int size) { nodes = new Node[size]; heapIndexMap = new HashMap<>(); distanceMap = new HashMap<>(); this.size = 0; } public boolean isEmpty() { return size == 0; } 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]); 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; } } 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; } }
所有的耗时操作包括:
1.将所有的顶点插入优先级队列中,耗时为O(V)
2.从优先级队列中提取一个最小值,耗时为O(V)
3.Relax 操作对边进行d值减少,耗时为O(E);实现优先队列的方式不同,耗时不同。
使用Array,提取最小值,则时间复杂度为O(V),Relax对d值进行减少O(1),操作所有的队列中元素,时间就是O(V*V+E*1)=O(V^2)
使用最小堆,时间复杂度为O(lgV),减少key的花销O(lgV),操作所有的队列中的元素,那么时间复杂度为O(V*lgV+E*lgV)
使用Fibonacci堆,提取最小值时间复杂度为O(lgV),减少key的花销O(1),能达到O(V*lgV+E)
假设绿色的点是源点,如果用这样长度的绳子将各个节点连接起来,那么拎起绿色的球,从上往下悬挂,那些绷直的线相加就是源点到各个点的最短距离,比如绿色是源点,到其他点的最短距离分别是7,12,18,22.
需要注意的是,迪杰斯特拉算法不能处理负权重环问题。
迪杰斯特拉算法不会去看已经处理好的节点,只会处理没有看到的节点,如果已经处理的节点都是最小的值,在不存在负权值重环的情况下,是不会出现使得路径变小的情况。