Dijkstrala算法

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

Dijkstrala算法查找图中从一个节点到另一个节点的最短路径,输出结果是最短路径以及长度。算法执行的前提条件是权重不能是负数。

起始顶点记为sid,目的节点记为tid。

数组predecessor记录了最短路径上一个节点的前驱节点。例如predecessor[3]=5,就是从节点5到节点3的路径最短。

节点Vertext,保存节点id、当前节点距离sid的最短路径值。

优先队列queue按照节点的距离排序,在堆顶的是距离值最小的节点。

每次处理一个节点node的时候,处理其每条边,计算每条边的目的节点vertexts[to].dist。如果vertexts[to].dist>vertexts[node.id].dist + 边的权重,则表示有新的最短路径,更新,并且将目的节点加入到队列中。

因为可能有多条边达到同一个节点,所以队列中可能有多个元素表示同一个节点,会有多余的操作。解决此问题可以自己写一个优先队列,实现更新元素。参考代码

import java.util.*;

public class Graph {
    //顶点数量
    private int v;
    //链接表
    private LinkedList<Edge> adjacency[];
    
    /**
     * 构造顶点数为v的图
     * @param v
     */
    public Graph(int v) {
        this.v = v;
        adjacency = new LinkedList[v];
        for(int i=0;i<v;i++){
            adjacency[i] =  new LinkedList<>();
        }
    }

    /**
     * 添加一条边
     * @param sid
     *          源节点id
     * @param tid
     *          目的节点id
     * @param weight
     *          权重
     */
    public void addEdge(int sid, int tid,int weight){
        Edge edge = new Edge(sid,tid,weight);
        adjacency[sid].add(edge);
    }

    /**
     * 计算从sid到tid的最短路径。返回值是经过路径的节点id
     * @param sid
     * @param tid
     * @return
     */
    public List<Integer> dijkstrala(int sid ,int tid){
        Vertext[] vertexts = new Vertext[v];
        for(int i=0; i < v;i++){
            vertexts[i] = new Vertext(i);
        }
        vertexts[sid].dist = 0;

        //predecessor[i]=得到i的前驱节点
        int[] predecessor = new int[v];
        predecessor[sid] = -1;
        PriorityQueue<Vertext> queue = new PriorityQueue<>(this.v,
                new Comparator<Vertext>(){
                    public int compare(Vertext o1, Vertext o2){
                        return o1.dist - o2.dist;
                    }
                });
        //队列中可能存在重复节点,会存在多余操作
        queue.offer(vertexts[sid]);
        while(! queue.isEmpty()){
            Vertext node = queue.poll();
            if(node.id == tid) break;
            if(adjacency[node.id]!=null){
                for(Edge edge : adjacency[node.id]){
                    int to = edge.tid;
                    if(vertexts[node.id].dist + edge.weight < vertexts[to].dist){
                        vertexts[to].dist = vertexts[node.id].dist + edge.weight;
                        queue.add(vertexts[to]);
                        predecessor[to] = node.id;
                    }
                }
            }
        }
        List<Integer> path = new ArrayList<Integer>();
        if(vertexts[tid].dist < Integer.MAX_VALUE){
            visitPredecessor(predecessor,tid,path);
            Collections.reverse(path);
        }
        return path;
    }

    private void visitPredecessor(int[] predecessor, int tid,List<Integer> path) {
        path.add(tid);
        if(predecessor[tid] != -1){
            visitPredecessor(predecessor,predecessor[tid],path);
        }
    }

    private class Vertext{
        //编号
        private int id;
        //距离
        private int dist = Integer.MAX_VALUE;

        public Vertext(int id) {
            this.id = id;
        }
    }
    private class Edge{
        private int sid;
        private int tid;
        private int weight;

        public Edge(int sid, int tid, int weight) {
            this.sid = sid;
            this.tid = tid;
            this.weight = weight;
        }
    }
}

时间复杂度是:O(ElogV)。程序主体是一个while循环,里面是一个for循环。因为每次for循环次数不定,但是所有for循环执行次数之和不会超过E(边的个数)。换句话说第一个执行E0、第二次执行E1、…第V次执行EV,E0+E1+…+Ev=E。(这里先假设队列中每个节点只有一个元素)
for循环里面是队列的入队、出队操作。假设队列中每个节点只有一个元素,那么队列不会超过V。所以入队、出队操作时间复杂度logV。那么总的时间复杂度是O(E
logV)。

队列中有重复元素,队列的最大不会超过E。那入队、出队操作时间复杂度是logE。 E < = V 2 E<=V^2 E<=V2, l o g E < = 2 l o g V logE<=2logV logE<=2logV。总时间复杂度还是O(E*logV)这个级别。如果在稀疏图中,这个时间复杂度是可以接受的。但在某些情况下会很糟糕。可以考虑在队列中更新节点。参考代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值