搜索的剪枝与优化
一、搜索剪枝概述
搜索算法的时间复杂度大多是指数级的,难以满足对程序运行时间的限制要求,为使降低时间复杂度,对深度优先搜索可以进行一种优化的基本方法——剪枝。
搜索的进程可以看做是从树根出发,遍历一颗搜索树的过程,所谓剪枝,就是通过某些判断,避免一些不必要的遍历过程,形象的说,就是减去搜索树中的某些枝条。
二、优化技巧
1.优化搜索顺序
在不同的问题中,搜索树的各个层次、各个分支之间的顺序不是固定的,不同的搜索顺序会产生不同的搜索树形态,其规模大小也相差甚远。
2排除等效冗余
在搜索过程中,若能判断从搜索树当前节点上沿某几条不同分支到达的子树是相同的,那么只需对其中一条分支执行搜索。
3.可行性剪枝
可行性剪枝也叫上下界剪枝,其是指在搜索过程中,及时对当前状态进行检査,若发现分支已无法到达递归边界,就执行回溯
4.最优性剪枝
在最优化问题的搜索过程中,若当前花费的代价已超过当前搜索到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案,此时可以停止对当前分支的搜索进行回溯。
5.记忆化搜索
在搜索过程中记录每个状态的结果,在重复便利某个状态的时候直接返回其结果。
三、经典题目
小木棍
题目:
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入第一行为一个单独的整数N表示砍过以后的小木棍的总数。第二行为N个用空格隔开的正整数,表示N根小木棍的长度。
输出仅一行,表示要求的原始木棍的最小可能长度。
分析:
从小到大枚举原始木棒长度
l
e
n
len
len,假设所有小木棒总和为
s
u
m
sum
sum,那么原始小木棒的数量为
t
o
t
=
s
u
m
/
l
e
n
tot = sum / len
tot=sum/len。
在搜索时,可进行以下剪枝
1、优化搜索顺序
将小木棍按照长度从大到小的顺序排序,优先使用较长的小木棍
2、排除等效冗余
(1)同一根木棒上木棍顺序等效性:在组成同一根木棒时按照小木棉从大到小的顺序进行搜索。
(2)等长木棍等效性:若某个小木棍无法组合到当前木棒上,那么等长的小木棍都不能。
(3)空木棒等效性:若某个小木棍拼接到一根空木棒上已经失败,那么这根小木棍拼接到剩下空木棒都会失败。
(4)贪心思想:若使用某个小木棍后刚好将一根木棒拼接完整,但是拼接后面的木棒时失败,那么此时直接判定当前分支失败。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
int a[65], cnt, len, n, ans;
bool flag[65];
bool cmp(int a, int b) {
return a > b;
}
//铺第sum根木棍,tot表示已铺的长度, last表示上一根的数量
bool dfs(int sum, int tot, int last) {
if (sum > cnt)
return true;
if (tot == len)
return dfs(sum + 1, 0, 1);
int fail = 0;
for (int i = last; i <= n; i++) {
if (!flag[i] && a[i] + tot <= len && fail != a[i]) {
flag[i] = true;
if (dfs(sum, tot + a[i], i + 1))
return true;
fail = a[i];
flag[i] = false;
if (tot == 0 || tot + a[i] == len)
return false;
}
}
return false;
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
ans += a[i];
}
sort(1 + a, 1 + a + n, cmp);
for (int i = a[1]; i <= ans; i++) {
if(ans % i == 0) {
len = i;
cnt = ans / i;
if(dfs(1, 0, 1)) {
printf("%d", len);
return 0;
}
}
}
return 0;
}