📜  门| GATE-CS-2007 |问题 18(1)

📅  最后修改于: 2023-12-03 15:42:16.679000             🧑  作者: Mango

门 | GATE-CS-2007 | 问题 18

本题是 GATE 计算机科学考试的一道问题。它涉及到了图论中的最短路径问题以及动态规划技巧。

问题描述:

在一个城市里,有 n 个公路门,它们使用 1 到 n 的编号进行标识。每个门可以通向同一或不同的目的地。每个道路都是双向的,有一个指定的时间值。需要设计一个算法来计算从门 1 到门 n 的最短时间。

输入格式:

输入包括两个部分,第一部分是门的总数和道路的总数,以及道路的详细信息。 具体格式如下:

n m
x1 y1 w1
x2 y2 w2
...
xm ym wm

其中,n 表示门的总数,m 表示道路的总数,xi 和 yi 分别表示第 i 条道路的起点和终点,wi 表示运行时间。

输出格式:

输出从门 1 到门 n 的最短时间。

示例:

输入:

3 3
1 2 2
2 3 3
1 3 4

输出:

5

解释:

从门 1 到门 3,共有两条路径:1, 2, 3,长度为 5;1, 3,长度为 4。因此,所求结果为 5。

解题思路

本题的输入为一个图,而我们要找的是从 1 号门到 n 号门的最短路径。这引导我们想到了最短路径算法,最短路径算法有多种,包括 Dijkstra 算法、Floyd 算法、Bellman-Ford 算法等。这里推荐使用 Dijkstra 算法,因为它的时间复杂度与边的数量有关,而 Bellman-Ford 算法和 Floyd 算法都与边和节点的数量相关。

Dijkstra 算法是一种贪心算法,可以求解从源节点到其余节点的最短路径。其基本思想是:通过选择距离源节点最近的未访问节点来扩展路径。更具体的说,我们可以先将源节点到其余节点的距离都初始化为无穷大,然后将源节点到源节点的距离设置为 0。接下来,每次从距离源节点最近的未访问节点开始搜索,如果找到了一个更短的路径,就更新距离源节点的距离,直到所有的节点都被访问为止。

具体实现时,我们可以使用一个优先队列(或者叫堆),按照距离源节点的距离从小到大的顺序来获取下一个访问的节点。这样,就能保证每次取出的节点都是当前距离源节点最短的。

在我们找到从门 1 到门 n 的最短路径后,就可以输出结果了。

代码实现

我们可以定义一个 Graph 类,其中包含顶点数目 n、邻接矩阵(或邻接表)adj 和 dist 数组:

import heapq

class Graph:
    def __init__(self, n):
        self.n = n
        self.adj = [[] for _ in range(n)]
        self.dist = [float("inf") for _ in range(n)]
        self.visited = [False for _ in range(n)]

接下来,我们可以定义 Graph 类的两个方法:add_edge 和 dijkstra。其中,add_edge 用于向邻接表中添加边,而 dijkstra 则用于求解从门 1 到门 n 的最短路径。

    def add_edge(self, i, j, w):
        self.adj[i].append((j, w))
        self.adj[j].append((i, w))
    
    def dijkstra(self):
        heap = [(0, 0)]  # (distance, node)
        self.dist[0] = 0

        while heap:
            d, u = heapq.heappop(heap)
            if self.visited[u]:
                continue
            self.visited[u] = True
            for v, w in self.adj[u]:
                if not self.visited[v] and self.dist[v] > self.dist[u] + w:
                    self.dist[v] = self.dist[u] + w
                    heapq.heappush(heap, (self.dist[v], v))

        return self.dist[-1]

其中,self.adj[i] 表示顶点 i 所连接的所有边,每个元素是一个元组 (j, w),表示顶点 i 和顶点 j 之间有一条权值为 w 的边。

在 dijkstra 函数中,首先将节点 0 的距离初始化为 0,并将其加入堆中。堆中元素为 (d, u),其中 d 是距离源节点的距离,u 是节点编号。然后,我们开始执行循环,每次从堆中取出距离源节点最近的节点 u,将其标记为已访问,并遍历其所有的邻居 v。如果未访问的邻居 v 到源节点的距离比当前路径更短,就更新距离,并将其加入堆中。最后,返回 dist 数组的最后一个元素,即从门 1 到门 n 的最短时间。

完整代码
import heapq

class Graph:
    def __init__(self, n):
        self.n = n
        self.adj = [[] for _ in range(n)]
        self.dist = [float("inf") for _ in range(n)]
        self.visited = [False for _ in range(n)]

    def add_edge(self, i, j, w):
        self.adj[i].append((j, w))
        self.adj[j].append((i, w))
    
    def dijkstra(self):
        heap = [(0, 0)]  # (distance, node)
        self.dist[0] = 0

        while heap:
            d, u = heapq.heappop(heap)
            if self.visited[u]:
                continue
            self.visited[u] = True
            for v, w in self.adj[u]:
                if not self.visited[v] and self.dist[v] > self.dist[u] + w:
                    self.dist[v] = self.dist[u] + w
                    heapq.heappush(heap, (self.dist[v], v))

        return self.dist[-1]


if __name__ == "__main__":
    n, m = map(int, input().split())
    g = Graph(n)
    for i in range(m):
        x, y, w = map(int, input().split())
        g.add_edge(x-1, y-1, w)

    print(g.dijkstra())
总结

本题涉及到了一个经典问题——最短路径问题。Dijkstra 算法是最短路径问题的一种解决方法,可以非常高效地求解从源节点到其余节点的最短路径。在实现过程中需要注意,dijkstra 函数结束后 dist 数组中的内容即为从 1 到 n 的最短路径。