迭代加深搜索(Iterative Deepening Depth-First Search, IDDFS)经常用于理论上解答树深度上没有上界的问题,这类问题通常要求出满足某些条件时的解即可。比如在“埃及分数”问题中要求将一个分数a/b分解成为若干个形如1/d的加数之和,而且加数越少越好,如果加数个数相同,那么最小的分数越大越好。下面总结一下该方法的一般流程:
(1)概述:迭代加深搜索是通过限制每次dfs的最大深度进行的搜索。令maxd表示最大的搜索深度,那么dfs就只能在0~maxd之间来进行,如果在这个范围内找到了解,就退出大循环,否则maxd++,扩大搜索范围。但可想而知,倘若没有高效及时的退出无解的情况,那么时间上的开销也是会比较大的。这时就需要进行“剪枝”操作,及时地判断此时能否找到解。对于迭代加深搜索,经常通过设计合适的“乐观估价函数”来判断能否剪枝。设当前搜索的深度是cur,乐观估价函数是h(),那么当cur+h()>maxd时就需要剪枝。
那么什么是乐观估价函数呢?简单的说就是从当前深度到找到最终的解“至少”还需要多少步,或者距离找到最终的解还需要扩展多少层。如果超出了当前限制的深度maxd,说明当前限制的最大深度下是不可能找到解的,直接退出。比如像前面的“埃及分数”问题,要拆分19/45这样的一个分数,假设当前搜索到了第三层,得到19/45=1/5+1/100...那么根据题意此时最大的分数为1/101,而且如果需要凑够19/45,需要(19/45-1/5-1/100)*101=23个1/101才行。即从第3层还要向下扩展至少大于23层的深度才可能找到所有的解。所以如果此时的maxd<23,就可以直接剪枝了。因此(a/b-c/d)/(1/e)便是本题的乐观估价函数。
注意,使用迭代加深搜索时要保证一定可以找到解,否则会无限循环下去。
下面给出“埃及分数”问题的代码以便更好地理解迭代加深搜索的过程:
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<algorithm> #include<string> #include<sstream> #include<set> #include<vector> #include<stack> #include<map> #include<queue> #include<cstdlib> #include<cstdio> #include<cstring> #include<cmath> using namespace std; typedef long long LL; int maxd; int a, b; const int maxn = 1000; int ans[maxn], v[maxn]; int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); } int get_first(int a, int b)//找到1/c≤a/b时最小的c { int c = 1; while (b > a*c)c++; return c; } bool better(int d)//比较深度为d时,现在找到的解是不是更优的 { for (int i = d; i >= 0; i--) if (v[i] != ans[i]) { return ans[i] == -1 || v[i] < ans[i];//两种情况下说明当前更优:(1)此时尚未找到过解;(2)当前的分母小于原来的分母,说明当前的分数比原来的更大,符合题意要求 } return false; } bool dfs(int d, int from, LL aa, LL bb)//当前深度为d,分母不能小于from,分数之和恰好是aa/bb { if (d == maxd)//到达了最后一层 { if (bb%aa)return false;//不能整除,说明最后一项不符合埃及分数的定义,失败退出 v[d] = bb / aa; if (better(d))memcpy(ans, v, sizeof(LL)*(d + 1));//当前找到的解是更优的,更新ans return true; } bool ok = false; from = max(from, get_first(aa, bb));//更新from for (int i = from;; i++)//枚举分母 { if (bb*(maxd + 1 - d) <= i*aa)break;//利用乐观估价函数来剪枝,从当前深度d到达maxd一共有maxd-d+1项,如果(maxd-d+1)*(1/i)还凑不够aa/bb,需要剪枝 v[d] = i; LL b2 = bb*i;//计算aa/bb-1/i,通分后,分母是bb*i,分母是aa*i-bb LL a2 = aa*i - bb; LL g = gcd(a2, b2);//计算分子,分母的最大公约数,便于约分 if (dfs(d + 1, i + 1, a2 / g, b2 / g))ok = true; } return ok; } int main() { int ok = 1; while (scanf("%d%d", &a, &b))//输入分数a/b { for (maxd = 1;; maxd++) { memset(ans, -1, sizeof(ans)); if (dfs(0, get_first(a, b), a, b)){ ok = 1; break; } } printf("%d/%d=", a, b); for (int i = 0;; i++) if (ans[i]>0) printf("%s1/%d", i == 0 ? "" : "+", ans[i]); else { printf("\n"); break; } } return 0; }