蓝桥杯Python赛道备赛——Day9:搜索算法(基础)

   本博客就蓝桥杯中所涉及的搜索算法基础问题进行讲解,包括:深度优先搜索(DFS)、广度优先搜索(BFS)和剪枝。每一种搜索算法都在给出定义的同时,也给出了示例代码,以供低年级师弟师妹们学习和练习。

   前序知识:
(1)Python基础语法
(2)动态规划(基础)


一、深度优先搜索(DFS)

  1. 定义:DFS 是一种"不撞南墙不回头"的算法,沿着路径尽可能深入探索,直到无法继续再回溯。

  2. 原理与步骤:
    (1)使用栈结构(递归本质也是栈);
    (2)从起点出发选择一个方向深入;
    (3)遇到死路时回退到最近的分叉点;
    (4)重复直到找到解或遍历所有可能。

  3. 适用场景:
    (1)路径存在性判断;
    (2)全排列/组合问题;
    (3)连通性问题。

  4. 代码示例(迷宫路径搜索):

def dfs(maze, start, end):
    """
    :param maze: 二维数组表示的迷宫,0可走,1不可走
    :param start: 起点坐标 (x,y)
    :param end: 终点坐标 (x,y)
    :return: 是否存在路径
    """
    directions = [(-1,0), (1,0), (0,-1), (0,1)]  # 上下左右四个方向
    visited = set()  # 记录已访问的位置
    
    def _dfs(x, y):
        # 递归终止条件:到达终点
        if (x, y) == end:
            return True
        
        # 标记当前位置已访问
        visited.add((x, y))
        
        # 尝试四个方向
        for dx, dy in directions:
            nx, ny = x + dx, y + dy
            # 检查新位置是否合法
            if 0 <= nx < len(maze) and 0 <= ny < len(maze[0]) \
               and maze[nx][ny] == 0 \
               and (nx, ny) not in visited:
                if _dfs(nx, ny):  # 递归探索
                    return True
        return False  # 四个方向都不通
    
    return _dfs(start[0], start[1])

# 示例迷宫
maze = [
    [0,1,0,0],
    [0,0,0,1],
    [1,1,0,0],
    [0,0,0,0]
]
print(dfs(maze, (0,0), (3,3)))  # 输出:True

二、广度优先搜索(BFS)

  1. 定义:BFS 是"地毯式搜索",逐层向外扩展,保证第一次到达终点时路径最短。

  2. 原理与步骤:
    (1)使用队列结构;
    (2)将起点放入队列;
    (3)取出队首元素,检查相邻节点;
    (4)将未访问的相邻节点入队;
    (5)重复直到队列为空或找到目标。

  3. 适用场景:
    (1)最短路径问题;
    (2)层次遍历;
    (3)扩散类问题。

  4. 代码示例(迷宫最短路径):

from collections import deque

def bfs(maze, start, end):
    """
    :return: 最短步数,不可达返回-1
    """
    directions = [(-1,0), (1,0), (0,-1), (0,1)]
    queue = deque()
    visited = set()
    
    queue.append((start[0], start[1], 0))  # (x, y, 步数)
    visited.add((start[0], start[1]))
    
    while queue:
        x, y, steps = queue.popleft()
        
        # 到达终点
        if (x, y) == end:
            return steps
            
        # 探索四个方向
        for dx, dy in directions:
            nx = x + dx
            ny = y + dy
            # 检查新位置合法性
            if 0 <= nx < len(maze) and 0 <= ny < len(maze[0]) \
               and maze[nx][ny] == 0 \
               and (nx, ny) not in visited:
                visited.add((nx, ny))
                queue.append((nx, ny, steps + 1))
    
    return -1  # 不可达

print(bfs(maze, (0,0), (3,3)))  # 输出:6

三、剪枝优化

  1. 定义:通过提前判断舍弃不可能的解,减少搜索空间的优化技巧。

  2. 常用的简直方法:
    (1)可行性剪枝:当前路径明显不可行时终止;
    (2)最优性剪枝:当前路径已不如已知最优解时终止;
    (3)重复状态剪枝:避免重复处理相同状态。

  3. 代码示例(全排列的剪枝优化):

def permutation(nums):
    res = []
    used = [False] * len(nums)  # 记录元素使用情况
    
    def backtrack(path):
        # 终止条件:路径长度等于数组长度
        if len(path) == len(nums):
            res.append(path.copy())
            return
        
        for i in range(len(nums)):
            if used[i]:  # 剪枝:已经使用过的元素跳过
                continue
                
            # 做选择
            used[i] = True
            path.append(nums[i])
            
            # 递归探索
            backtrack(path)
            
            # 撤销选择
            used[i] = False
            path.pop()
    
    backtrack([])
    return res

print(permutation([1,2,3]))  
# 输出:[[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

四、综合练习题

  1. 问题描述——尝试用DFS+BFS+剪枝解决以下问题:
"""
给定一个8x8棋盘,求马从起点到终点的最短步数
(马走日字,可能有障碍物)
"""
# 提示:使用BFS记录步数,用三维数组记录不同状态
  1. 关键点说明:
    (1)方向定义:通过8个偏移量表示马的日字型移动;
    (2)访问标记:使用二维数组避免重复访问,保证时间复杂度为O(n²);
    (3)队列结构:存储坐标和步数,保证层序扩展(BFS核心)
    (4)剪枝条件:
          ①越界检查(0 <= nx < 8)
          ②障碍物检查(chessboard[nx][ny] == 0)
          ③访问状态检查(not visited[nx][ny])

  2. 示例代码:

from collections import deque

def min_knight_steps(chessboard, start, end):
    """
    计算马从起点到终点的最短步数(BFS实现)
    
    参数:
    chessboard -- 8x8二维数组,0表示空地,1表示障碍物
    start -- 起始坐标元组 (x, y)
    end -- 终点坐标元组 (x, y)
    
    返回:
    整数:最短步数(不可达返回-1)
    """
    # 马可走的八个方向(日字型移动)
    directions = [
        (-2, -1), (-2, 1),  # 上移两格,左/右移一格
        (-1, -2), (-1, 2),  # 左移两格,上/下移一格
        (1, -2), (1, 2),    # 右移两格,上/下移一格
        (2, -1), (2, 1)     # 下移两格,左/右移一格
    ]

    # ---------- 前置条件检查 ----------
    # 检查起点或终点是否有障碍物
    if chessboard[start[0]][start[1]] == 1 or chessboard[end[0]][end[1]] == 1:
        return -1
    
    # 起点和终点重合的情况
    if start == end:
        return 0

    # ---------- BFS初始化 ----------
    # 创建访问标记数组(记录已访问的位置)
    visited = [[False for _ in range(8)] for _ in range(8)]
    
    # 创建队列,元素格式:(x坐标, y坐标, 已走步数)
    queue = deque()
    queue.append((start[0], start[1], 0))
    visited[start[0]][start[1]] = True  # 标记起点已访问

    # ---------- BFS主循环 ----------
    while queue:
        x, y, steps = queue.popleft()  # 取出队首元素
        
        # 找到终点,立即返回当前步数(BFS保证此时是最短路径)
        if (x, y) == end:
            return steps

        # 遍历所有可能的移动方向
        for dx, dy in directions:
            nx = x + dx  # 计算新坐标x
            ny = y + dy  # 计算新坐标y
            
            # 检查新坐标是否在棋盘范围内
            if 0 <= nx < 8 and 0 <= ny < 8:
                # 同时满足以下条件才入队:
                # 1. 目标位置是空地
                # 2. 未被访问过
                if chessboard[nx][ny] == 0 and not visited[nx][ny]:
                    visited[nx][ny] = True  # 标记为已访问
                    queue.append((nx, ny, steps + 1))  # 步数+1入队

    # 队列已空但未找到路径
    return -1

# --------------------------
# 示例测试
# --------------------------
# 创建示例棋盘(全空)
chessboard = [[0 for _ in range(8)] for _ in range(8)]

# 测试案例1:无障碍最短路径
print(min_knight_steps(chessboard, (0,0), (1,2)))  # 输出:1

# 测试案例2:设置障碍物
chessboard[1][2] = 1  # 堵住第一步的最短路径
chessboard[2][1] = 1  # 堵住另一个可能方向
print(min_knight_steps(chessboard, (0,0), (1,2)))  # 输出:-1

# 测试案例3:跨棋盘移动
chessboard[1][2] = 0  # 恢复第一步的最短路径
chessboard[2][1] = 0 
print(min_knight_steps(chessboard, (0,0), (7,7)))  # 输出:6(无障碍时)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值