迭代加深搜索
迭代加深搜索是一种状态空间搜索策略,其实质是多轮次地执行有深度限制的深度优先搜索,直到找到目标解或确定无解为止。其中随着轮次的增加,搜索深度的上限也不断增加。
记当前搜索的深度限制为 \(\tau\),算法的主要流程如下:
- 先将深度限制设为 \(\tau = 1\)。
- 从起始状态出发,执行深度不超过 \(\tau\) 的深度优先搜索。
- 如果当前状态是目标解,则算法终止并返回相关信息。
- 如果当前状态的深度 \(d\) 达到深度限制,或其所有子状态均以访问过,则返回前一状态;否则依次递归地处理其尚未访问过的子状态。
- 按照指定的步长,增加搜索深度限制。
- 如果全部搜索结束且没有找到目标解,则输出无解。
当找某个搜索问题的最优解(步数最少的目标解)时,往往优先考虑的是 BFS。而某些问题由于状态的存储消耗很大,单纯的 BFS 可能会导致空间消耗过多。而迭代加深搜索可以看作是用 DFS 方式实现的 BFS,它的空间消耗相对更小,在 DFS(可能时间超限)和 BFS(可能空间超限)之间达成了一种平衡。
例题:UVA529 Addition Chains
题目要求寻找最小长度 \(m\),但由于序列长度不确定,且搜索过程可能会非常深,单纯的 BFS 会因状态过多导致空间爆炸,而单纯的 DFS 则可能深陷无效分支。
为了平衡,采用迭代加深搜索。从小到大限制搜索的深度,如果在当前深度限制下找到了目标值 \(n\),则该深度即为最短长度。在寻找下一个数时,从大到小枚举之前的某两个数,因为较大的数能更快逼近目标 \(n\)。
还可以引入一些剪枝,比如假设当前序列最后一个数为 \(v\),距离深度限制还差 \(\Delta\) 步。即使每一步都取最大增长(即当前的数翻倍),能达到的最大值也仅为 \(v \times 2^{\Delta}\),如果这个数小于 \(n\),说明在当前限制下绝对无法到达 \(n\),直接回溯。
参考代码
#include <cstdio>
const int N = 10005;
int n, path[N];
/**
* DFS 搜索函数
* @param u 当前搜索到的层数(从 0 开始)
* @param depth 当前限制的总深度(最大下标)
*/
bool dfs(int u, int depth) {
if (u == depth) return path[u] == n;
// 剪枝:如果当前值以最快速度(翻倍)增长都无法在规定步数内达到 n
// path[u] * 2^(depth - u) < n
int maxv = path[u];
for (int i = 0; i < depth - u; i++) {
maxv *= 2;
if (maxv >= n) break;
}
if (maxv < n) return false;
// 策略:从大到小枚举,优先尝试较大的数,以便更快接近 n
for (int i = u; i >= 0; i--) {
for (int j = i; j >= 0; j--) {
int val = path[i] + path[j];
// 必须满足严格递增且不大于目标值
if (val > path[u] && val <= n) {
path[u + 1] = val;
if (dfs(u + 1, depth)) return true;
}
}
}
return false;
}
int main()
{
while (true) {
scanf("%d", &n);
if (n == 0) break;
if (n == 1) {
printf("1\n");
continue;
}
path[0] = 1;
// 迭代加深搜索:增加深度限制 m (代表序列的最大下标)
int depth = 0;
while (true) {
if (dfs(0, depth)) {
// 输出结果
for (int i = 0; i <= depth; i++) {
printf("%d%s", path[i], i == depth ? "" : " ");
}
printf("\n");
break;
}
depth++;
}
}
return 0;
}
习题:P10494 [USACO02FEB] Power Hungry Cows
解题思路
搜索空间很大而答案在一个比较小的深度内,适合迭代加深搜索。
- 状态表示:
dfs(a, b, step, lim),其中a和b分别代表两个工作变量当前的指数,step为当前步数,lim为当前设定的最大深度限制。 - 剪枝策略
- 如果当前最大的指数
a经过剩余所有步数(lim - step)连续翻倍(即每次都进行a + a)仍无法达到目标值P,则当前路径必然无解。 - 计算
a和b的最大公约数g,如果目标P不能被g整除,那么无论如何加减都无法得到P,可以提前返回。
- 如果当前最大的指数
参考代码
#include <cstdio>
#include <algorithm>
using namespace std;
int p;
// 求最大公约数,用于剪枝
int gcd(int x, int y) {
return y == 0 ? x : gcd(y, x % y);
}
/**
* 搜索函数
* @param a 当前变量1的指数值
* @param b 当前变量2的指数值
* @param step 当前已进行的步数
* @param lim 当前迭代加深预设的最大深度限制
*/
bool dfs(int a, int b, int step, int lim) {
// 成功条件:其中一个变量达到目标指数 p
if (a == p || b == p) return true;
// 达到当前搜索深度限制仍未找到解
if (step == lim) return false;
// 始终保持 a 为较大值,简化逻辑
if (a < b) swap(a, b);
// 剪枝 1:即便剩下的每一步都让当前最大值翻倍,如果依然无法达到 p,则说明此路径无解
if ((a << (lim - step)) < p) return false;
// 剪枝 2:如果 a 和 b 的最大公约数 g 大于 1,且目标 p 不能被 g 整除,
// 那么仅通过 a, b 的加减运算永远无法得到 p
int g = gcd(a, b);
if (g > 1 && p % g != 0) return false;
// 尝试所有可能的运算组合 (a+a, a+b, b+b, a-b)
int v[4] = {a + a, a + b, b + b, a - b};
for (int i = 0; i < 4; i++) {
int nv = v[i];
// 排除无效值或范围过大的值
if (nv <= 0 || nv > 2 * p) continue;
// 如果生成的新值与现有值重复,跳过
if (nv == a || nv == b) continue;
// 递归搜索:将新生成的值 nv 分别替换旧的 a 或 b
if (dfs(nv, a, step + 1, lim)) return true;
if (dfs(nv, b, step + 1, lim)) return true;
}
return false;
}
int main()
{
scanf("%d", &p);
// 特判:目标是 1 次幂,不需要操作
if (p == 1) {
printf("0\n");
} else {
// 迭代加深:从 1 步开始尝试,直到找到解
// 这种方法保证了找到的第一个解一定是最少步数
int lim = 1;
while (true) {
// 初始状态:变量1为 x^1 (指数为1),变量2为 x^0 (1, 指数为0)
if (dfs(1, 0, 0, lim)) {
printf("%d\n", lim);
break;
}
lim++;
}
}
return 0;
}

浙公网安备 33010602011771号