搜索优化
康拓展开
康拓展开是一种在全排列中数值与所处位置的对应关系的展示,可以在搜索算法中起到判重的作用,这样可以大大缩短运算量。
举例说明一下:有12345这5个数,给出21543,根据康拓展开可以求出这是位于字典序中的第几大数,首先从第一个数开始,比它小的只有1,后面还有4!种,所以是14!种,再看下一位数,没有比1小的,这样只有03!,以此类,就可以得到比当前这个数的字典序。(注意:这里不仅要求是比当前位的数小,还要求被比较的数没有出现过,所以只需要让当前位的数与在他之后的每一位数作比较即可)
long int factory[]={1,1,2,6,24,120,720,5040,40320,362880};
bool flag[362880];
bool cantor(int str[],int n)
{
long result=0;
for(int i=1;i<=n;i++)
{
int counted=0;
for(int j=i;j<n;j++)
{
if(str[i]>str[j])
counted++;
//看看这一位的数在未出现的数中第几大
}
result+=counted*factory[n-i-1];//康拓公式的运用
}
if(!flag[result])
{
flag[result]=1;
return 1;
}
else return 0;
}
针对八位数字的判重,这里定义的factory数组记录了0到8的情况即提前将对应的阶乘部分乘数放入数组内,减少运算量。
A*算法
这个是非常好理解的一个概念,即在搜索中加入贪心的算法,将超出最优解的情况排除,达到减少运算量的目的,最简单的就是引入曼哈顿距离(即两点之间纵坐标差与横坐标差之和)
可以在搜索中引入评估函数:f(x)=g(x)+h(x),g(x)表示初始状态到当前状态的实际代价,h(x)表示当前状态到最终状态的最优解(也被称为启发函数)这说明。h(x)决定了整个算法的优劣。
如果g(x)=0,那么就是f(x)=h(x),也就是纯粹的贪心算法,没有搜索的帮助下可能会陷入局部最优而无法得到答案(无法考虑每个位置的情况),如果h(x)=0,那么就是f(x)=g(x),又变成了纯粹的搜索,这样运算量又会加大。
双向广搜
这个是广搜算法的优化,即从两头开始进行搜索,这样只需要查看中间是否出现了交点,可以适用于判断能否在固定步数内完成状态的变化。(可以使用双向队列作为辅助)
迭代加深搜索
这个我个人认为对题目的限制要求较高,只针对于那些可能会出现无限或者数据巨大的情况,比如埃及数这道题(给出一个有理数,让他由分数组成,要求分数最少的情况,同时最小的分数尽可能大)这道题的数据范围就是无限的,这样即使是深搜还是广搜都是会爆的,这时候可以对dfs的范围进行限制,从第一层开始广搜,然后是第二层,第三层,这样直接避免了dfs的弊端。
IDA*
这个是将上面两种算法结合使用,在迭代加深搜索的同时给出启示函数,目的都是减少运算量
#include <iostream>
#include <cmath>
using namespace std;
int val[1010]={0};
int pos,depth;
int n;
bool ida(int now,int depth)
{
if(now>depth) return false;//第一次剪枝,如果超出当前迭代范围,直接退出
if(val[pos]<<(depth-now)<n) return false;//第二次剪枝,估价函数,也是启示函数,即当前数以2的倍数增长,也就是最大到达的数值,如果小于目标直接退出
if(val[pos]==n) return true;//返回得到结果
pos++;
for(int i=0;i<pos;i++)//深搜部分
{
val[pos]=val[pos-1]+val[i];//深搜的范围:这个数与已知任意数的一次加或减运算
if(ida(now+1),depth) return true;
val[pos]=abs(val[pos-1]-val[i]);
if(ida(now+1),depth) return true;
}
pos--;//未能找到结果,返回上次的状态
return false;
}
int main()
{
while(cin>>n&&n!=0)
{
depth=0;
for(depth=0;;depth++)//迭代部分核心代码,控制当前搜索深度
{
val[0]=1;
pos=0;
if(ida(0,depth))
break;
}
cout<<depth<<endl;
}
return 0;
}