你必须会的--Dijkstra算法--单源最短路径问题

2,684 阅读3分钟

一.算法原理

1.基本原理

Dijkstra算法是利用广度优先搜索思想(BFS)的一种单源最短路径算法,相对于简单粗暴时间复杂度为O(n^3)的Floyed算法,(详情见我另一篇博客 只有五行的Floyed算法及全路径输出),Dijkstra算法的时间复杂度则有好的多,为O(n^2)。

该算法以起点为中心,将相邻边加入到优先队列中,每次弹出队列中距起点最近的点,利用伸缩操作(relaxation),更新该点各相邻节点到源点的最近距离(这里用到了贪心算法原理), 存入到一个集合disTo中,该集合中记录每一个点到源点的最近距离。

伪代码:

while(!queue.isEmpty())
	//利用最小优先队列,取到源点距离最近节点
	node = queue.poll()
	for each neighbor node的相邻节点
		if neighbor没有被访问过
			queue.push(neightbor)
		if disTo.get(neighbor) > disTo.get(node) + node到该相邻点距离
			//记录路径
			neighbor.parent = node;
			//relaxation伸缩操作
			disTo.put(neighbor, disTo.get(node) + node到该相邻点距离)
			//其他操作
			todo...

2.如何保存最短路径?

在pathTo集合中,设置此节点的上一节点。如果这点没有被访问过,就加入到优先队列中,就这样重复操作层层向外遍历,最后就可以生成一个最短路径树,对于从该源点到某一点的最短路径问题,只要看该点是否被访问过,被访问过的点说明存在最短路径,回溯pathTo集合,如pathTo(A) = B, B是使A到源点距离最近的相邻点(由贪心算法可知),pathTo(B) = C , C是使B到源点距离最近的相邻点,反复操作,直到pathTo(X) = 源点。即可得到最短路径

二.算法实现

package com.example.Dijkstra;

import java.util.*;

public class Dijkstra {
    // <1, [2, 3]>表示节点1的父节点是节点2,到源点距离为3
    private Map<Integer, int[]> disTo;
    /**
     * @param edges 一个节点到其他节点的距离
     *              [[0, 1, 1], [1, 2, 2]] 表示点0到点1的距离为1,点1到点2的距离为2
     * @param n   所有节点个数  1<= n <= 1000
     * @param k   源节点   1< k <n
     */
    public Map<Integer, int[]> Dijkstra(int[][] edges, int n, int k) {
        //通过edges数组生成有向图
        //<0, <{1,2}, {2,3}>>表示节点0有1,2两个相邻节点,距离分别为2, 3
        Map<Integer, List<int[]>> graph = new HashMap<>();
        for (int[] edge : edges) {
            if (!graph.containsKey(edge[0]))
                graph.put(edge[0], new ArrayList<>());
            graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
        }
        //初始化disTo
        disTo = new HashMap<>();
        for(int i = 0; i<n; i++){
            if(i==k)
                disTo.put(i, new int[]{0, 0});
            else disTo.put(i, new int[]{-1, Integer.MAX_VALUE});
        }
        //Dijkstra
        //省略了最小优先队列,用isSeen数组辅助disTo来取代
        boolean[] isSeen = new boolean[n];
        while(true){
            //得到距离最近点
            int candiNode = -1;
            int candiDistance = Integer.MAX_VALUE;
            for(int i=0;i<n;i++){
                int[] temp = disTo.get(i);
                if(!isSeen[i] && temp[1]<candiDistance){
                    candiNode = i;
                    candiDistance = temp[1];
                }
            }
            if(candiNode == -1) break;
            isSeen[candiNode] = true;
            if(graph.containsKey(candiNode))
                for(int[] edge : graph.get(candiNode)){
                    if(disTo.get(edge[0])[1]> candiDistance + edge[1]){
                    	//对该点相邻点进行伸缩操作
                        disTo.get(edge[0])[1] = candiDistance + edge[1];
                        //更新父节点
                        disTo.get(edge[0])[0] = candiNode;
                    }
                }
        }
        return disTo;
    }

    /**
     * 输出结果
     * @param disTo
     * @param end
     */
    public void printPath(Map<Integer, int[]> disTo,int pathTo){
        int distance = disTo.get(pathTo)[1];
        List<Integer> path = new ArrayList<>();
        int temp = pathTo;
        path.add(temp);
        while (temp!=0 && temp!=-1){
            temp = disTo.get(temp)[0];
            path.add(temp);
        }
        System.out.print("从初始节点到节点"+end+"的最短距离为"
        				+distance+"\n"+"最短路径为:\n"+path.get(0));
        for(int i=1;i<path.size();i++){
            System.out.print("<--"+path.get(i));
        }
    }

}

1.测试

输入:

edges: n: k: pathTo
{{0, 1, 15},{0, 3, 5}, 6 0 4
{1, 5, 6}, {3, 5, 20},
{1, 2, 15}, {3, 2, 30},
{2, 4, 10}, {5, 4, 9}};

预计输出:最短距离:30 路径:0-->1-->5-->4

环境:windows10,java11

2.结果

在这里插入图片描述

会了Dijkstra,不会DFS那更不得行,帮你安排了DFS的递归实现与堆栈实现