【LeetCode】743 and 917(Dijkstra 算法,最小路径算法)

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值