Dijkstra 算法
对于Dijkstra 算法,我们可以借助BFS算法来进行理解。
首先,我们来看下Dijkstra 算法的签名:
# 输⼊⼀幅图和⼀个起点 start,计算 start 到其他节点的最短距离,其中graph以邻接表形式保存
def dijkstra(start, graph)
输⼊是⼀幅图 graph 和⼀个起点 start,返回是⼀个记录最短路径权重的数组。
其次,普通 BFS 算法中,根据 BFS 的逻辑和⽆权图的特点,第⼀次遇到某个节点所⾛的步数就是最短距离,所以⽤⼀个 visited 数组(备忘录)防⽌⾛回头路,每个节点只会经过⼀次。加权图中的 Dijkstra 算法和⽆权图中的普通 BFS 算法不同,在 Dijkstra 算法中,你第⼀次经过某个节点时的路径权重,不⻅得就是最⼩的,所以对于同⼀个节点,我们可能会经过多次,⽽且每次的距离start的最短距离 可能都不⼀样,⽐如下图:
我会经过节点 5 三次,每次的 distFromStart 值都不⼀样,那我取distFromStart 最⼩的那次,不就是从起点 start 到节点 5 的最短路径权重了么?
好了,明⽩上⾯的⼏点,我们可以来看看 Dijkstra 算法的代码模板。
# 返回节点from 到 节点 to 之间边的权重
def weight(form ,to)
# 图的邻接表
graph = {}
# 输入一幅图和⼀个起点 start,计算 start 到其他节点的最短距离。
def dijkstra(start, graph):
# 图中节点个数
n = len(graph)
# 记录最短路径的权重
# 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重。初始化为正无穷
distTo = [float("inf") for _ in range(n)]
# base case,start 到 start 的最短距离就是 0
distTo[start] = 0
# 优先队列,distFromStart 较⼩的排在前⾯。
# 贪心思维,对于每个状态,选择权值最小的那条边前进
pq = PriorityQueue()
# 从起点开始进行BFS
pq.put([0, start])
while not pq.empty():
curDistFromStart, curNodeId = pq.get()
if (curDistFromStart > distTo[curNodeID]):
# 已经有⼀条更短的路径到达 curNode 节点了
continue
# 将curNode的相邻节点装入队列,(层序遍历)
for nextNodeID in graph[curNodeID]:
# 看看从 curNode 达到 nextNode 的距离是否会更近
distToNextNode = distTo[curNodeID] + weight(curNodeID,
nextNodeID)
if distTo[nextNodeID] > distToNextNode:
# 更新 dp table
distTo[nextNodeID] = distToNextNode
# 将这个节点以及距离放⼊队列
pq.put(distToNextNode, nextNodeID)
return distTo;
对⽐普通的 BFS 算法,你可能会有以下疑问:
1、没有 visited 集合记录已访问的节点,所以⼀个节点会被访问多次,会被多次加⼊队列,那会不会导致队列永远不为空,造成死循环?
循环结束的条件是队列为空,那么你就要注意看什么时候往队列⾥放元素(调⽤ offer)⽅法,再注意看什么时候从队列往外拿元素(调⽤ poll ⽅法)。
while 循环每执⾏⼀次,都会往外拿⼀个元素,但想往队列⾥放元素,可就有很多限制了,必须满⾜下⾯这个条件:如果你能让到达 nextNodeID 的距离更短,那就更新 distTo[nextNodeID] 的值,让你⼊队,否则的话不让⼊队。
因为两个节点之间的最短距离(路径权重)肯定是⼀个确定的值,不可能⽆限减⼩下去,所以队列⼀定会空,队列空了之后,distTo 数组中记录的就是从 start 到其他节点的最短距离。
2、为什么⽤优先级队列 PriorityQueue ⽽不是 LinkedList 实现的普通队列?为什么要按照distFromStart 的值来排序?
如果你⾮要⽤普通队列,其实也没问题的,你可以直接把 PriorityQueue 改成 LinkedList,也能得到正确答案,但是效率会低很多。
Dijkstra 算法使⽤优先级队列,主要是为了效率上的优化,类似⼀种贪⼼算法的思路。
743. 网络延迟时间
解法:Dijkstra 算法
原理和算法框架如文章开头所述。
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
graph = self.buildGraph(times, n)
# print(graph)
distances = [float("inf") for _ in range(n+1)]
distances[k] = 0
# return -1
return dijkstra(graph, k, distances)
# 有向加权图的构造方案
def buildGraph(self, times, n):
graph = {}
for i in range(1,n+1):
graph[i] = []
for time in times:
h, r, t = time
graph[h].append([r, t])
return graph
from queue import PriorityQueue
def dijkstra(graph, start, distances):
pq = PriorityQueue()
# root = State(start, 0)
pq.put([0, start])
# print(pq.queue)
while not pq.empty():
# print(pq.queue)
currDis, node = pq.get()
if currDis > distances[node]:
continue
for neighbor in graph[node]:
nextNode, weight = neighbor
if distances[nextNode] > (currDis+weight):
distances[nextNode] = currDis + weight
# newNode = State(nextNode, currDis+weight)
pq.put([distances[nextNode], nextNode])
result = 0
# print(distances)
for dis in distances[1:]:
if dis > 100:
return -1
if dis > result:
result = dis
return result
917. 仅仅反转字母
解法:双指针
我们使用 left 指针从左边开始扫描字符串 s,right 指针从右边开始扫描字符串 s。如果两个指针都扫描到字母,且 left < right,那么交换 s[left] 和 s[right],然后继续进行扫描;否则表明反转过程结束,返回处理后的字符串。
class Solution:
def reverseOnlyLetters(self, s: str) -> str:
s = list(s)
left, right = 0, len(s)-1
while not s[right].isalpha() and left < right:
right -= 1
while left < right:
if s[left].isalpha():
s[right], s[left] = s[left], s[right]
right -= 1
while not s[right].isalpha():
right -= 1
left += 1
return "".join(s)