《啊哈算法》的Java实现 | 第六章 :最短路径及最短路径算法的对比分析

33 篇文章 1 订阅
4 篇文章 0 订阅

《啊哈算法》的Java现实 | 第一章:排序.

《啊哈算法》的Java现实 | 第二章:栈、队列、链表.

《啊哈算法》的Java现实 | 第三章:枚举!很暴力.

《啊哈算法》的java实现 | 第四章:万能的搜索.

《啊哈算法》的Java实现| 第五章:图.

《啊哈算法》的Java实现 | 第六章 :最短路径及最短路径算法的对比分析.

《啊哈算法》的Java实现 | 第七章:神奇的树.

《啊哈算法》的Java实现 |第八章:更多精彩算法.

只有五行算法的—Floyd-Warshall

**多资源路径问题:**求任意两点之间的的最短路径

**代码基本思想:**最开始只允许经过1号顶点进行中转,接下来只允许经过1号和2号顶点进行中转…允许经过1-n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前面k号点的最短路径。其实这是一种“动态规划“的思想。

package ch6;

import java.util.Scanner;
/*
4 8
1 2 2
1 3 6
1 4 4
2 3 3
3 1 7
3 4 1
4 1 5
4 3 12
 */
public class Floyd_Warshall {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();//顶点的个数
        int m = scanner.nextInt();//两个顶点之间的条数
        int[][] e = new int[n+1][n+1];
        int t1,t2,t3;

        for (int i =1;i<=n;i++){
            for (int j=1;j<= n;j++){
                if (i==j) e[i][j] = 0;
                else  e[i][j] = 9999;
            }
        }
        //读入边
        for (int i =1;i<=m;i++){
            t1 = scanner.nextInt();
            t2 = scanner.nextInt();
            t3 = scanner.nextInt();
            e[t1][t2] = t3;

        }
        scanner.close();
        floyd_washall(n,e);
        for (int i =1; i<= n;i++){
            for (int j =1; j<=n;j++){
                System.out.print(e[i][j] + " ");
            }
            System.out.println();
        }


    }
    public static int[][] floyd_washall(int n,int[][] e){
        for (int k =1;k<=n;k++){
            for (int i =1; i<= n;i++){
                for (int j=1;j<=n ;j++){
                    //加上<9999的判断条件,就可以不用计算无法到达的边和可以达到的边之和-7 
                    if (e[i][k] < 9999 && e[k][j] < 9999 & e[i][j] > e[i][k] + e[k][j])
                        e[i][j] = e[i][k] + e[k][j];
                }
            }
        }
        return  e;
    }
}

它的时间复杂度为O(N3)

Dijkstra算法—单源最短路

算法:指定一个点到其余各个顶点的最短路径,也叫做单源最短路

需要用一个数组dis来存储1号顶点到其余各顶点的初始路程

123456
dis0112

此时dis数组中的值称为最短路径的”估计值“。

算法的基本思想:每次找到离源点最近的一个顶点,然后以该顶点为中心进行扩展,最终得到源点到其余所有点的最短路径。基本步骤如下:

1.将所有的顶点分为两个部分:已知最短路程的顶点集合P和位置最短路径的顶点集合Q。最开始,已知最短路径的顶点集合P中只有源点一个顶点。我们在这里用一个book数组来记录哪些点在集合P中。

2.设置源点s到自己的最短路径为0即dis[s] = 0。若存在有源点能直接到达的顶点i,则把dis[i]设为e[s] [i]。同时把所有其他顶点的最短路径设为∞

3.在集合Q的所有顶点中选择一个离源点s最近的顶点u(即dis[u]最小)加入到集合P。并考察所有以点u为起点的边,对每一条边进行松弛操作。例如,如果存在一条从u到v的边,那么可以通过将边u→v添加到尾部来扩展一条从s到v的路径,这条路径的长度是dis[u] + e[u] [v]。如果这个值比目前已知的dis[v]的值要小,我们就可以用新的值来代替当前dis[v]中的值。

4.重复第三步,直到Q为空,算法结束

代码实现

package ch6;

import java.util.Scanner;

public class Dijkstra {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] e = new int[n][n];
        int[] dis = new int[n];
        int[] book = new int[n];
        int t1,t2,t3;
        int min;
        int max = 9999999;
        int u = 0,v;
        for (int i =0;i<n;i++){
            for (int j =0; j<n ; j++){
                if (i==j) e[i][j] = 0;
                else e[i][j] =max;
            }
        }
        for (int i =0; i<m ; i++){
            t1 =scanner.nextInt();
            t2 =scanner.nextInt();
            t3 = scanner.nextInt();
            e[t1][t2] = t3;
        }
        scanner.close();
        for (int i = 0; i< n; i++){
            dis[i] = e[0][i];//0号顶点开始
        }
        for (int i =0; i<n ;i++){
            book[i] = 0;
        }
        book[0] = 1;//标记为顶点

        for (int i=0; i< n-1 ;i++){
            //找到离源点最近的点
            for (int j = 0; j< n;j++){
                if (book[j] == 0 && dis[j]< max){
                    min = dis[j];
                    u = j;
                }
            }
            //用这个点去扩展其他的点
            book[u] = 1;
            for (v = 0; v < n; v++){
                if (e[u][v]<max){
                    if (dis[v] > dis[u] + e[u][v]){
                        dis[v] = dis[u] + e[u][v];
                    }
                }
            }
        }
        for (int i =0;i<n ;i++){
            System.out.print(dis[i] + " ");
        }

    }


}

时间复杂度为O(N2)

邻接表

package ch6;

import java.util.Scanner;
/*
4 5
1 4 9
4 3 8
1 2 5
2 4 6
1 3 7
 */
public class Sparse {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] u = new int[m+1];
        int[] v = new int[m+1];
        int[] w = new int[m+1];
        int[] first = new int[n+1];
        int[] next = new int[m+1];
        //初始化frist数组
        for(int i =1 ;i<= n;i++){
            first[i] = -1;
        }
        for (int i =1; i<=m ; i++){
            u[i] = scanner.nextInt();
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
            //first[u[i]]代表第i条边
            next[i] = first[u[i]];
            first[u[i]] = i;
        }
        for (int i =1 ; i<= n;i++){
            int k = first[i];
            while(k!=-1){
                System.out.println(u[k] + " " + v[k] + " " + w[k]);
                k = next[k];
            }
        }
    }
}

Bellman-Frod-解决负权边问题

package ch6;

import java.util.Scanner;
/*
5 5
1 2 2
0 1 -3
0 4 5
3 4 2
2 3 3
 */
public class Bellman_Ford {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] dis = new int[n];
        int[] u = new int[m];
        int[] v= new int[m];
        int[] w= new int[m];
        int max = 99999;
        for (int i =0; i< m;i++){
            u[i] = scanner.nextInt();
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }

        //初始化dis
        for (int i =0; i<m;i++){
            dis[i] = max;
        }
        dis[0] = 0;
        for (int i = 0; i< n-1; i++){
            for (int j =0; j<m;j++){
                if (dis[v[j]] > dis[u[j]] + w[j])
                    dis[v[j]] = dis[u[j]] + w[j];
            }
        }
        //输出
        for (int i =0; i<n;i++){
            System.out.println(dis[i]);
        }
        scanner.close();

    }
}
package ch6;

import java.util.Scanner;
/*
5 5
1 2 2
0 1 -3
0 4 5
3 4 2
2 3 3
 */
public class Bellman_Ford {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] dis = new int[n];
        int[] u = new int[m];
        int[] v= new int[m];
        int[] w= new int[m];
        int max = 99999;
        int flag = 0;
        for (int i =0; i< m;i++){
            u[i] = scanner.nextInt();
            v[i] = scanner.nextInt();
            w[i] = scanner.nextInt();
        }

        //初始化dis
        for (int i =0; i<m;i++){
            dis[i] = max;
        }
        dis[0] = 0;
        for (int i = 0; i< n-1; i++){
            int check = 0;//标记
            for (int j =0; j<m;j++){
                if (dis[v[j]] > dis[u[j]] + w[j]) {
                    dis[v[j]] = dis[u[j]] + w[j];
                    check = 1;//如果发送了改变则为1
                }
            }
            if(check ==0 ) break;//如果没有发生更新数组则退出循环
        }
        for (int i =0; i<m;i++){
            if(dis[v[i]] > dis[u[i]] + w[i])
                flag = 1;
        }
        //输出
        for (int i =0; i<n;i++){
            System.out.println(dis[i]);
        }
        scanner.close();

    }
}

Bellman_Frod的优化

  1. 每次仅对最短路估计值发生变化了的顶点的所有出边执行松弛操作
  2. 首先把所有的顶点和边建立邻接表
  3. 建立一个队列数组,每次选取队首的顶点u,对顶点u的所有出边进行松弛操作,如果u->v这这条边使得源点到v的最短路程变短,则v入队
  4. 同一个顶点多次入队是毫无意义的,所以要利用book数组判断是否重复
  5. 对顶点u的所有边进行松弛操作后,u出队
  6. 循环操作直到队列变空
package ch6;

import java.util.Scanner;
/*
5 7
1 2 2
1 5 10
2 3 3
2 5 7
3 4 4
4 5 5
5 3 6
 */
public class Bellman_Ford_optimize {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[] u = new int[m+1];
        int[] v = new int[m+1];
        int[] w = new int[m+1];
        int[] dis = new int[n+1];
        int[] book = new int[n+1];
        int[] first = new int[n+1];
        int[] next = new int[m+1];
        int[] que = new int[10];//比n的值大就可以
        int max  =9999;
        int head = 1,tail = 1;
        for (int i =1; i<=n;i++){
            dis[i] = max;
        }
        dis[1] = 0;

        //初始化book数组
        for (int i =1; i<= n;i++){
            book[i] = 0;//表示每个顶点都不在队列当中
        }
        //初始化first数组
        for (int i =1;i<= n;i++){
            first[i] = -1;//表示每一个顶点都没有对应的边
        }
        for (int i =1;i<=m;i++){
            u[i] = scanner.nextInt();
            v[i] =scanner.nextInt();
            w[i] = scanner.nextInt();
            //下面是建立邻接表的关键
            next[i] = first[u[i]];
            first[u[i]] = i;
        }

        //1号顶点入队
        que[tail] = 1;tail++;//后移
        book[head] = 1;//标记
        while(head<tail){//只要队列不为空就循环
            int k = first[que[head]];//取出当前需要处理的队列头节点
            while(k !=-1){//把que[head]的所有边都遍历到
                if (dis[v[k]] > dis[u[k]] +w[k]){
                    dis[v[k]] = dis[u[k]] + w[k];//更新顶点1到顶点v[k]的路程
                    //这的book数组用来判断定点给v[k]是否在队列当中
                    //如果不适用这样一个数组来标记的话,那么每一次都要从头开始遍历队列,浪费时间
                    if(book[v[k]] ==0){
                        que[tail] = v[k];
                        tail++;
                        book[v[k]] = 1;
                    }
                }
                k = next[k];//取出下一条边
            }
            //出队
            book[que[head]] = 0;//把出队列的顶点置为0,方便下一次入队
            head ++;
        }
        for (int i =1; i<=n;i++){
            System.out.println(dis[i]);
        }
    }
}

最短路径算法对比分析

image-20211006153433974
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小七rrrrr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值