1. 问题描述:
给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为非负值。请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。
输入格式
第一行包含整数 n 和 m。接下来 m 行每行包含三个整数 x,y,z,表示存在一条从点 x 到点 y 的有向边,边长为 z。
输出格式
输出一个整数,表示 1 号点到 n 号点的最短距离。如果路径不存在,则输出 −1。
数据范围
1 ≤ n,m ≤ 1.5 × 10 ^ 5,
图中涉及边长均不小于 0,且不超过 10000。
输入样例:
3 3
1 2 2
2 3 1
1 3 4
输出样例:
3
来源:https://www.acwing.com/problem/content/description/852/
2. 思路分析:
这道题目与849题Dijkstra求最短路 I的题目是一模一样的的,只是数据范围扩大了,如果我们使用朴素版本的Dijkstra算法求解,时间复杂度为O(n ^ 2)肯定会超时,所以我们必须对之前的算法进行优化才能够通过;朴素版本的Dijkstra算法的瓶颈在于寻找当前还没有被访问且与起点s距离最近的点,每一次找这个点的时候都需要循环n次,基于这个想法我们想到从这个瓶颈对之前的算法进行优化,我们可以借助于小根堆来存储与起点s距离最近的点;因为使用的是python语言所以使用heapq模块来模拟小根堆,我们可以声明一个列表q,每一次heapq模块是对列表q进行操作(heapqush和heapop操作),在往列表q中加入元素之后将列表调整为一个小根堆,弹出元素之后将列表q调整为小根堆;具体的操作:一开始时候往q中加入起点信息,包含两个信息,python可以使用元组来封装这些信息,c++可以使用pair,第一个信息是从起点到这个点的最短距离,第二个信息是这个点的编号,当q不为空的时候执行循环,一开始的时候弹出堆顶元素,然后判断弹出元素的点到下一个点的距离是否更短,如果更短那么将到这个点的最短距离与下一个点的编号作为元组加入到q中,...一直到q为空就结束了;假设有向图中点的个数为n,边的个数为m,所以最多有n(n - 1) / 2条边,也即m最多可以看成是n ^ 2,而最多可以更新m次,堆中的边最多为m,调整为小根堆的时间复杂度为O(logm) = O(logn ^ 2) = O(2logn) = O(logn),所以时间复杂度为O(mlogn),则总的时间复杂度为O(1.5 * 10 ^ 5 * 20) = O(3 * 10 ^ 6)是可以通过的。
3. 代码如下:
每一个g[i]是一个字典:
from typing import List
import heapq
class Solution:
# 这里找距离起点最近的点使用堆来维护最小的那个距离以及对应的下标(优化)
# s为起点, t为终点, n为图中节点的数目, g为有向图
def dijkstra(self, s: int, t: int, n: int, g: List[dict]):
vis = [0] * (n + 1)
INF = 10 ** 10
dis = [INF] * (n + 1)
dis[s] = 0
q = list()
heapq.heappush(q, (0, s))
while q:
# 注意这里使用的是heapq.heappop而不是直接从列表弹出之前答案是错的老是检查不出错误, 贼坑
cur = heapq.heappop(q)
# 有的点可能加入两次所以需要判断一下是否被访问过了, 例如题目中的测试数据就是这样的, 3这个点被加入了两次但是一旦这个点被标记了一次说明这个点到其余点的最短距离已经更新完了再更新就没有意思了属于一个小优化, 不加会超时
if vis[cur[1]] == 1: continue
vis[cur[1]] = 1
# next为当前节点的下一个点的信息,next为一个元组, next[0]是下一个点的编号, next[1]是权重
for next in g[cur[1]].items():
# cur[0]是从起点到cur[1]的最短距离
if dis[next[0]] > cur[0] + next[1]:
dis[next[0]] = cur[0] + next[1]
heapq.heappush(q, (dis[next[0]], next[0]))
return dis[t] if dis[t] != INF else -1
# 使用小根堆来优化
def process(self):
n, m = map(int, input().split())
# 每一个g[i]都是一个字典这样可以很方便地找对应节点的权重
g = [dict() for i in range(n + 1)]
for i in range(m):
x, y, z = map(int, input().split())
# if判断可以处理重边的情况, 取的是权重最小的那条边
if y in g[x]:
g[x][y] = min(g[x][y], z)
else:
g[x][y] = z
return self.dijkstra(1, n, n, g)
if __name__ == "__main__":
print(Solution().process())
每一个g[i]是一个列表:
from typing import List
import heapq
class Solution:
def dijkstra(self, s: int, t:int, n: int, g: List[List[int]]):
INF = 10 ** 10
dis = [INF] * (n + 1)
dis[s] = 0
vis = [0] * (n + 1)
q = list()
heapq.heappush(q, (0, s))
while q:
p = heapq.heappop(q)
if vis[p[1]] == 1: continue
vis[p[1]] = 1
for next in g[p[1]]:
if dis[next[0]] > next[1] + p[0]:
dis[next[0]] = next[1] + p[0]
heapq.heappush(q, (dis[next[0]], next[0]))
return dis[t] if dis[t] != INF else -1
def process(self):
n, m = map(int, input().split())
g = [list() for i in range(n + 1)]
for i in range(m):
x, y, z = map(int, input().split())
g[x].append((y, z))
return self.dijkstra(1, n, n, g)
if __name__ == "__main__":
print(Solution().process())
go语言:
package main
import (
"container/heap"
"fmt"
)
type edge struct {
// to表示入边, w表示权重
to, w int
}
// 堆中两个元素
type pair struct{ v, dis int }
// hp为结构体列表
type hp []pair
// 实现小根堆需要用到的几个方法
func (h hp) Len() int {
return len(h)
}
func (h hp) Less(i, j int) bool {
return h[i].dis < h[j].dis
}
func (h hp) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h *hp) Push(v interface{}) {
*h = append(*h, v.(pair))
}
func (h *hp) Pop() (v interface{}) {
a := *h
*h, v = a[0:len(a)-1], a[len(a)-1]
// 下面直接写return也可以
return v
}
func dijkstra(n int, s int, t int, g [][]edge) int {
INF := 10000000000
vis, dis := make([]int, n+10), make([]int, n+10)
for i := 0; i <= n; i++ {
dis[i] = INF
}
// 起点为1, 距离为0
dis[s] = 0
h := hp{{s, 0}}
for len(h) > 0 {
p := heap.Pop(&h).(pair)
v := p.v
if vis[v] == 1 {
continue
}
vis[v] = 1
for _, e := range g[v] {
to := e.to
if w := dis[v] + e.w; w < dis[to] {
dis[to] = w
heap.Push(&h, pair{to, w})
}
}
}
if dis[t] == INF {
dis[t] = -1
}
return dis[t]
}
func main() {
// n, m为点数和边数
var (
n, m, a, b, c int
)
fmt.Scan(&n, &m)
// g存储图(二维结构体, g[i]属于切片, 切片中包含若干个结构体)
g := make([][]edge, n+10)
for i := 0; i < m; i++ {
fmt.Scan(&a, &b, &c)
// 将当前边加入到g中的对应位置上
g[a] = append(g[a], edge{b, c})
}
fmt.Printf("%d\n", dijkstra(n, 1, n, g))
}