搜索专题小结:迭代加深搜索

迭代加深搜索

迭代加深搜索(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;
}

你可能感兴趣的:(迭代加深搜索)