最短路径问题(附航班最小价格练习)

dijkstra算法

算法描述

Dijkstra算法是解决单源最短路径问题的贪心算法,它先求出长度最短的一条路径,再参照该最短路径求出长度次短的一条路径,直到求出从源点到其他各个顶点的最短路径。

题目引入

给定一个景点地图,景点编号[0,1…5],各个景点之间的路况以及路的长度如图所示,给定一个起始点0,求出该点到其他所有点的最短路径。

在这里插入图片描述

解题思路

  1. 确定数据结构:通常情况下,大部分图都可以使用矩阵来存储,可根据需求选择对应的数据结构,这里通过简单的矩阵(二维数组[][])存储图。
  2. 初始化:在算法中,我们指定某一节点u为源点,对于源点到其他节点的距离,我们需要更新到最短路径数组dist[]中,dist[i]=G[u][i]。
  3. 找最小:根据贪心策略,找未遍历节点中,距离已遍历节点的最小距离的节点,访问该节点并标记该节点的状态为已遍历。
  4. 松弛操作:对于已遍历的节点,检查是否可以通过新遍历到的节点得到更短的"源点-旧节点"的距离。
  5. 重复执行3、4:当所有的节点都已被访问到(状态都标记为已遍历),算法结束。

符号含义

  • G[][]:图的邻接矩阵表示
  • dist[]:起始点到所有点的距离(最终迭代为最短距离)
  • flag[]:boolean类型,记录点的访问情况,已访问为true,未访问未false

图解过程

确定数据结构
  • 用矩阵(二维数组)

G [ ] = [ 0 2 5 ∞ ∞ ∞ 0 2 6 ∞ ∞ ∞ 0 7 1 ∞ ∞ 2 0 4 ∞ ∞ ∞ ∞ 0 ] G[] = \left[ \begin{matrix} 0 & 2 & 5 & \infty & \infty \\ \infty & 0 & 2 & 6 & \infty \\ \infty & \infty &0 & 7 & 1 \\ \infty & \infty & 2 & 0 & 4 \\ \infty & \infty & \infty & \infty & 0 \\ \end{matrix} \right] G[]=0205202670140

初始化

我们约定,假设存在两个集合,已访问节点放在集合S中,未访问节点放在U中。对最短距离数组dist[]进行初始化,dist[i] = G[0][i]

  • S={0}
  • U={1,2,3,4}
01234
dist[]025
01234
flag[]truefalsefalsefalsefalse
找最小1

根据贪心策略,查找distp[]中最小的距离,dist[1]= 2 ,将点1添加进S集合

  • S={0,1}
  • U={2,3,4}
01234
flag[]truetruefalsefalsefalse
松弛操作1

对于U集合的所有点,检查是否可以借助刚添加进来的1号点,更新起始点到U中其他点的距离

  • 1号点可以更新0-2的距离:dist[2] = dist[1] + G[1][2] = 2+2
  • 1号点可以更新0-3的距离:dist[3] = dist[1] + G[1][3] = 2+6
01234
dist[]0248
找最小2

根据贪心策略,查找distp[]中最小的距离,dist[2] = 4 ,将点2添加进S集合

  • S={0,1,2}
  • U={3,4}
01234
flag[]truetruetruefalsefalse
松弛操作2

对于U集合的所有点,检查是否可以借助刚添加进来的2号点,更新起始点到U中其他点的距离

  • 2号点可以更新0-4的距离:dist[4] = dist[2] + G[2][4] = 4+1
01234
dist[]02485
找最小3

根据贪心策略,查找distp[]中最小的距离,dist[2] = 4 ,将点2添加进S集合

  • S={0,1,2,4}
  • U={3}
01234
flag[]truetruetruefalsetrue
松弛操作3

对于U集合的所有点,检查是否可以借助刚添加进来的4号点,更新起始点到U中其他点的距离

  • 4号节点没有邻接节点,没得更新
01234
dist[]02485
找最小4

根据贪心策略,查找distp[]中最小的距离,此时只剩下3号点未被访问 ,将点3添加进S集合

  • S={0,1,2,4,3}
  • U={}
01234
flag[]truetruetruetruetrue
算法结束

当所有的点均被访问,算法结束。得到最终的最短距离数组dist[]

  • 起始点 0 - 0 的距离:0
  • 起始点 0 - 1 的距离:2
  • 起始点 0 - 2 的距离:4
  • 起始点 0 - 3 的距离:8
  • 起始点 0 - 4 的距离:5
01234
dist[]02485

代码实现

//代码算出了起始点start到所有点的最短距离,若想返回start到所有点的最短距离,返回值改成dist即可

public class Dijkstra {
    public static void main(String[] args) {
        int max = Integer.MAX_VALUE/2;
        int[][] G = {{0,2,5,max,max},{max,0,2,6,max},{max,max,0,7,1},{max,max,2,0,4},{max,max,max,max,0}};
        System.out.println(dijkstra(G,0,4));

    }
    public static int dijkstra(int[][] G,int start,int end){
        //初始化
        int[] dist = new int[G.length];
        boolean[] flag = new boolean[G.length];
        for (int i = 0; i < G.length; i++) {
            dist[i]=G[start][i];
        }
        //初始的集合S中只有起始点
        flag[start]=true;

        //外层for循环:重复找最小、松弛操作
        for (int i = 0; i < G.length; i++) {
            int temp = Integer.MAX_VALUE/2;
            int small = start;

            //找最小
            for (int j = 0; j < G.length; j++) {
                if(!flag[j] && dist[j]<temp){
                    small=j;
                    temp = dist[j];
                }
            }
            flag[small]=true;      //标记该点为已访问

            //松弛操作
            for (int j = 0; j < G.length; j++) {
                if(!flag[j] && dist[j]>dist[small]+G[small][j]){
                    dist[j]=dist[small]+G[small][j];
                }
            }
        }
        return dist[end];
    }
}

扩展题

有 n 个城市通过一些航班连接。给你一个数组 flights ,其中 flights[i] = { start(i), end(i), price(i) } ,表示该航班都从城市 start(i) 开始,以价格 price(i) 抵达 end(i)。

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,求一条中转的路线,使得从 src 到 dst 的 总价格最便宜 ,并返回该价格。

在这里插入图片描述

思路:
  • 这样的题仍然是可以用图去解决的,但是要进行映射操作,即{“广州=>0”},{“深圳”=>1},将城市名称映射成数字,就可以像第一个例子那样去解了。
代码:
  1. 航班类
/**
 * 航班类
 */
public class Flight {
    private String start, end;
    private int  price;

    Flight(String s, String e, int p) {
        start = s;
        end = e;
        price = p;
    }
    
    // 获取起点名称
    public String getStart() {
        return start;
    }

    // 获取终点名称
    public String getEnd() {
        return end;
    }

    // 获取从起点到终点的机票价格
    public int getPrice() {
        return price;
    }
}
  1. 方法类
public class TestDemo {
    
    public static void main(String[] args) {
        Flight[] flight = new Flight[8];
        Flight flight1 = new Flight("广州","深圳",2);
        Flight flight2 = new Flight("广州","上海",5);
        Flight flight3 = new Flight("深圳","上海",2);
        Flight flight4 = new Flight("深圳","北京",6);
        Flight flight5 = new Flight("上海","北京",7);
        Flight flight6 = new Flight("上海","重庆",1);
        Flight flight7 = new Flight("北京","上海",2);
        Flight flight8 = new Flight("北京","重庆",4);
        flight[0]=flight1;
        flight[1]=flight2;
        flight[2]=flight3;
        flight[3]=flight4;
        flight[4]=flight5;
        flight[5]=flight6;
        flight[6]=flight7;
        flight[7]=flight8;
        
        TestDemo ts = new TestDemo();
        String src = "广州";
        String dst = "北京";
        int minPrice = ts.findCheapestPrice(5,flight,src,dst);
        System.out.println(src+"到"+dst+"的最小价格为:"+minPrice);  
    }

    /**
     * 计算最便宜的机票总价钱
     * @param n 表示最多总共有n个城市
     * @param flights 所有航班信息,以对象数组给出,每个Flight对象表示一个航班数据(起点-终点-价格)
     * @param src 起点的名称
     * @param dst 终点的名称
     * @return 搜索一条路径可以从起点转乘一直飞到终点,并且机票总价格最小,输出最小机票价格
     */
    public int findCheapestPrice(int n, Flight[] flights, String src, String dst) {
        //1.准备图,做【节点-数字索引】的映射
        int[][] G = new int[n][n];      //图
        int[] dist = new int[n];        //源点到各点的最短路径数组
        boolean flag[] = new boolean[n];    //节点对应的访问状态数组
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if(i==j){
                    G[i][j]=0;
                }else{
                    G[i][j]=100000;
                }
            }
        }
        //2.映射
        Set<String> pointSet = new HashSet<>();         //节点集合
        Map<String,Integer> pointMap = new HashMap<>(); //节点-数字索引的映射
        int m = 0;
        for (Flight flight : flights) {
            pointSet.add(flight.getStart());
            pointSet.add(flight.getEnd());
        }
        for (String s : pointSet) {
            pointMap.put(s,m++);
        }

        //航班数据映射填充到图的矩阵中
        for (Flight flight : flights) {
            String start = flight.getStart();
            String end = flight.getEnd();
            int price = flight.getPrice();
            G[pointMap.get(start)][pointMap.get(end)]=price;
        }

        //3.初始化源点到所有节点的最短距离数组dist[]
        for(int i=0;i<dist.length;i++){
            dist[i]=G[pointMap.get(src)][i];
        }
        flag[pointMap.get(src)]=true;
        dist[pointMap.get(src)]=0;

        //重复【找最小-松弛操作】,直到所有节点都被访问
        for(int i=0;i<n;i++){
            int temp = 100000;
            int small = pointMap.get(src);
            //4.找最小,当节点未被访问,且小于定义的无穷标记10000
            for (int j = 0; j < n; j++) {
                if(!flag[j]&&dist[j]<temp){
                    //small和temp一值在更新,保证找到最小的
                    small=j;
                    temp=dist[j];
                }
            }
            //找到了,这个点对应的状态数组中标记为true,代表已访问
            flag[small]=true;

            //5.松弛操作,借助上面找到的点进行距离更新
            for (int j = 0; j < n; j++) {
                //如果原先的距离:[源点->该节点] > [源点->新节点]+[新节点->该节点],更新最短距离
                if(!flag[j] && dist[j]>dist[small]+G[small][j]){
                    dist[j]=dist[small]+G[small][j];
                }
            }
        }
        return dist[pointMap.get(dst)];
    }
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值