搜索是一个万能方法
暴力搜索
蛮力的基本方法——扫描
关键——依次处理所有元素
基本的扫描技术——遍历
(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),不可能更好了。