最短路之Dijkstra 算法

适用范围: 单源点到其他节点的最短路径求解;不包括负权边(因为是基于贪心的,边相加会变大才行);

1. 传统最短路算法

将所有节点分成两类:已确定从起点到当前点的最短路径长度的节点,以及未确定从起点到当前点的最短路径长度的节点(下面简称[未确定节点] 和 [以确定节点])。
每次从「未确定节点」中取一个与起点距离最短的点,将它归类为「已确定节点」,并用它「更新」从起点到其他所有「未确定节点」的距离。直到所有点都被归类为「已确定节点」。
用节点 A「更新」节点 B 的意思是,用起点到节点 A 的最短路长度加上从节点 A 到节点 B 的边的长度,去比较起点到节点 B 的最短路长度,如果前者小于后者,就用前者更新后者。这种操作也被叫做「松弛」。

时间复杂度:O(n^2)

空间复杂度:O(m) ,其中 m 为图中边的数量。
java代码实现

// 邻接矩阵
int[][] mapp = new int[n][n];
// 最短路记录
int[] dis = new int[n];
// visited[i] = true 表示 i 节点已找到最短路径
boolean[] visited = new int[n];
// 找 v0 到其他节点的最短路
public void Dijkstra(int v0) {
	// 初始化
    for (int i = 1; i <= n; i++) {
        dis[i] = mapp[v0][i];
        visited[i] = false;
    }
    // 初始 v0 已找到最短路
    visited[v0] = true;
    // 每次执行找到一个节点的最短路径
    for (int i = 1; i <= n; i++) {
        int p, minn = Integer.MAX_VALUE;
        // 找到当前的最短路径
        for (int j = 1; j <= n; j++) {
            if (!visited[j] && dis[j] < minn) {
                p = j;
                minn = dis[j];
            }
        }
        // 记录
        visited[p] = true;
        // 松弛操作,更新与其他点的距离
        for (int j = 1; j <= n; j++) {
            if (!visited[j] && dis[p] + mapp[p][j] < dis[j]) {
                dis[j] = dis[p] + mapp[p][j];
            }
        }
    }
    return;
}

2. 基于堆优化的Dijkstra 算法

**适用范围:**稀疏图,因为堆的时间复杂度与边的数量有关。
概念:

传统方法中每次需要在[未确定节点]中找到距离最小的节点,这个时间复杂度时O(N), 在这个步骤上利用堆的性质进行优化,用小顶推进行存储[未确定节点]。堆的存取时间为O(logN),因此可以节省时间。

Java代码实现
首先对节点进行封装:

// 自定义比较类
class Pair implements Comparable<Pair>{
    int node;
    int distance;
    public Pair(int node, int distance){
        this.node = node;
        this.distance = distance;
    }

    @Override
    public int compareTo(Pair pair2) {
        return this.distance - pair2.distance;
    }
}

Pair 类代表的意思是某个节点到 node 节点的距离是 distance 。重写了compareTo方法使之可以入队后默认从小到大排序。也可以用长度为2的一维数组代替。
整体代码:

/**
	n: 节点个数
	edges:   egdes[i] = [1, 2, 3] 代表1到2有路径,距离为3。
	本方法求第 n 个节点到其他节点的最短路径 
*/
public int[] Dijkstra(int n, int[][] edges) {
        int INF = Integer.MAX_VALUE;
        // 构建邻接表 graph.get(i) 也是个list,存储与节点 i 直接相邻的节点以及距离(Pari 对象)
        List<List<Pair>> graph = new ArrayList<>();
        for (int i = 0; i <= n; i++) {
            graph.add(new ArrayList<Pair>());
        }
        for (int[] edge : edges){
           graph.get(edge[0]).add(new Pair(edge[1], edge[2]));
           graph.get(edge[1]).add(new Pair(edge[0], edge[2]));
        }
        // 优先级队列
        PriorityQueue<Pair> pq = new PriorityQueue<Pair>();
        // 要求 n 到其他节点的最短路径,因此将 n  的邻接节点封装的 Pair 实体入队。
        for(Pair pair : graph.get(n)){
            pq.offer(pair);
        }
        // 答案数组,默认值为 INF
        int[] res = new int[n];
        Arrays.fill(res, INF);
       // 到自己的最短路径是 0
        res[n - 1] = 0;
        while (!pq.isEmpty()){
           // 队头元素就是当前距离最短的节点元素,无需遍历
            Pair poll = pq.poll();
            int node = poll.node;
            int dis = poll.distance;
            // 如果比答案里的要大,说明在这之前就已经通过其他节点找到了更优的路径,不做操作。
            if(dis > res[node - 1]){
                continue;
            }
            // 设置最短路径
            res[node - 1] = dis;
            // 通过该节点对其他节点进行松弛操作
            for(Pair pair : graph.get(node)){
            	// 松弛成功,更新最短路径,并将新的路径信息封装成 Pair 对象入队。
                if(res[pair.node - 1] > dis + pair.distance){
                    res[pair.node - 1] =  dis + pair.distance;
                    pq.offer(new Pair(pair.node, res[pair.node - 1]));
                }
            }
        }
        return res;
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值