蓝桥杯——第五章:搜索

搜索是一个万能方法

暴力搜索

蛮力的基本方法——扫描
关键——依次处理所有元素
基本的扫描技术——遍历
(1)集合的遍历
(2)线性表的遍历
(3)树的遍历
(4)图的遍历

【BFS】Breadth-First Search,宽度优先搜索,或称为广度优先搜索

【DFS】Depth-First Search,深度优先搜索

DFS的代码比BFS更简短。
DFS的主要操作:
时间戳
DFS序
树深度
子树节点数
中序输出
先序输出
后序输出

DFS基础:递归和记忆化搜索

递归:斐波那契数列
fib=[0]*25
fib[1]=1
fib[2]=1
for i in range(3,21):
fib[i]=fib[i-1]+fib[i-2]
print(fib[20])

下面是递归实现:

def fib(n):
    global cnt
    cnt += 1               #统计执行了多少次递归
    if n==1 or n==2:       #到达终止条件,即最小问题
       return 1          
    return fib(n-1)+fib(n-2) #递归调用自己2次,复杂度O(2n)
cnt = 0
print(fib(20))   
print(cnt)                   #递归了cnt=13529次

函数fib(20)计算斐波那契数。

递归过程:
递归前进:fib(20) = fib(19) + fib(18)
递归前进:fib(19) = fib(18) + fib(17)
递归前进:fib(18) = fib(17) + fib(16)

递归前进:fib(3) = fib(2) + fib(1)

到达终止条件:fib(2) = 1,fib(1) = 1

对递归进行改进:记忆化

递归的关键问题:递归深度不能太大。
Python默认递归深度1000,如果递归深度太大,提示“maximum recursion depth exceeded in comparison”。

用sys.setrecursionlimit()设置递归深度。
常常有深度大于1000的递归题目

import sys
sys.setrecursionlimit(30000) #设置递归深度
def fib(n):
    global cnt
    cnt += 1   
    if n==1 or n==2:   
       data[n]=1; return data[n]
    if data[n] != 0:   
       return data[n]
    data[n] = fib(n-1)+fib(n-2)
    return data[n]
data=[0]*3005 
cnt = 0
print(fib(3000))       #约等于4*10626
print(cnt)           #递归次数,cnt=5997

上面的记忆化操作 在有时候不需要递归返回, 把曾经记忆下来的东西直接返回即可,不用再深入和回溯。

下面是模板
在DFS框架中,最让初学者费解的是第10行和第12行。
第10行的used[i] = 1,称为“保存现场”,或“占有现场”。
第12行的used[i] = 0,称为“恢复现场”,或“释放现场”。

ans                   #答案,用全局变量表示
def dfs(层数,其他参数):
    if (出局判断)    #到达最底层,或者满足条件退出 
        更新答案     #答案一般用全局变量表示
        return       #返回到上一层
    
    剪枝             #在进一步DFS之前剪枝
    for (枚举下一层可能的情况):  #对每一个情况继续DFS 
        if (used[i] == 0):       #如果状态i没有用过,就可以进入下一层
            used[i] = 1          #标记状态i,表示已经用过,在更底层不能再使用
            dfs(层数+1,其他参数)    #下一层 
            used[i] = 0          #恢复状态,回溯时,不影响上一层对这个状态的使用
            
    return                       #返回到上一层

5.1.3 DFS搜索和输出所有路径

【题目描述】 给出一张图,输出从起点到终点的所有路径。
【输入描述】 第一行是整数n,m,分别是行数和列数。后面n行,每行m个符号。’@’是起点,’*’
是终点,’•’能走,’#’是墙壁不能走。在每一步,都按左-上-右-下(顺时针方向)的顺序搜索。在样例中,左上角坐标(0, 0),起点坐标(1, 1),终点坐标(0, 2)。1<n, m <7。
【输出描述】输出所有的路径。坐标(i, j)用ij表示,例如坐标(0, 2)表示为02。从左到右是i,从上到下是j。
【输入样例】
5 3
.#.
#@.
*…

#.#
在这里插入图片描述

def dfs(x, y):
    global num
    for i in range(0, 4):
        dir = [(-1, 0), (0, -1),(1, 0),  (0, 1)]         #左、上、右、下 
        nx,ny = x + dir[i][0]  ,y + dir[i][1]            #新坐标
        if nx<0 or nx>=hx or ny<0 or ny>wy:  continue    #不在地图内
        if mp[nx][ny]=='*':
            num+=1  # 标记第num 条 路径 
            print("%d: %s->%d%d"%(num,p[x][y],nx,ny))    #打印路径
            continue                                     #不退出,继续找下一个路径
        if mp[nx][ny]=='.':
            mp[nx][ny]='#'                 #保存现场。这个点在这次更深的dfs中不能再用
            p[nx][ny]=p[x][y]+'->'+str(nx)+str(ny)       #记录路径
            dfs(nx,ny)
            mp[nx][ny]='.'                 #恢复现场。回溯之后,这个点可以再次用
num = 0
wy,hx = map(int, input().split())          #Wy行,Hx列。用num统计路径数量 
a =['']*10                   
for i in range(wy):  a[i]=list(input())    #读迷宫
mp = [[' '] * (10) for i in range(10)]     #二维矩阵mp[][]表示迷宫
for x in range(hx):
    for y in range(wy):
        mp[x][y] = a[y][x]                 
        if mp[x][y]=='@': sx=x; sy=y        #起点
        if mp[x][y]=='*': tx=x; ty=y        #终点
print("from %d%d to %d%d"%(sx,sy,tx,ty))
p = [[' '] * (10) for i in range(10)]       #记录从起点到点path[i][j]的路径
p[sx][sy] = str(sx)+str(sy)
dfs(sx,sy)                                  #搜索并输出所有的路径


用BFS可以极快地搜到最短路。DFS适合用来处理暴力搜所有情况的题目

DFS 用来求全排列

def dfs(s,t):
    if s == t:          #递归结束,产生一个全排列
        print(a[0:n]) 
    else: 
        for i in range(s,t+1):
           a[s],a[i] = a[i],a[s]   #交换
           dfs(s+1,t)              #缩小范围
           a[i],a[s] = a[s],a[i]   #恢复
a = [1,2,3,4,5,6,7,8,9] 
n = 3
dfs(0,n-1)                #求前n个数的全排列

DFS例题

迷宫 2017年第八届蓝桥杯省赛,lanqiaoOJ题号641

【问题描述】给出一个迷宫,问迷宫内的人有多少能走出来。迷宫如下:其中L表示向左走,R表示向右走,U表示向上走,D表示向下走。

UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR

UDDLUULRUL
UURLLLRRRU
RRUURLDLRD
RUDDDDUUUU
URUDLLRRUU
DURLRLDLRL
ULLURLLRDU
RDLULLRDDD
UUDDUDUDLL
ULRDLUURRR

寒假作业 2016年第七届省赛,lanqiaoOJ题号1388

在这里插入图片描述
全排列

#初始化 二维数组
#vis=[[0]*10 for _ in range(10)]   # 初始化 vis[][]
def dfs(num):
    global ans
    if num == 13:
        if b[10]== b[11]*b[12]:
            ans+=1
        return
    if num ==4 and b[1]+b[2]!=b[3]: return
    if num == 7 and b[4]-b[5]!=b[6]: return
    if num == 10 and b[7]*b[8]!=b[9]: return
    for i in range(1,14):
        if not vis[i]:
            b[num]=i
            vis[i]=1
            dfs(num+1)
            vis[i]=0
ans=0
b=[0]*15
vis=[0]*15
dfs(1)
print(ans)

连通性判断:图论的一个简单问题,给定一张图,图由点和连接点的边组成,要求找到图中互相连通的部分。

DFS例题

全球变暖 2018年第九届蓝桥杯省赛,lanqiaoOJ题号178

【题目描述】你有一张某海域 NxN 像素的照片,".“表示海洋、”#"表示陆地,如下所示:
在这里插入图片描述

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。例如上图中的海域未来会变成如下样子:
在这里插入图片描述
请你计算:照片中有多少岛屿会被完全淹没。照片保证第 1 行、第 1 列、第N行、第N列的像素都是海洋。
【输入描述】第一行包含一个整数N(1≤N≤1000)。以下N行N列代表一张海域照片。
【输出描述】输出一个整数表示答案。
连通性问题,计算步骤:
遍历一个连通块(找到这个连通块中所有的’#‘,标记已经搜过,不用再搜);
再遍历下一个连通块……;
遍历完所有连通块,统计有多少个连通块。

什么岛屿不会被完全淹没?
若岛中有个陆地(称为高地),它周围都是陆地,那么这个岛不会被完全淹没。
用DFS搜出有多少个岛(连通块),检查这个岛有没有高地,统计那些没有高地的岛(连通块)的数量,就是答案。
计算复杂度:每个像素点只用搜一次且必须至少搜一次,共N2个点,DFS的复杂度是O(N2),不可能更好了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值