注意全局变量只能在函数中定义,且只在该函数下生效,故每个函数中若要使用都必须要重新定义。
来看一个非常简单的问题:
给定n个整数,要求选出K个数,使得选出来的K个数的和为sum。
我们依然借助dfs来解决这个问题。对于每一个数,枚举选或者不选两种情况,我们可以用dfs思想来完成这样的枚举过程。我们在搜索的过程中,用S来记录当前选择的数值总和,k来记录选择的数的个数, deep表示当前正在枚举第几个数是否选择。
在第一层dfs的时候,我们可以枚举是否选第一个数,如果选第一个数则让S加上第一个数且k加一,dfs进入到下一层;否则dfs直接进入到下一层。当然,这里我们还需要借助全局变量、参数或修改数组中元素的值等方式来标识出当前的层数,为了减少篇幅,在下文中就直接忽略掉了。
在第二层,对第二个数做同样的处理, dfs的过程中记录已经选取的数的个数,如果已经选取了k个数,判断S值是否等于sum。对于每一层,我们都有两个选择--选和不选。不同的选择,都会使得搜索进入完全不同的分支继续搜索。
下图是这个搜索过程对应的 搜索树,搜索树上的每一个结点都是一个状态,一个状态包含两个值S和k,也就是一个状态对应当前的数值总和,以及选的数的个数。
这一节我们对搜索树进行深入的理解,如果对搜索树和状态有很好的理解,对后面的广度优先搜索和动态规划的学习都有很大的帮助。
前面说过, dfs看起来是运行在图上的搜索算法,而前一节给大家展示的dfs过程,我们没有看到图的存在,这就是抽象形式的dfs的特点。
我们可以根据搜索状态构建一张抽象的图,图上的一个顶点就是一个状态,而图上的边就是状态之间的转移关系(进一步搜索或回溯) 。虽然dfs是在这张抽象的图上进行的,但我们不必把这张图真正地建立出来。
目录
题型一
部分和问题
题目来源
挑战程序设计竞赛(第二版)
描述
给定整数 a1, a2, ..., an,判断是否可以从中选出若干数,使它们的和恰好为 k。
样例1
输入:
n = 4
a = {1, 2, 4, 7};
k = 13
输出:
Yes
样例2
输入:
n = 4
a = {1, 2, 4, 7};
k = 15
输出:
No
#给定整数a1、a2、、an,判断是否可以从中选出若干数,使它们的和恰好为k
n=int(input())
a=list(map(int,input().split()))
k=int(input())
# 已经从前i项得到了和sum,然后对于i项之后的进行分支
def dfs(i,sum):
if sum>k:#剪枝
return False
#如果前n项都计算过了,则返回sum是否与k相等
if i==n:
return sum==k
#不加a[i]的情况
if dfs(i+1,sum):
return True
#加上a[i]的情况
if dfs(i+1,sum+a[i]):
return True
#无论是否加上a[i]都不能凑成k就返回false
return False
if dfs(0,0):
print('YES')
else:
print('NO')
k个数的和
题目大意
从n个数中,选取k个数,使得和为sum,求选取的方案数(注意:123 和213是同一种选取方法)
#给定整数a1、a2、、an,判断是否可以从中选出m个数,使它们的和恰好为k
n,m,k=map(int,input().split())
a=list(map(int,input().split()))
def dfs(x,cnt,sums):
global ans
if cnt==m and sums==k:
ans+=1
return
if x==n:
return
dfs(x+1,cnt+1,sums+a[x])
dfs(x+1,cnt,sums)
ans=0
dfs(0,0,0)
print(ans)
优化
# 给定整数a1、a2、、an,判断是否可以从中选出m个数,使它们的和恰好为k
n, m, k = map(int, input().split())
a = list(map(int, input().split()))
vis = [0] * n
def dfs(x, cnt, sums):
global ans
if cnt > m: # 剪枝
return
if cnt == m and sums == k: # 剪枝
ans += 1
return
for i in range(x, n):
if vis[i] != 1:
vis[i] = 1
dfs(i+1, cnt + 1, sums + a[i])
vis[i] = 0
ans = 0
dfs(0, 0, 0)
print(ans)
顺序也算的情况
这是因为这样搜索,最后方案实际上乘上了一个k!.比如一个组合2,3,5、这样的方法,会把2, 3, 5。 2, 5,3。 3, 2,5。3, 5, 2。 5,3, 2。 5,2,3。当成不同的方法,而之前的搜索方法只会搜到2, 3,5。
#给定整数a1、a2、、an,判断是否可以从中选出m个数,使它们的和恰好为k
n,m,k=map(int,input().split())
a=list(map(int,input().split()))
vis=[0]*n
def dfs(x,cnt,sums):
global ans
if cnt>m:#剪枝
return
if cnt==m and sums==k:#剪枝
ans+=1
return
for i in range(n):
if vis[i]!=1:
vis[i] = 1
dfs(i,cnt+1,sums+a[i])
vis[i] = 0
ans=0
dfs(0,0,0)
print(ans)
【数字全排序】递归实现枚举
n=int(input())
vis=[0]*n
a=[]
def dfs(x,fx,d,list1):
global cnt
if d>n:
return
if x==fx and d!=n:
return
if d==n and x==fx:
cnt+=1
a.append(list1)
return
for i in range(n):
if vis[i]!=1 and x!=i:
vis[x]=1
dfs(i,fx,d+1,list1+[i+1])
vis[x]=0
return
sum=0
if n==1:#特殊情况不能忘
print(1)
else:
for i in range(n):
for j in range(n):
if i!=j:
cnt=0
dfs(i,j,1,[i+1])
sum+=cnt
a.sort()
#print(sum)
#print(a)
for i in range(sum):
print(*a[i])
简化版
n=int(input())
vis=[0]*n
a=[]
def dfs(d,list1):
if d>=n:
print(*list1)
return
for i in range(n):
if vis[i]!=1:
vis[i]=1 #dfs未传入初值,初值在dfs内部第一层for循环输出,故取当前i为标记已走过
dfs(d+1,list1+[i+1])
vis[i]=0
return
dfs(0,[])
n, m = map(int, input().split())
vis = [0] * n
def dfs(d, list1,x):
if d >= m:
print(*list1)
return
for i in range(x,n):
if vis[i]!=1:
vis[i] = 1
dfs(d + 1, list1 + [i + 1],i)
vis[i] = 0
dfs(0,[],0)
题型二
【01背包】
n,m=map(int,input().split())
w,v=[],[]
for i in range(n):
wi,vi=map(int,input().split())
w.append(wi)
v.append(vi)
#print(w,v)
def dfs(i,sumw,sumv):
global maxn
if i==n:
maxn=max(sumv,maxn)
return
#情况一,不选第i件商品
dfs(i+1,sumw,sumv)
#情况二,选第i件商品
if sumw+w[i]<=m:
dfs(i+1,sumw+w[i],sumv+v[i])
maxn=0
dfs(0,0,0)
print(maxn)
在建列表上面推荐下面直接第二种方式,速度更快。
n,m=map(int,input().split()) #n=3件物品 #m=5kg
w=[0]*(n+1) #物品重量
v=[0]*(n+1) #物品价值
for i in range(1,n+1):
w[i],v[i]=map(int,input().split())
n,m=map(int,input().split())
w,v=[],[]
for i in range(n):
wi,vi=map(int,input().split())
w.append(wi)
v.append(vi)
题型三
等边三角形
注意该题的性质就是进行三次DFS搜索找到3个边,故从头开始,并不是一个DFS找到所有边
蒜头君手上有一些小木棍,它们长短不一,蒜头君想用这些木棍拼出一个等边三角形,并且每根木棍都要用到。 例如,蒜头君手上有长度为 1,2,3,3 的4根木棍,他可以让长度为1,2 的木棍组成一条边,另外 2 跟分别组成 2 条边,拼成一个边长为 3 的等边三角形。蒜头君希望你提前告诉他能不能拼出来,免得白费功夫。
输入格式
首先输入一个整数 n(3≤n≤20),表示木棍数量,接下来输入 n 根木棍的长度 pi (1≤ pi ≤10000)。
输出格式
如果蒜头君能拼出等边三角形,输出"yes",否则输出"no"。
样例输入1
5
1 2 3 4 5
输出1
yes
样例输入2
4
1 1 1 1
输出2
no
n=int(input())
a=list(map(int,input().split()))
sums=sum(a)
vis=[0]*n
f=False
def dfs(p, s, x):
global f
if f:
return
if p == 2: # 剪枝,因为能被3整除,前两个可以确认,则第三个必定可以
f = True
return
if s > sums // 3:
return
if s == sums // 3:
dfs(p + 1, 0, 0)#x清零,因为该题的性质就是进行三次DFS搜索找到3个边,故从头开始,并不是一个DFS找到所有边
for i in range(x, n):
if vis[i] != 1:
vis[i] = 1
dfs(p, s + a[i], i + 1)
vis[i] = 0
if sums%3!=0:
print('no')
else:
dfs(0, 0,0)
if f:
print('yes')
else:
print('no')
正方形
蒜头君手上有一些小木棍,它们长短不一,蒜头君想用这些木棍拼出一个正方形,并且每根木棍都要用到。 例如,蒜头君手上有长度为 1,2,3,3, 3 的 5 根木棍,他可以让长度为1,22 的木棍组成一条边,另外三根分别组成 3 条边,拼成一个边长为 3 的正方形。蒜头君希望你提前告诉他能不能拼出来,免得白费功夫。
输入格式
首先输入一个整数 n(4≤n≤20),表示木棍数量,接下来输入 n 根木棍的长度 pi (1≤pi≤10000)。
输出格式
如果蒜头君能拼出正方形,输出"Yes",否则输出"No"。
样例输入1
4
1 1 1 1
样例输出1
Yes
样例输入2
5
10 20 30 40 50
样例输出2
No
n=int(input())
a=list(map(int,input().split()))
sums=sum(a)
vis=[0]*n
f=False
def dfs(p, s, x):
global f
if f:#剪枝,已找到一种符合组合,退出所有递归
return
if p == 3: # 剪枝,因为能被4整除,前三个可以确认,则第四个个必定可以
f = True
return
if s > sums // 4:#剪枝
return
if s == sums // 4:
dfs(p + 1, 0, 0)
for i in range(x, n):#重复性剪枝
if vis[i] != 1:
vis[i] = 1
dfs(p, s + a[i], i + 1)
vis[i] = 0
if sums%4!=0:
print('no')
else:
dfs(0, 0,0)
if f:
print('yes')
else:
print('no')
小猫爬山
def dfs(u,k):
global minnum
if k>minnum:#剪枝,当前大循环小车数大于之前完整的大循环后得到的最小小车数时结束循环
return
if u==n:#满足全部小猫都坐上小车的要求
minnum=k
return
for i in range(k):#目前创建了k-1的小车,遍历每个小车
if sum[i]+c[u]<=w:#看是否有小车还能继续坐人
sum[i]+=c[u]
dfs(u+1,k)
sum[i]-=c[u]# 表示dfs调用结束了,换句话说就是小猫已经全部坐上小车
# 需要按照顺序将小猫收回,重新放,也就是前面所说的
sum[k]=c[u]#不能就创建第k个小车
dfs(u+1,k+1)
return#这里表示这一级别的dfs函数已经结束了,返回上一级 dfs函数
n,w=map(int,input().split())
c=[int(input()) for i in range(n)]
sum=[0]*n
minnum=float('inf')
dfs(0,0)
print(minnum)
题型四
方程的解数
问题描述
输出格式
输出一行,输出一个整数,表示方程的整数解的个数。
样例输入
3 100 1 2 -1 2 1 2
样例输出
104
n=int(input())
m=int(input())
k=[]
p=[]
for i in range(n):
k1,p1=map(int,input().split())
k.append(k1)
p.append(p1)
ans=0
def dfs(x,sums):
global ans
if x==n:
if sums==0:
ans+=1
return
for i in range(1,m+1):#有序也算
dfs(x+1,sums+k[x]*(i**p[x]))
dfs(0,0)
print(ans)
题型五
数独
蒜头君今天突然开始还念童年了,想回忆回忆童年。他记得自己小时候,有一个很火的游戏叫做数独。便开始来了一局紧张而又刺激的高阶数独。蒜头君做完发现没有正解,不知道对不对? 不知道聪明的你能否给出一个标准答案?
标准数独是由一个给与了提示数字的 9*9 网格组成,我们只需将其空格填上数字,使得每一行,每一列以及每一个3*3 宫都没有重复的数字出现。
输入格式
一个9×9的数独,数字之间用空格隔开。*表示需要填写的数字。
输出格式
输出一个9×9的数独,把出入中的*替换成需要填写的数字即可。
本题答案不唯一,符合要求的答案均正确
样例输入
* 2 6 * * * * * * * * * 5 * 2 * * 4 * * * 1 * * * * 7 * 3 * * 2 * 1 8 * * * * 3 * 9 * * * * 5 4 * 1 * * 7 * 5 * * * * 1 * * * 6 * * 9 * 7 * * * * * * * * * 7 5 *
样例输出
1 2 6 7 3 4 5 9 8 3 7 8 5 9 2 6 1 4 4 9 5 1 6 8 2 3 7 7 3 9 4 2 5 1 8 6 8 6 1 3 7 9 4 2 5 2 5 4 8 1 6 3 7 9 5 4 7 2 8 1 9 6 3 6 1 3 9 5 7 8 4 2 9 8 2 6 4 3 7 5 1
maze=[]
for i in range(9):
list1=list(input().split())
maze.append(list1)
#print(maze)
vx=[[0]*9 for i in range(9)] #纵
vy=[[0]*9 for i in range(9)] #横
vv=[[0]*9 for i in range(9)] #9格框
for i in range(9):
for j in range(9):
if maze[i][j] != '*':
vx[i][int(maze[i][j])-1]=1
vy[j][int(maze[i][j])-1] = 1
vv[int(i/3)*3+int(j/3)][int(maze[i][j]) - 1]=1
# print(vx)
# print(vy)
# print(vv)
f=False
def dfs(x,y):
global f
if f:#只要求输出一次正确结果故用f结束其他结果
return
if x==9:
f=True
for i in range(9):
print(*maze[i])
return
if y==9:
dfs(x+1,0)
return
if maze[x][y]!='*':
dfs(x,y+1)
return
for i in range(9):
if vx[x][i]!=1 and vy[y][i]!=1 and vv[int(x/3)*3+int(y/3)][i]!=1:
vx[x][i] = 1
vy[y][i] = 1
vv[int(x / 3) * 3 + int(y / 3)][i] = 1
maze[x][y]=str(i+1)
dfs(x,y+1)
vx[x][i] = 0
vy[y][i] = 0
vv[int(x / 3) * 3 + int(y / 3)][i] = 0
maze[x][y]='*'
dfs(0,0)
本体和八皇后类似,需要注意的是vx,vy,vv三个列表的建立和取值。
int(x / 3) * 3 + int(y / 3)=0表示在第0号9方格中
题型六
2n皇后问题
和n皇后类似但我更感觉是n皇后和等边三角形的结合。
问题描述
给定一个n*n的棋盘,棋盘中有一些位置不能放皇后。现在要向棋盘中放入n个黑皇后和n个白皇后,使任意的两个黑皇后都不在同一行、同一列或同一条对角线上,任意的两个白皇后都不在同一行、同一列或同一条对角线上。问总共有多少种放法?n小于等于8。
输入格式
输入的第一行为一个整数n,表示棋盘的大小。
接下来n行,每行n个0或1的整数,如果一个整数为1,表示对应的位置可以放皇后,如果一个整数为0,表示对应的位置不可以放皇后。
输出格式
输出一个整数,表示总共有多少种放法。
样例输入
4
1 1 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
2
样例输入
4
1 0 1 1
1 1 1 1
1 1 1 1
1 1 1 1
样例输出
0
n=int(input())
bx,bx1,by1=[0]*n,[0]*2*n,[0]*2*n
wx,wx1,wy1=[0]*n,[0]*2*n,[0]*2*n
vis=[[0]*n for i in range(n)]
maze=[]
for i in range(n):
list1=list(map(int,input().split()))
maze.append(list1)
ans=0
def dfs(x,y):
global ans
if y==2:
ans+=1
return
if x==n:
dfs(0,y+1)
return
if y==0:
for i in range(n):
if maze[x][i]!=0 and vis[x][i]!=1 and bx[i]!=1 and bx1[x+i]!=1 and by1[n+x-i]!=1:
vis[x][i] = 1
bx[i] = 1
bx1[x + i] = 1
by1[n + x - i] = 1
dfs(x+1,y)
vis[x][i] = 0
bx[i] = 0
bx1[x + i] = 0
by1[n + x - i] = 0
elif y==1:
for i in range(n):
if maze[x][i] != 0 and vis[x][i] != 1 and wx[i] != 1 and wx1[x + i] != 1 and wy1[n + x - i] != 1:
vis[x][i] = 1
wx[i] = 1
wx1[x + i] = 1
wy1[n + x - i] = 1
dfs(x + 1, y)
vis[x][i] = 0
wx[i] = 0
wx1[x + i] = 0
wy1[n + x - i] = 0
dfs(0,0)
print(ans)
简写一下
n=int(input())
xx=[[0]*2 for i in range(n)]
xy=[[0]*2 for i in range(2*n)]
yx=[[0]*2 for i in range(2*n)]
vis=[[0]*n for i in range(n)]
maze=[]
for i in range(n):
list1=list(map(int,input().split()))
maze.append(list1)
ans=0
def dfs(x,y):
global ans
if y==2:
ans+=1
return
if x==n:
dfs(0,y+1)
return
for i in range(n):
if maze[x][i]!=0 and vis[x][i]!=1 and xx[i][y]!=1 and xy[x+i][y]!=1 and yx[n+x-i][y]!=1:
vis[x][i] = 1
xx[i][y] = 1
xy[x + i][y] = 1
yx[n + x - i][y] = 1
dfs(x+1,y)
vis[x][i] = 0
xx[i][y] = 0
xy[x + i][y] = 0
yx[n + x - i][y] = 0
dfs(0,0)
print(ans)
题型七
引爆炸弹
在一个 n×m 的方格地图上,某些方格上放置着炸弹。手动引爆一个炸弹以后,炸弹会把炸弹所在的行和列上的所有炸弹引爆,被引爆的炸弹又能引爆其他炸弹,这样连锁下去。
现在为了引爆地图上的所有炸弹,需要手动引爆其中一些炸弹,为了把危险程度降到最低,请算出最少手动引爆多少个炸弹可以把地图上的所有炸弹引爆。
输入格式
第一行输两个整数 n,m,用空格隔开。
接下来 n 行,每行输入一个长度为 m 的字符串,表示地图信息。0表示没有炸弹,1表示炸弹。
数据约定:
对于 60% 的数据:1≤n,m≤100;
对于 100% 的数据:1≤n,m≤1000;
数据量比较大,不建议用cin输入。
输出格式
输出一个整数,表示最少需要手动引爆的炸弹数。
样例输入
5 5 00010 00010 01001 10001 01000 样例输出
2
n,m=map(int,input().split())
maze=[]
xx=[0]*n
yy=[0]*m
for i in range(n):
list1=list(map(int,input()))
maze.append(list1)
#print(maze)
def dfs(x,y):
maze[x][y]=0
if xx[x]==0:#该行的炸弹未被引爆
xx[x]=1#已引爆,剪枝
for i in range(m):
if maze[x][i]==1:
dfs(x,i)#该行炸弹的所有列也要检查
if yy[y]==0:
yy[y]=1
for i in range(n):
if maze[i][y]==1:
dfs(i,y)#该列炸弹的所有行也要检查
cnt=0
for i in range(n):
for j in range(m):
if maze[i][j]==1:
cnt+=1
dfs(i,j)
print(cnt)