Dijkstra Algorithm基本过程与理解

Series S2: 求一点出发到一点/其他所有点的最短路径

  • 使用条件:边上的权证可以有且不相同但必须为正值

  • Series S1:讲了五个series(见前面的shortest path)

Use Case 1:求出一个点到一个点的最短路径

什么是Dijkstra Algorithm? Best First Search VS Dijkstra

  • Dijkstra:需要有一个Starting Node(题目给的),每一次从到目前为止Cost最低的点出发(Expand),generate所有它的邻居,直到我们Expand到targetNode/所有Node为止!

  • 每当一个点被Expand的时候,它的最短路径(从start到他)就确定了

注意expand& generate

  • expand表示你接下来要访问的

    • 所以你一旦访问了,就表示确定了,所以对于dijkstra来说,每次要访问的(expand),一旦他被访问了,从源头点到该点的最短就确定了

    • 如果记录的每点cost最小,也就是costMark(x) = min Cost(源头,x)

      • min cost(源头,x)= minCost (源头,y) + minCost(y,x)

      • 这个成立的唯一条件,就是cost是正的&cost函数是monotonicity increasing的?

  • generate表示你potential需要访问的点

Greedy算法

  • 不看所有解,我觉得按着某一种方法(aha point)去走,我就得到最优解

  • Greedy算法需要证明,也需要一些灵感!

反证法:会不会有一个被expand的点,其实从源点到它的最短路径没有确定,会在后面的expansion来确定

  • 被Generate的点, A(13), B(15), D(17)

  • Expansion: dis(Src, first_time_C) = 10

  • dis(Src, second_expanded_C) = initial dis from src to the cur node(>= 10) + dis of all the path behind

  • dis(Src, second_expanded_C) > dis(Src, first_time_C)

  • 假设错误

在Dijsktra算法中需要注意

  • 明确你的cost是什么

  • 我们允许一个点被Generate多次,先generate的不一定是最短路径(时间最先!=cost最优)

  • 在有权重图中,Mark Visited at Generation是不一定正确的!!

Cost of Neighbor When Generation

  • dis(src, nei) = dis(src, cur) + weight of (cur, nei)

General Implementation for Dijkstra

class GraphNode{
    String node;
    int weight;
    public GraphNode(String node, int weight) {
        this.node;
        this.weight;
    }
}
public int findShortestPath(String start, String end, Map<String, List<GraphNode>> graph) {
    // mapping for node and index
    Map<String, Integer> nodeToIndex = new HashSet<>(); //这个Map可以不要,根据你的构图决定
    int index = 0;
    for (String node: graph.keySet()) {
        nodeToIndex.put(node, index++);
    }
    int n = graph.size(); //有几个node
    int[] distances = new int[n]; //这个是记录node的对应的distance //有点像dp
    Arrays.fill(distances, Integer.MAX_VALUE); //现在把distance全部记录最大 
    boolean[] visited = new boolean[];    
    distance[nodeToIndex.get(start)] = 0;
    
    //至多node轮
    for (int i = 0; i < n - 1; i++) { //明确告诉你Dijkstra最多有多少轮
        int uIndex = selectMinimumIndex(distances, visited); // 从当前cost最低点出发// 这部分可以被pq优化
        visited[uIndex] = true; // u 是个index不是这个点,所以你要反向找回那个node
        for (GraphNode v: graph.getOrDefault(indexToNode(uIndex, nodeToIndex), new ArrayList<>())) {
            int vIndex = nodeToIndex.get(v.node);
            if (!visited[vIndex] && distances[uIndex] != Integer.MAX_VALUE && distances[uIndex] + v.weight < distance[vIndex]) {
                distances[vIndex] = distance[uIndex] + v.weight;
            }
        }
    }
    return distances[nodeToIndex.get(end)];
}

private String indexToNode(int index, Map<String, Integer> nodeToIndex) {
    for (Map.Entry<String, Integer> entry: nodeToIndex.entrySet()) {
        if (entry.getValue().equals(index)) {
            return entry.getKey();
        }
    }
    return null;
}
private int seletecMinimumIndex(int[] distances, boolean[] visited) {
    int minIndex = -1;
    int minValue = Integer.MAX_VALUE;
    for (int i = 0; i < distance.length; i++) {
        if (!visited[i] && distances[i] < minValue) {
            minValue = distance[i];
            minIndex = i;
        }
    }
    return minIndex;
}

这个array版本的实现

  • 缺点:时间肯定不如现代版本的heap/treeMap/treeSet版本实现

  • 优点:元和可读性以及区分性: already_expanded & already_generated

    • if (distances[uIndex] + v.weight < distances[vIndex]) {distances[vIndex] = distance[uIndex] +v.weight};

    • 在这个视线里面,如果一个点被generate多次,不管后面的generate比前面generate得到的结果好还是坏

    • distances[i]只要visited是false i的状态都是already_generated but not yet expand。存储的只会是最优解,一旦visited[i] mark成为true了,说明被expanded

General Implementation for Dijkstra 现代版

class GraphNode{
    String label;
    int cost;// 到目前为止的cost,src到这个node的距离和
    public GraphNode(String label, int cost) {
        this.label;
        this.cost;
    }
}
public int findShortestPath(String start, String end, Map<String, List<GraphNode>> graph) {
    if (start.equals(end)) return 0;
    PriorityQueue<GraphNode> minHeap = new PriorityQueue<> ((node1, node2) -> .compare(node1.cost, node2.cost));
    Map<String, Integer> shortestPath = new HashMap<>(); //这个其实是你mark Visited,这里把mark和map合起来了嘛。。
    
    minHeap.offer(new GraphNode(Sstart, 0));
    
    while (!minHeap.isEmpty()) {
        GraphNode currentNode = minHeap.poll();
        String currentLabel = currentNode.label;
        int currentCost = currentNode.cost;
        // 第一次exapnd到target就可以结束了,上面的实现没有加
        if(currentLabel.equals(end)) {
            return currentCost;
        }
        // 已经expand过的点不再expand
        if (shortestPath.containsKey(currentLabel)) {
            continue;
        }
        shortestPath.put(currentLabel, currentCost);
        
        for(GraphNode neighbor: graph.getOrDefualt(currentLabel, new ArrayList<>()) {
            String neighborLabel = neighbor.label;
            int totalCostToNeighbor = currentCost + neighbor.cost;
            // given graph 初始化cost储存的是weight
            
            if (!shortestPath.containsKey(neighborLabel)) {
                minHeap.offer(new GraphNode(neighborLabel, totalCostToNeighbor));
            }
            /*
            其实可以完全分开
                case 1: neighbor node 已经被expand过了
                    无视
                case 2: neighbor node虽然没有被exapnd过, 被generate过了
                    Map<Node, Integer> already_generateMap: <Node, 上一次generate的cost>
                    如果这一次generate的totalCostToNeighbor比上一次低才generate
                case 3: 这个node还没有被generate过
                    直接generate
            */
        }
        return -1;
}

Quesiton: 我能不能对于这种被generate的点,直接在PriorityQueue里更新的Entry?

  • 不行,但是可以进行如下操作

  • 优化Java PQ: Search + update: 除非自己实现PriorityQueue, MappedPriorityQueue

  • 选择TreeSet/ TreeMap update: remove + insert

TC&SC

  • TC:普通的遍历算法O(|V| + |E|) ==> O((|V| +|E|) * log |E|)

    • Dijkstra: O(|V+E| * log(E))

    • 除非你做了generation的优化,我保证了每个点至多被generate一次O((V+E)log(V))

  • SC: O(E) or O(V) if you optimize for generation

Use Case 2: 求出从一个点到所有点的最短路径

shortestPaths 里其实存的就是每一个点的最短路径值,而我们只要把遇到target node就return的这个限制条件去掉就好

Use Case 3: Dijkstra在面试中的难点: Multiple-Dimension Node

Example

  • 告诉你地图中所有城市的道路,求从一个start城市出发,走exactly K步能哦租到的所有城市中,总距离和距离当前城市最近的城市

  • graph representation?明白什么是点,什么是边,每个点能去哪些点

    • 为什么不用这个edge不好用,因为没法快速的知道,每个点能去哪些点

    • 比较好的表达方式 adjacency list

      • Map<Node, Map<Node, Integer>> weightGraph

      • Map<Node, List<Node>> unidirectedGraph

小心

  • 走exactly K步能得到的距离和最小 不等与

  • first time走k步的city,因为可能还有些点没有看到

  • K步之内的距离和最小,因为这里说的是我要走K步呀

  • 思考:说白了,因为dijkstra解决的是,某个点,到某个点的最短路径,但是这里的问题没有给出到底到哪个点的?——》所以你就都看step = K——》让每次expand出来的点的step是排好序的

多维度问题的关系

  • 如果你保证,Expand点或者generate点的顺序是单调的,就可以在第一次遇到它就终止算法

  • 如果你能保证你generate点的时候,从小到达的单调递增的来generate,甚至可以在generate mark visited

  • 如果你能保证你expand点的顺序是单调的,那就可以在第一次expand到他的时候终止

Summary

  • Dijkstra算法中每exapnd一次,就确定了从点A到点B的最短路径,往后只能增大不能减少!如果求的是从一个点到所有点的最短路径,也只需要做一次!One to ALL!

  • 是否可以在Generate的时候Mark Visited:不一定

  • 时空复杂度: O(|V| +|E|) log(|E|)

    • 每个点允许被generate多次但是最多只能被expand一次

    • 如果我们自己实现MappedPriorityQueue,那么可以实现Node只能被Generated一次,但是非常复杂情况不如用TreeMap/TreeSet代替更为简洁

  • MappedPriortyQueue:Use case是我们遇到一个已经generated的点的时候,Map找到Node Position,UpdatedValue +上下调整

Last updated