牛牛取快递(一)从dfs到dijkstra以及堆优化的dijkstra

由于上一篇博客的第三题“牛牛取快递”上次还未ac,因此今天特意再来尝试一下,上次使用暴力dfs搜索最短路径超时的我,在大佬们解题思路的“熏陶”之下,终于在我的记忆深处找回了那被封印依旧的dijkstra算法。

dijkstra算法主要是选择一个初始点s,每次选择一个离s距离最小并未被选过的点t,并通过t作为跳板,更新其余点到s的距离(如果比原来还长就不更新啦)。然后不断重复这个过程即可。(ps:不要和prime搞混啦,虽然他们主要思路很相似)。

然后,题目没审仔细的我用邻接矩阵写出了以下代码。

import java.util.Scanner;
public class Main {
    static int [][] road;
    static int min;
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) {//注意while处理多个case
            int n = in.nextInt();
            int M = in.nextInt();
            int S = in.nextInt();
            int T = in.nextInt();
            road = new int[n+1][n+1];
            for(int i = 0;i<M;i++){
                int s = in.nextInt();
                int t = in.nextInt();
                int D = in.nextInt();
                if(road[s][t]==0||road[s][t]>D)
                    road[s][t] = D;
            }
            min = Integer.MAX_VALUE;
            int res = dijkstra(S, T) + dijkstra(T, S);
            System.out.println(res);
        }
    }
    static int dijkstra(int s,int t){
        // 接受一个有向图的权重矩阵,和一个起点编号start(从0编号,顶点存在数组中) 
        // 返回一个int[] 数组,表示从start到它的最短路径长度 
        int n = road.length; // 顶点个数 
        int[] shortPath = new int[n]; // 保存start到其他各点的最短路径 
        int[] visited = new int[n]; // 标记当前该顶点的最短路径是否已经求出,1表示已求出 
        // 初始化,第一个顶点已经求出 
         
        for(int i = 1;i<n;i++){
            shortPath[i] = (road[s][i]==0?Integer.MAX_VALUE:road[s][i]);
        }
        shortPath[s] = 0; 
        visited[s] = 1; 
        for (int count = 2; count < n; count++) { // 要加入n-1个顶点 
            int k = -1; // 选出一个距离初始顶点start最近的未标记顶点 
            int dmin = Integer.MAX_VALUE; 
            for (int i = 1; i < n; i++) { 
                if (visited[i] == 0 && shortPath[i] < dmin) { 
                    dmin = shortPath[i]; 
                    k = i;
                } 
            }
            if(k==-1){
                return shortPath[t];
            }
            // 将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin
            shortPath[k] = dmin;
            visited[k] = 1; 
   
            // 以k为中间点,修正从start到未访问各点的距离 
            for (int i = 1; i < n; i++) { 
                if (visited[i] == 0 && road[k][i]!=0 &&shortPath[k] + road[k][i] < shortPath[i]){//使用k作为跳板,更新未被访问过并且通过k之后离start更近的点的距离
                    shortPath[i] = shortPath[k] + road[k][i]; 
                } 
            }
            if(visited[t] == 1){
                return shortPath[t];
            }
        }
        return shortPath[t];
    }
}

因为题目中这个条件,用邻接矩阵来做,测试数据跑到80%报出了堆溢出,那么我的第一反应自然是把邻接矩阵改成邻接表就成了。

既然有了思路,改起来也没有太大的问题,代码如下,这下总能ac了吧,我边祈祷边按下了“提交”按钮。

static LinkedList<Integer[]>[] road;//邻接表
static int dijkstra(int s,int t){
    // 接受一个有向图的权重矩阵,和一个起点编号start(从0编号,顶点存在数组中)  
    // 返回一个int[] 数组,表示从start到它的最短路径长度  
    int n = road.length; // 顶点个数  
    int[] shortPath = new int[n]; // 保存start到其他各点的最短路径  
    int[] visited = new int[n]; // 标记当前该顶点的最短路径是否已经求出,1表示已求出  
    // 初始化,第一个顶点已经求出  
    
    for(int i = 1;i<n;i++){
        shortPath[i] = Integer.MAX_VALUE;
    }
    for(Integer[] kv:road[s]){
        if(shortPath[kv[0]]>kv[1]){
            shortPath[kv[0]] = kv[1];
        }
    }
    visited[s] = 1;  
    for (int count = 2; count < n; count++) { // 要加入n-1个顶点  
        int k = -1; // 选出一个距离初始顶点start最近的未标记顶点  
        int dmin = Integer.MAX_VALUE/2;  
        for (int i = 1; i < n; i++) {  
            if (visited[i] == 0 && shortPath[i] < dmin) {  
                dmin = shortPath[i];  
                k = i;
            }  
        }
        if(k==-1){
            return shortPath[t];
        }
        // 将新选出的顶点标记为已求出最短路径,且到start的最短路径就是dmin 
        shortPath[k] = dmin;
        visited[k] = 1;  
        int fromToDis;
        // 以k为中间点,修正从start到未访问各点的距离  
        for (int i = 1; i < n; i++) {  
            if(visited[i] == 0){
                fromToDis = find(k, i);
                if(fromToDis + shortPath[k]< shortPath[i]){
                    shortPath[i] = fromToDis+shortPath[k];
                }
            }
        }
        if(visited[t] == 1){
            return shortPath[t];
        }
    }
    return shortPath[t];
}
//从邻接表查找from到to有路的距离
static int find(int from,int to){
    int res = Integer.MAX_VALUE/2;
    for (Integer[] kv: road[from]) {
        if(kv[0] == to && kv[1]<res){
            res = kv[1];
        }
    }
    return res;
}

结果很快就出来了,可是,嗯?还是80%的通过率?不过这次是超时了,邻接矩阵可以一次查出从a节点到b节点是否有边,距离是多少,而邻接表则需要查询一个链表,效率自然是变低了,那么是否有优化的办法呢?

经过查阅资料后,我发现了dijstra堆优化的一种算法,通过一个优先队列记录下一个点以及离原点的距离,每次取队列的头判断该节点是否加入过,若没加入过,就用该节点更新其余点的距离,感觉似乎是比普通的方法快一些,堆的插入以及取头都是o(logn)的,很快,堆优化的dijstra也码出来了。

static int dijkstra(int s, int t) {
    // 堆优化
    int n = road.length;
    int[] visited = new int[n];
    int[] dis = new int[n];
    int to, d;
    Integer[] toDis;
    PriorityQueue<Integer[]> queue = new PriorityQueue<>((Integer[] o1,Integer[] o2)->o1[1]-o2[1]);
    for (int i = 1; i < n; i++) {
        dis[i] = Integer.MAX_VALUE / 2;
        visited[i] = 0;
    }
    dis[s] = 0;// 到自己的距离为0
    int newdis;
    queue.offer(new Integer[] { s, 0 });
    while (!queue.isEmpty()) {
        toDis = queue.element();
        queue.poll();
        to = toDis[0];
        d = toDis[1];
        if (visited[to] == 1) {
            continue;
        }
        visited[to] = 1;
        for (int i = 1; i < n; i++) {
            newdis = dis[to] + find(to, i);
            if (visited[i] == 0 && dis[i] > newdis) {// i没去过,并且dis[to]+find(to,i)直接到i的距离大于到to再到i的距离
                dis[i] = newdis;
                queue.offer(new Integer[] { i, newdis });
            }
        }
        if (visited[t] == 1) {
            return dis[t];
        }
    }
    return dis[t];
}

嗯,结果也不算出乎意料,同样只有80%的通过率,最主要的问题应该在于dijstra每次更新距离时需要查找是否存在a到b的边,以及其最短距离,因此每次查询的效率较低,而堆优化的性能在测试用例中效果并不是特别明显吧。

从邻接矩阵到邻接表再到堆优化的dijstra,经过三次编码之后,虽然通过率没有提高,但是能dijstra的思路基本是不会再忘了的(毕竟前后三次从满怀期待的编码到仅有80%通过而产生的失落),尤其是prime和dijstra的不同之处(其实一开始我以为是同一种思路嘿嘿),下次应该会学习spfa算法再尝试本题,因为看到不少c++通过的都是用spfa算法做的。这也算自己日后需要填的一个坑吧。

猜你喜欢

转载自www.cnblogs.com/zzzdp/p/9091372.html
今日推荐