Dijkstra算法

Dijkstra算法

Dijkstra算法完成的是找到某个节点到其他各个节点的最短距离返回一个距离表,规定所有路线权重都是大于0的,一开始需要给一个点,因为完成的就是找这个点到其他各个节点的最短距离。

算法思路:

1)从已经解锁的点中找一个开始点到它最近距离的点

2)解锁这个点的所有边,更新距离表,距离表中一开始没有到达这个节点的就新增,之前有的就进行更新。

3)每个点只处理一次,进行了一次解锁边集和更新距离表后就将它锁住,返回1继续操作直到所有可达节点都处理一遍即可拿到最小距离表。

为什么Dijkstra算法能够拿到最短距离呢?

我简单说说,严格证明就不谈,我也不会。首先,从已经解锁的点中找到可以到达的最小距离。比如下面的这个图,从v1开始进行算法。

1.解锁的有v1,拿出v1,v1所能到达的点,{v6,v5,v3}解锁更新距离表为{v1:0,v6:100,v5:30,v3:10}

2.从已经解锁的点集中找到可达的最短距离的点,那就是v3,为什么要拿最小的呢?为什么一个点处理完之后就可以不管了呢?

原因是如果你是从已经解锁的点中找到的最短距离的点,那就说明这就是开始点能到达它的最短距离,就像是上面第二次处理v3,v3是v1的所有直达点中最短的,那么就说明从其他直达点走再绕回v3,在边权值大于0的前提下距离不可能比v1直达v3要短。

每次都选出一个可达的最短距离,最终将所有点都找出来,那么每个点肯定是可达最短距离的加和,这就是贪心策略。

image-20210226210336847.png

java实现Dijkstra算法1.0

public static HashMap dijkstra(Node begin) {
    if (begin == null) return null;
    HashMap nodeDistanceMap = new HashMap<>();
    nodeDistanceMap.put(begin, 0);
    //锁住已经处理过的节点
    HashSet selectedNode = new HashSet<>();
    //每次找到当前距离最短的节点
    while (true) {
        int minDistance = Integer.MAX_VALUE;
        Node minNode = null;
        for (Map.Entry entry : nodeDistanceMap.entrySet()) {
            Integer distance = entry.getValue();
            if (!selectedNode.contains(entry.getKey()) && distance < minDistance) {
                minDistance = distance;
                minNode = entry.getKey();
            }
        }
        if (minDistance == Integer.MAX_VALUE) break;
        for (Edge next : minNode.edges) {
            if (!nodeDistanceMap.containsKey(next.to)) {
                nodeDistanceMap.put(next.to, minDistance + next.weight);
            } else {
                nodeDistanceMap.put(next.to, Math.min(nodeDistanceMap.get(next.to), minDistance + next.weight));
            }
        }
        //锁住Node,以后不用管它了
        selectedNode.add(minNode);
    }
    return nodeDistanceMap;

改良一下代码,不是优化,就是将找最小到达点功能给抽离出来。

//将找到最小距离的节点给抽离出一个函数,其实是再写一遍,不太熟练
public static HashMap dijkstra1(Node begin) {
    if (begin == null) return null;
    HashMap distanceMap = new HashMap<>();
    distanceMap.put(begin, 0);
    HashSet lockedNodes = new HashSet<>();
    Node minNode;
    while ((minNode = getMinDistanceNode(distanceMap, lockedNodes)) != null) {
        for (Edge edge : minNode.edges) {
            Integer distance = distanceMap.get(minNode);
            Node to = edge.to;
            if (!distanceMap.containsKey(to)) {
                //add
                distanceMap.put(to, distance + edge.weight);
            } else {
                //update
                distanceMap.put(to, Math.min(distance + edge.weight, distanceMap.get(to)));
            }
        }
        lockedNodes.add(minNode);//锁住
    }
    return distanceMap;
}

public static Node getMinDistanceNode(HashMap distanceMap, HashSet lockedNodes) {
    int min = Integer.MAX_VALUE;
    Node minNode = null;
    for (Map.Entry entry : distanceMap.entrySet()) {
        if (!lockedNodes.contains(entry.getKey()) && entry.getValue() < min) {
            min = entry.getValue();
            minNode = entry.getKey();
        }
    }
    return minNode;
}

优化2.0版本,这才是进行了优化

思考:

通过研究dijkstra算法我们可以发现,核心就两点
1)每次都找到那个最小距离的到达点,
2)遍历那个最小距离的点更新距离表
很明显第二点是不可能优化的,你总得看看所有的边看看距离怎么样然后更新距离表。
但是第一点找到最小距离,我们很容易会想到使用最小堆是否能够优化,答案是可以的。
因为最小堆每次进行堆的调整也不过是logN,也就是说我们可以直接使用一个小根堆来存储距离表
不过注意一点,因为距离表中的距离是需要进行更新的,所以我们需要更新后保证堆正确性,这就不能使用PriorityQueue,需要我们自己实现resign功能,这需要维护一个indexMap需要知道哪个位置的元素发生了更新,这个更新只会变小,不会变大,所以肯定是向上进行heapInsert

如何在堆中完成一个节点的锁住功能,避免已经处理过的节点再次插入堆中?

做以下约定
1)如果indexMap中没有这个Node,那肯定还没有,就直接加入
2)如果有,但是index为-1,则表示加入过,但是已经弹出,直接跳过,弹出的肯定是找到最小距离了

  1. 以上都不符合表示还在堆中,进行更新操作,需要维护堆

实现

用来找最小到达点的堆

public static class Heap {
    private static class NodeRecord {
        private Node node;
        private Integer distance;

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

    private ArrayList eleMentData;//Entry
    private HashMap indexMap;//这里的Integer是下标
    private int heapSize;

    public Heap() {
        eleMentData = new ArrayList<>();
        indexMap = new HashMap<>();
    }

    public void swap(int aIdx, int bIdx) {
        //保证下标随着一起改
        NodeRecord aEntry = eleMentData.get(aIdx);
        NodeRecord bEntry = eleMentData.get(bIdx);
        eleMentData.set(aIdx, bEntry);
        eleMentData.set(bIdx, aEntry);
        indexMap.put(aEntry.node, bIdx);
        indexMap.put(bEntry.node, aIdx);
    }

    public void heapInsert(int index) {
        int father;
        //维护一个小根堆,如果父节点的distance比这个子节点要大的话需要交换
        while (compare(index, (father = (index - 1) / 2)) < 0) {
            swap(father, index);
        }
    }

    public void heapify(int index) {
        int leftChild;
        while ((leftChild = (index << 1) | 1) < heapSize) {
            int lowest = leftChild + 1 < heapSize && compare(leftChild + 1, leftChild) < 0 ? leftChild + 1 : leftChild;
            lowest = compare(index, lowest) < 0 ? index : lowest;
            if (index == lowest) break;
            swap(index, lowest);
            index = lowest;
        }
    }

    //比较两个下标处的Entry里distance大小
    private int compare(int i1, int i2) {
        return eleMentData.get(i1).distance - eleMentData.get(i2).distance;
    }

    //进行三个情况的检查和处理,完成操作
    public void addOrUpdateOrIgnore(Node node, int distance) {
        if (!indexMap.containsKey(node)) {//add
            eleMentData.add(heapSize, new NodeRecord(node, distance));
            //indexMap同步
            indexMap.put(node,heapSize++);
            heapInsert(heapSize - 1);
        } else if (indexMap.get(node) != -1) {//update
            Integer index = indexMap.get(node);
            eleMentData.get(index).distance = Math.min(eleMentData.get(index).distance, distance);
            heapInsert(index);//可能更新,维护一下
        }
    }

    public NodeRecord poll() {
        if (isEmpty()) throw new RuntimeException("no more ele...");
        final NodeRecord res = eleMentData.get(0);
        swap(0,--heapSize);
        eleMentData.set(heapSize,null);
        indexMap.put(res.node,-1);//-1:曾经来过
        heapify(0);
        return res;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }
}

算法本身

public static HashMap dijkstra(Node begin){
    if(begin==null) return null;
    HashMap result = new HashMap<>();
    Heap smallRHeap = new Heap();
    smallRHeap.addOrUpdateOrIgnore(begin,0);
    while (!smallRHeap.isEmpty()){
        Heap.NodeRecord heapTop = smallRHeap.poll();
        for(Edge edge:heapTop.node.edges){
            smallRHeap.addOrUpdateOrIgnore(edge.to,edge.weight+heapTop.distance);
        }
        result.put(heapTop.node,heapTop.distance);
    }

    return result;
}

测试

用上面那个例子进行测试

public static void main(String[] args) {
    int[][] matrix = {{10, 1, 3}, {100, 1, 6}, {30, 1, 5}, {5, 2, 3}, {50, 3, 4}, {20, 5, 4}, {10, 4, 6}, {60, 5, 6},};
    Graph graph = GraphGenerator.graphGenerator(matrix);
    HashMap result = dijkstra(graph.nodesMap.get(1));
    for (Map.Entry entry : result.entrySet()) {
        System.out.println("to Node{" + entry.getKey().value + "} distance:" + entry.getValue());
    }
}

这里的Graph是我自己熟悉的图的表示方式,matrix是一个边组成的二维数组,里面存的每个一维数组都是这样的意思{weight,from,to}

Graph

public class Graph{
    private HashMap nodes;//Node.value map to Node
    private HashSet edges;
    public Graph(){
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

Node

public class Node{
    private int in;
    private int out;
    private int value;
    private ArrayList nexts;
    private ArrayList edges;
    public Node(){
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

Edge

public class Edge{
    private int weight;
    private int from;//from Node's value
    private int to;//to Node's value
    public Edge(int weight,int from,int to){
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

你可能感兴趣的:(Dijkstra算法)