DFS——剪枝

文章目录

        • 概述
          • 优化搜索顺序
          • 排除等效冗余
          • 可行性剪枝
          • 最优性剪枝
        • 例题
          • 小猫爬山
          • 木棒
        • 总结

概述

优化搜索顺序

不同的搜索顺序会产生不同的搜索树形态,与可行性剪枝结合,去除非法状态,按照一定顺序可使规模大幅度减小。

例:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
示例 : 输入:candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ]
DFS——剪枝_第1张图片

可见第二种情况所访问的节点数量小于第一种,顺序的优化时为了尽快的减掉不合法的分支。也就是易于满足可行性剪枝的条件

排除等效冗余

在搜索过程中,如果我们能够判定从搜索树的当前节点上沿着某几条不同分支到达的子树是等效的,那么只需要对其中一条分支进行搜索。
一种是组合还是排列问题,如果是组合那么就上面的例题而言[1, 2]和[2, 1]是一种效果
其次就是路径选择时,前一个选择的路径和当前选择的路径效果相同,且前一个路径搜索失败,那么如果按照当前路径走的话效果相同,故可跳过。例如上题,搜索[2, 4, 4, 5]目标时7,如果搜索到第一个4,发现失败跳过后,搜索第二个失败状态也是一样的。如果成功的话对于组合数来说也是同等效果。

可行性剪枝

在搜索过程中,及时对当前状态进行检查,如果发现分支已经无法到达递归边界(非法),就执行回溯。
常见的剪枝是“上下界剪枝”

最优性剪枝

在最优化问题的搜索过程中,如果当前花费的代价已经超过了当前已经搜到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案。此时可以停止搜索,执行回溯。

例题

小猫爬山

翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。

经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。

翰翰和达达只好花钱让它们坐索道下山。

索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。

当然,每辆缆车上的小猫的重量之和不能超过 W。

每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?

输入格式
第 1 行:包含两个用空格隔开的整数,N 和 W。

第 2…N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。

输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围
1≤N≤18,
1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2

  1. 搜索顺序
    搜索每一个小猫,考虑小猫放到已有的缆车中还是新开一个缆车。
    终点:搜索完最后一个小猫,所以需要一个参数cur表示当前搜索的是第几只猫
    路径选择:新开一个缆车或是在已有缆车中选择,所以需要一个参数u表示已经开好的缆车
    所以参数def dfs(u, cur)

  2. 代码

N = 20

cab = [0] * N
cats = []
ans = N

def dfs(u, cur) :
	global ans
	if ans <= u : #最优性剪枝
		return
	if cur == n : 
		ans = u
		return
	
	cab[u] = cats[cur] #开一个新缆车
	dfs(u + 1, cur + 1)
	cab[u] = 0
	for i in range(u) :
		if cab[i] + cats[cur] <= m :#可行性剪枝
			cab[i] += cats[cur]
			dfs(u, cur + 1)
			cab[i] -= cats[cur]
			
n, m = map(int, input().split())
for i in range(n) : 
	t = int(input())
	cats.append(t)

cats.sort(reverse = True) #优化搜索顺序

dfs(0, 0)
print(ans)
  1. 剪枝的操作
    if cab[i] + cats[cur] <= m :#可行性剪枝,对应于可行性剪枝,缆车的承重有一定限制,那么从大到小搜索质量可以尽快的去处不合法的分支
木棒

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

输入格式
输入包含多组数据,每组数据包括两行。

第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。

输出格式
为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。

数据范围
数据保证每一节木棍的长度均不大于 50。

输入样例:
9
5 2 1 5 2 1 5 2 1
4
1 2 3 4
0
输出样例:
6
5

  1. 搜索顺序
    枚举木棒长度,因为,木棍是由所有长度相等的木棒砍断而来,所以只有当木棒长度可以整除木棍长度之和,才可行。
    枚举能被木棍长度之和整除的木棒长度。假如此时枚举的长度为length,搜索木棍的组合,考虑木棍的组合的和是否等于length。如果相等,则除去当前搜索组合中的木棍,并记录木棒个数,从剩余木棍中搜索组合和为length的木棍。如果不相等则失败。搜索直到木棒的个数*length等于木棍长度之和为止
    终点:木棒的个数u * length等于木棍长度之和
    路径选择:组合数,需要有搜索的木棍的位置start
    可行性 : 当前组合之和cur + 枚举到木棍长度

  2. 代码

N = 70

def dfs(u, cur, start) :
	if length * u == cal : return True
	if cur == length : return dfs(u + 1, 0, 0)

	i = start
	while i < n :
		if not st[i] and cur + sticks[i] <= length : #可行性剪枝
			st[i] = True
			if dfs(u, cur + sticks[i], i + 1) : return True
			st[i] = False
			if not cur or cur + sticks[i] == length : return False #排除首尾冗余
			j = i + 1
			while j < n and sticks[i] == sticks[j] : j += 1 #排除路径冗余
			i = j - 1
		i += 1
			

while True :
	n = int(input())
	if n == 0 : break
	st = [False] * N
	sticks = list(map(int, input().split()))
	sticks.sort(reverse = True) # 优化搜索顺序
	cal = sum(sticks)
	length = 1
	while True :
		if cal % length == 0 and dfs(0, 0, 0) :
			break
		length += 1
	print(length)
  1. 剪枝
    除了上面提到的可行性剪枝,本题值得注意的是等效冗余剪枝

DFS——剪枝_第2张图片

总结

一般题目都会有可行性剪枝,其他几个如何剪枝需要依据题意进行剪枝

你可能感兴趣的:(数据结构与算法,深度优先,剪枝,算法)