【学习 & 记录】狄克斯特拉算法 - Java

最近学习《算法图解》一书时,看到狄克斯特拉算法之前没有使用过,在学习后用常用语言Java将算法实现出来以加深印象。

作用

狄克斯特拉算法用于找出最快的路径,而常用的广度优先搜索算法可用于找出最短的路径

限制

狄克斯特拉算法作用于:单向、加权图且无负权边

算法步骤

  1. 在未处理的节点中找出开销最小的节点
  2. 遍历开销最小节点的邻居(邻边)
  3. 如果经当前节点前往该邻居更近,则更新到达该邻居的开销
  4. 同时将该邻居的父节点设置为当前节点
  5. 遍历完当前节点的邻边后,将当前节点标记为已处理
  6. 找出下一个开销最小且未处理过的节点,循环上面的步骤

目标图

在这里插入图片描述

算法步骤

1. 将图转换成数据结构

这里将图转换成Map

/**
 * 将图转化成Map
 *
 * @return
 */
private static Map<String, Map<String, Integer>> initGraphMap() {
    Map<String, Integer> startPoint = new HashMap<>();
    startPoint.put("A", 6);
    startPoint.put("B", 2);

    Map<String, Integer> aPoint = new HashMap<>();
    aPoint.put("final", 1);

    Map<String, Integer> bPoint = new HashMap<>();
    bPoint.put("A", 3);
    bPoint.put("final", 5);

    Map<String, Map<String, Integer>> graphMap = new HashMap<>();
    graphMap.put("start", startPoint);
    graphMap.put("A", aPoint);
    graphMap.put("B", bPoint);
    graphMap.put("final", new HashMap<>());

    return graphMap;
}

转换后的关系图如下:
在这里插入图片描述

2. 初始化记录达到每个节点的花销数据结构

因为我们肯定是从起点开始,所以需要先将起点能够达到的所有邻边的花销记录起来

private static void initMap(String startPoint) {
	 // 通过起点找关联的邻边
    Map<String, Integer> pointMap = GRAPH_MAP.get(startPoint);
    // 如果起点没有邻边,就直接返回结束
    if (pointMap == null) {
        return;
    }

    // 一开始起点能到达的地方,且需要花费的金钱,就等于costsMap的初始化Map
    COSTS_MAP = pointMap;
    // 需要给costsMap加上达到终点的花费,默认为Integer.MAX_VALUE
    COSTS_MAP.put("final", Integer.MAX_VALUE);

    // 初始化parentsMap,维护好起点和邻边的关系
    for (Map.Entry<String, Integer> pointEntry : pointMap.entrySet()) {
        // 起点能达到的地方
        String point = pointEntry.getKey();
        PARENTS_MAP.put(point, "start");
    }
}

初始化后的开销图如下:
在这里插入图片描述
初始化后的父节点关系图如下:
在这里插入图片描述

3. 找出开销最小且未处理过的节点

private static String findLowestCostPoint() {
    Integer minCost = Integer.MAX_VALUE;
    String targetPoint = null;
    // 遍历costsMap,找出花费最小的节点
    for (Map.Entry<String, Integer> costEntry : COSTS_MAP.entrySet()) {
        String point = costEntry.getKey();
        Integer cost = costEntry.getValue();
        if (cost < minCost && !HISTORY_SET.contains(point)) {
            minCost = cost;
            targetPoint = point;
        }
    }
    return targetPoint;
}

4. 遍历开销最小节点的邻边,如果经当前节点前往邻边更近,则更新到达该邻边的开销,并且维护父节点的关系

public static void main(String[] args) {
    if (COSTS_MAP == null) {
        System.out.println("地图为空!");
        return;
    }
    String lowestCostPoint = findLowestCostPoint();
    while (lowestCostPoint != null) {
        // 首先拿到到达给当前最小花费节点所需费用
        Integer cost = COSTS_MAP.get(lowestCostPoint);

        // 获取到最小花费节点所能到达的节点列表
        Map<String, Integer> targetPointMap = GRAPH_MAP.get(lowestCostPoint);
        // 遍历最小花费节点所能到达的节点,并计算所需花费
        for (Map.Entry<String, Integer> targetEntry : targetPointMap.entrySet()) {
            String currentPoint = targetEntry.getKey();
            // 到达当前节点所需费用
            Integer newCost = targetEntry.getValue() + cost;
            // 判断当前所能到达的节点,是否已经存在costsMap当中
            if (COSTS_MAP.containsKey(currentPoint)) {
                // 如果存在,则判断不同路径到达该节点的费用哪个更便宜
                if (newCost < COSTS_MAP.get(currentPoint)) {
                    // 如果新的路径更便宜,则更新costsMap以及parentsMap
                    COSTS_MAP.put(currentPoint, newCost);
                    PARENTS_MAP.put(currentPoint, lowestCostPoint);
                }
                continue;
            }
            // 如果不存在,则直接更新costsMap以及parentsMap
            COSTS_MAP.put(currentPoint, newCost);
            PARENTS_MAP.put(currentPoint, lowestCostPoint);
        }
        // 将当前节点从添加到已处理列表中
        HISTORY_SET.add(lowestCostPoint);
        // 再次寻找花费最小的节点
        lowestCostPoint = findLowestCostPoint();
    }
}

开销图的执行经过,绿色背景表示已经处理过,下次不可再处理的节点:
在这里插入图片描述父节点关系维护图的执行经过,通过这个关系图,我们可以还原整个最快路径:
在这里插入图片描述

Show me the code

public class Dijkstra {
    // GRAPH,将图转换成Map
    private static Map<String, Map<String, Integer>> GRAPH_MAP;

    // COSTS,记录到达每个节点的花费
    private static Map<String, Integer> COSTS_MAP;

    // PARENTS,记录父节点路径
    private static Map<String, String> PARENTS_MAP = new HashMap<>();

    // 记录已经处理过的节点
    private static Set<String> HISTORY_SET = new HashSet<>();

    public static void main(String[] args) {
        // 将图初始化成Map
        GRAPH_MAP = initGraphMap();
        // 根据graphMap,计算出初始化的花费和父节点Map
        initMap("start");

        if (COSTS_MAP == null) {
            System.out.println("地图为空!");
            return;
        }
        String lowestCostPoint = findLowestCostPoint();
        while (lowestCostPoint != null) {
            // 首先拿到到达给当前最小花费节点所需费用
            Integer cost = COSTS_MAP.get(lowestCostPoint);

            // 获取到最小花费节点所能到达的节点列表
            Map<String, Integer> targetPointMap = GRAPH_MAP.get(lowestCostPoint);
            // 遍历最小花费节点所能到达的节点,并计算所需花费
            for (Map.Entry<String, Integer> targetEntry : targetPointMap.entrySet()) {
                String currentPoint = targetEntry.getKey();
                // 到达当前节点所需费用
                Integer newCost = targetEntry.getValue() + cost;
                // 判断当前所能到达的节点,是否已经存在costsMap当中
                if (COSTS_MAP.containsKey(currentPoint)) {
                    // 如果存在,则判断不同路径到达该节点的费用哪个更便宜
                    if (newCost < COSTS_MAP.get(currentPoint)) {
                        // 如果新的路径更便宜,则更新costsMap以及parentsMap
                        COSTS_MAP.put(currentPoint, newCost);
                        PARENTS_MAP.put(currentPoint, lowestCostPoint);
                    }
                    continue;
                }
                // 如果不存在,则直接更新costsMap以及parentsMap
                COSTS_MAP.put(currentPoint, newCost);
                PARENTS_MAP.put(currentPoint, lowestCostPoint);
            }
            // 将当前节点从添加到已处理列表中
            HISTORY_SET.add(lowestCostPoint);
            // 再次寻找花费最小的节点
            lowestCostPoint = findLowestCostPoint();
        }
        // 得到最短路径
        List<String> path = getPath();
        System.out.println("最短路径为:" + path.toString());
    }

    private static List<String> getPath() {
        List<String> path = new LinkedList<>();
        String parent = PARENTS_MAP.get("final");
        path.add(0, "final");
        while (parent != null) {
            path.add(0, parent);
            parent = PARENTS_MAP.get(parent);
        }
        return path;
    }

    private static String findLowestCostPoint() {
        Integer minCost = Integer.MAX_VALUE;
        String targetPoint = null;
        // 遍历costsMap,找出花费最小的节点
        for (Map.Entry<String, Integer> costEntry : COSTS_MAP.entrySet()) {
            String point = costEntry.getKey();
            Integer cost = costEntry.getValue();
            if (cost < minCost && !HISTORY_SET.contains(point)) {
                minCost = cost;
                targetPoint = point;
            }
        }
        return targetPoint;
    }

    private static void initMap(String startPoint) {
        Map<String, Integer> pointMap = GRAPH_MAP.get(startPoint);
        if (pointMap == null) {
            return;
        }

        // 一开始起点能到达的地方,且需要花费的金钱,就等于costsMap的初始化Map
        COSTS_MAP = pointMap;
        // 需要给costsMap加上达到终点的花费,默认为Integer.MAX_VALUE
        COSTS_MAP.put("final", Integer.MAX_VALUE);

        // 初始化parentsMap
        for (Map.Entry<String, Integer> pointEntry : pointMap.entrySet()) {
            // 起点能达到的地方
            String point = pointEntry.getKey();
            PARENTS_MAP.put(point, "start");
        }
    }

    /**
     * 将图转化成Map
     *
     * @return
     */
    private static Map<String, Map<String, Integer>> initGraphMap() {
        Map<String, Integer> startPoint = new HashMap<>();
        startPoint.put("A", 6);
        startPoint.put("B", 2);

        Map<String, Integer> aPoint = new HashMap<>();
        aPoint.put("final", 1);

        Map<String, Integer> bPoint = new HashMap<>();
        bPoint.put("A", 3);
        bPoint.put("final", 5);

        Map<String, Map<String, Integer>> graphMap = new HashMap<>();
        graphMap.put("start", startPoint);
        graphMap.put("A", aPoint);
        graphMap.put("B", bPoint);
        graphMap.put("final", new HashMap<>());

        return graphMap;
    }
}

结尾

算法的实现以及圈复杂度都存在优化的空间,后续想优化的时候再优化~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值