📜  Puzzle-59 |抓小偷(1)

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

Puzzle-59 | 抓小偷

本题是一道典型的求解最短路的问题,假设有一个城市的地图,地图上标记了若干个位置,每个位置的编号为 $1,2,\dots,n$,其中编号为 $1$ 的位置是警局,编号为 $n$ 的位置是小偷的藏身之处。每个位置都有一些通往其他位置的小路,每条小路的长度为 $1$(见下图)。

puzzle-59-image

警察需要通过某个程序,找到一条从警局($1$号位置)出发,到达小偷的藏身之处($n$号位置)的路径,这条路径上包含的边数必须不超过 $k$ 条,求这样的路径的最短长度。

为了求解这个问题,我们可以运用广度优先搜索(BFS)算法,设 $dis_i$ 表示从 $1$ 号位置到 $i$ 号位置的最短距离,那么有以下的转移方法:

从 $1$ 号位置开始搜索,将所有与编号为 $1$ 的位置相邻的位置(即与 $1$ 号位置之间有一条边相连的位置)加入队列中,同时把这些点的 $dis$ 的值附为 $1$。

每次从队列中取出一个点 $u$,找出所有与 $u$ 相邻的没被搜索过的位置 $v$,把它的 $dis_v$ 赋为 $dis_u+1$,然后把 $v$ 加入队列中。

当队列为空时,搜索结束,此时 $dis_n$ 的值即为 $1$ 号位置到 $n$ 号位置的最短距离。

代码展示如下:

def bfs(n:int, k:int, graph:List[List[int]]) -> int:
    # 初始时所有点的距离赋为 -1,表示未被访问
    dis = [-1] * n
    # 开始位置为 1 号点,距离为 0
    q = deque([(1, 0)])
    # 记录已经访问过的点(避免重复访问)
    visited = [False] * n
    visited[0] = True  # 1 号点已经被访问过了,无需再访问

    while q:
        u, u_dis = q.popleft()
        # 如果 u 到 n 的距离已经超过了 k,显然不符合题意,直接跳过
        if u_dis > k:
            break
        # 如果 u 为 n 号点,且当前距离不大于 k,说明找到了一条路径,直接返回该路径长度
        if u == n:
            return u_dis
        # 找出与 u 相邻的未被访问的点,加入队列中
        for v in graph[u-1]:
            if not visited[v-1]:
                visited[v-1] = True
                q.append((v, u_dis+1))
    # 如果搜索结束时还没有找到合法路径,则无解,返回 -1
    return -1

以上是求解本题的核心算法,完整的 Python 代码如下:

from typing import List
from collections import deque

def bfs(n:int, k:int, graph:List[List[int]]) -> int:
    # 初始时所有点的距离赋为 -1,表示未被访问
    dis = [-1] * n
    # 开始位置为 1 号点,距离为 0
    q = deque([(1, 0)])
    # 记录已经访问过的点(避免重复访问)
    visited = [False] * n
    visited[0] = True  # 1 号点已经被访问过了,无需再访问

    while q:
        u, u_dis = q.popleft()
        # 如果 u 到 n 的距离已经超过了 k,显然不符合题意,直接跳过
        if u_dis > k:
            break
        # 如果 u 为 n 号点,且当前距离不大于 k,说明找到了一条路径,直接返回该路径长度
        if u == n:
            return u_dis
        # 找出与 u 相邻的未被访问的点,加入队列中
        for v in graph[u-1]:
            if not visited[v-1]:
                visited[v-1] = True
                q.append((v, u_dis+1))
    # 如果搜索结束时还没有找到合法路径,则无解,返回 -1
    return -1

def main():
    n, k = map(int, input().split())
    graph = [[] for _ in range(n)]
    for i in range(n):
        nodes = list(map(int, input().split()))[1:]
        graph[i].extend(nodes)

    print(bfs(n, k, graph))

if __name__ == '__main__':
    main()

以上代码中,bfs 函数是最短路算法的实现,main 函数则是读入数据并输出结果的核心逻辑。在输入数据时,我们需要把图存储为邻接表的形式,以便在 BFS 中快速地找到一个节点的相邻节点。