Skip to content

230803 Shortest path in Directed Acyclic Graph

【题意】: DAG (有向不循环图)的最短路径

【 Excepted 】:

  • Time Complexity: O(N+M), where N is the number of nodes and M is edges
  • Space Complexity: O(N)

Dijkstra 算法需要的信息有:

  • nexts 数组 / map。 nexts[i] = [ [to, to_w] ] 表示: i 节点到 to 节点花费的代价为 to_w
  • shortest 数组 / map。 shortest[i] = min_dist 表示: 起始节点到 i 节点的最短距离为 min_dist
  • pq 有序数组。 按距离(或代价)从小到大排序。 该数组每个元素存储两个信息: 起始节点到 to 节点花费的代价 src_to_w

容易出错的地方:

  • 初始化 nexts 时,如果某一节点没有邻接节点,则需要初始化为空数组,不然后面访问时会出现 KeyError
  • 从 pq 中取出元素后,要先更新到达元素的最短距离,然后再去更新到达其邻接节点的距离

自己实现优先级队列思路:

  • 首先,明确提供的 API, 在这里我们只提供三个 API:
    • PriorityQueue()
    • pq.empty()
    • pq.put([weight, node])
    • pq.get()
  • 然后,确定私有属性,最重要的属性就是 __items, 它就是优先级队列本身,我们所做的一切都是在维护这个数组。 除 __items 外的属性都是可选的
    • __size, 完全可以使用 len(self.__items) 代替
    • __nodes_map, 空间换时间,因为我们的元素项不是简单的数字,而且数组。
  • 实现我们提供的 API, 在实现过程中,每当修改 __items 时,都要思考此时其他的私有属性是否需要更新! 并且,对于一些复杂操作,先声明一个私有方法占位。 等完成了 API 核心逻辑后,再实现对应的私有方法。在这里我们提供了下面几个私有方法:
    • __insert() 添加新元素到 __items 中
    • __update() 添加的元素已经存在,则更新该元素在优先级队列中的位置
    • __heapify() 向下调整
    • __bubble() 向上调整
    • __swap() 交换

优先级队列有待优化的地方:

  • 限制死了 __items 元素项的类型
  • 可以抽象化 __items 元素项,让他是一个对象,并且提供比较器方法!

Python3

【我的 - 自己实现优先级队列】:

py
from typing import List

class PriorityQueue:

    def __init__(self):
        self.__items = []
        self.__size = 0
        self.__nodes_map = {}

    def empty(self):
        return self.__size <= 0

    def put(self, item):
        if item is None or len(item) < 2:
            raise Exception('Unaccepted item.')

        weight, node = item
        if node not in self.__nodes_map:
            self.__insert(item)
        else:
            self.__update(item)

    def get(self):
        if self.empty():
            raise Exception('Queue is empty.')

        self.__swap(0, self.__size-1)
        res = self.__items.pop()
        self.__size -= 1
        self.__nodes_map.pop(res[1])
        self.__heapify(0)
        return res

    def __insert(self, item):
        self.__size += 1
        self.__items.append(item)
        self.__nodes_map[item[1]] = self.__size-1
        self.__bubble(self.__size-1)

    def __update(self, item):
        new_weight, node = item
        i = self.__nodes_map[node]
        old_weight = self.__items[i][0]
        self.__items[i][0] = new_weight

        if new_weight > old_weight:
            self.__heapify(i)
        else:
            self.__bubble(i)

    def __heapify(self, i):
        while 2*i+1 < self.__size:
            left = 2*i+1
            right = left+1
            min_child = left
            if right < self.__size and self.__items[right][0] < self.__items[min_child][0]:
                min_child = right

            if self.__items[i][0] <= self.__items[min_child][0]:
                break

            self.__swap(i, min_child)
            i = min_child

    def __bubble(self, i):
        parent = (i-1)//2
        while i > 0 and self.__items[i][0] < self.__items[parent][0]:
            self.__swap(i, parent)
            i = parent
            parent = (i-1)//2

    def __swap(self, i, j):
        self.__items[i], self.__items[j] = self.__items[j], self.__items[i]
        self.__nodes_map[self.__items[i][1]] = i
        self.__nodes_map[self.__items[j][1]] = j


class Solution:
    def shortestPath(self, n : int, m : int, edges : List[List[int]]) -> List[int]:
        shorted = [-1] * n
        pq = PriorityQueue()
        pq.put( [0, 0] )

        nexts = {i: [] for i in range(n)}
        for fr, to, w in edges:
            nexts[fr].append( [to, w] )

        while not pq.empty():
            src_to_w, to = pq.get()

            # 自己实现优先级队列后,可以直接修改!
            shorted[to] = src_to_w

            for to_next, to_next_w in nexts[to]:
                if shorted[to_next] == -1 or shorted[to_next] > src_to_w + to_next_w:
                    shorted[to_next] = src_to_w + to_next_w
                    pq.put( [shorted[to_next], to_next] )

        return shorted

【我的 - Dijkstra 】:

py
from typing import List
from queue import PriorityQueue
class Solution:
    def shortestPath(self, n : int, m : int, edges : List[List[int]]) -> List[int]:
        shorted = [-1] * n
        pq = PriorityQueue()
        pq.put( [0, 0] )

        nexts = {i: [] for i in range(n)}
        for fr, to, w in edges:
            nexts[fr].append( [to, w] )

        while not pq.empty():
            src_to_w, to = pq.get()

            if shorted[to] == -1 or shorted[to] > src_to_w: # 这里不能直接更新,因为我们的 pq 中有冗余的数据。
                shorted[to] = src_to_w
                src_to_w = shorted[to]

            for to_next, to_next_w in nexts[to]:
                if shorted[to_next] == -1 or shorted[to_next] > src_to_w + to_next_w:
                    shorted[to_next] = src_to_w + to_next_w
                    pq.put( [shorted[to_next], to_next] )

        return shorted