Dijkstra算法 (迪杰斯特拉算法) (图 / 最短路径 / 加强堆)

迪杰斯特拉算法是由荷兰计算机科学家在1956年发现的算法,此算法使用类似广度优先搜索的方法解决了带权图的单源最短路径问题。它是一个贪心算法。

思路

在这里插入图片描述

代码实现

普通实现

package graph;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class Code06_Dijkstra {
    public HashMap<Node, Integer> dijkstra1(Node from) {
        // 路径的集合,key:节点 value:到达这个点的路径长度
        HashMap<Node, Integer> distanceMap = new HashMap<>();
        distanceMap.put(from, 0); // 起点到自己的距离为0
        // 已经计算完成后的点的集合
        HashSet<Node> selectedNodes = new HashSet<>();
        // 从路径集合中,选择一个路径最短并且没有被计算完成的点
        Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);

        // 如果还有点没有计算完毕
        while (minNode != null) {
            // 拿到这个最短路径  原始点  ->  minNode(跳转点)   最小距离distance
            int minDistance = distanceMap.get(minNode);
            // 遍历这个点相连的边
            for (Edge edge : minNode.edges) {
                // 拿到边能到达的点
                Node toNode = edge.to;
                // 查看这个点是否以及在路径集合中
                // 如果在。说明之前被计算过,要选择最短路径的方案
                // 如果没有被计算过,说明没有添加过,是一个新的点。直接添加边的权重即可
                if (distanceMap.containsKey(toNode)) {
                    distanceMap.put(toNode, Math.min(distanceMap.get(toNode), edge.weight + minDistance));
                } else {
                    distanceMap.put(toNode, edge.weight);
                }
            }
            selectedNodes.add(minNode);
            minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
        }
        return distanceMap;
    }

    public 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) {
                minDistance = distance;
                minNode = node;
            }
        }
        return minNode;
    }
}

加强堆实现

堆排序以及加强堆讲解

package graph;

import java.util.HashMap;

public class Code06_Dijkstra_Heap {

    public static class NodeRecord {
        public Node node;
        public int distance;

        public NodeRecord(Node node, int distance) {
            this.node = node;
            this.distance = distance;
        }
    }

    static class NodeHeap {
        // 堆!
        private Node[] nodes;
        //
        // 反向索引表    key:node -> value:堆上的什么位置?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<>();
            size = 0;
        }
        
        // 判断栈是否为空
        public boolean isEmpty() {
            return size == 0;
        }
        
        // 如果一个节点从来都没有加入过,那么就是add
        // 如果一个节点在堆中,那么就是update
        // 如果两个都不是,说明当前节点已经被移出了堆,计算完毕,那么就是ignore
        public void addOrUpdateOrIgnore(Node head, int distance) {
            // 如果已经在堆中了,就执行update的流程
            if (inHeap(head)) {
                distanceMap.put(head, Math.min(distanceMap.get(head), distance));
                insertHeapify(head, heapIndexMap.get(head));
            } 
            // 如果不在堆中,并且也没有加入过,那么就走add流程
            if (!isEntered(head)) {
                nodes[size] = head;
                heapIndexMap.put(head, size);
                distanceMap.put(head, distance);
                insertHeapify(head, size++);
            }
            // 不是update和add,那么就直接ignore
        }

        // 弹出栈顶的方法
        public NodeRecord pop() {
            NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
            // 移出栈顶元素,但是在反向索引表中不删除,而是将它的下标设置为-1
            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;
            }
        }


        // 判断一个节点是否加入过,这里规定如果一个节点移出了堆,将他在反向索引表中的下标设置为-1,而不是真的删除
        private boolean isEntered(Node node) {
            // 如果反向索引表中存在这个节点的记录,那么说明这个节点加入过堆中
            return heapIndexMap.containsKey(node);
        }

        // 判断一个节点是否还在堆中
        private boolean inHeap(Node node) {
            // 节点加入过,并且索引不为-1
            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 temp = nodes[index1];
            nodes[index1] = nodes[index2];
            nodes[index2] = temp;
        }
    }

    // 从head除法,所有head能够到达的节点,生成达到每个节点的最小路径记录并返回
    public static HashMap<Node, Integer> dijkstra(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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值