前言:
DFS的迭代优化在于每次控制搜索的深度,然后搜索,就可以避免一些不必要的大量搜索,但要在自己有把握答案在应该浅层时使用,不然可能会适得其反。
IDA*则是基于DFS的一种优化思想。主体是设计一个估值函数valuate(n)以求当前状态到目标状态的理想步数,在加上当前状态的实际步数,如果已经大于某个数,就直接折返。valuate函数求出的值必须满足小于等于这个状态到目标状态的实际值,这样才能保证在时间优化基础上保证正确性。
IDA*通常搭配DFS的迭代优化使用,如果实际步数加上理想步数大于maxdep,则返回。
IDA*算法的难点就在于如果设计估值函数,在保证正确性的同时估值函数越大则程序更快。如果估值函数为0,那么就是普通的搜索。例如八数码的每个状态的估值函数则为每个数位置和它目标位置距离的和,实际步数不可能比这个理想步数更小,所以既保证了时间的优化,也保证了正确性。
DFS是成指数增加的,设计一个好的估值函数,则会省去很多层无用功,使效率更高。
例题.
一.骑士精神
代码:
#include<bits/stdc++.h> #define ll long long using namespace std; int n,m,t,mp[7][7],stx,sty,success; char ch; const int dx[]={0,1,1,-1,-1,2,2,-2,-2}; const int dy[]={0,2,-2,2,-2,1,-1,1,-1}; const int goal[7][7]={ {0,0,0,0,0,0}, {0,1,1,1,1,1}, {0,0,1,1,1,1}, {0,0,0,2,1,1}, {0,0,0,0,0,1}, {0,0,0,0,0,0} }; int vvv() { int cnt=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) if(mp[i][j]!=goal[i][j]) cnt++; return cnt; } int safe(int x,int y) { if(x<1 || x>5 || y<1 || y>5) return 0; return 1; } void A_star(int dep,int x,int y,int maxdep) { if(dep==maxdep) { if(!vvv()) success=1; return ; } for(int i=1;i<=8;i++) { int xx=x+dx[i]; int yy=y+dy[i]; if(!safe(xx,yy)) continue ; swap(mp[x][y],mp[xx][yy]); int eva=vvv(); if(eva+dep<=maxdep) A_star(dep+1,xx,yy,maxdep); swap(mp[x][y],mp[xx][yy]); } } int main() { int T; cin>>T; while(T--) { success=0; for(int i=1;i<=5;i++) for(int j=1;j<=5;j++) { cin>>ch; if(ch=='*') mp[i][j]=2,stx=i,sty=j; else mp[i][j]=ch-'0'; } if(!vvv()) { cout<<0<<endl; continue ; } for(int maxdep=1;maxdep<=15;maxdep++) { A_star(0,stx,sty,maxdep); if(success) { cout<<maxdep<<endl; goto zager; } } cout<<-1<<endl; zager:; } return 0; }
估值函数:因为这些马是按日字走的(不和八数码一样),所以我们干脆就将估值函数设为,不在目标状态的马的数量,这样显然是正确的。
题外话:这个goto zager,然后下面有个zager:,这个语法挺方便,背下来!!
二.铁盘整理
题目描述
在训练中,一些臂力训练器材是少不了的,小龙在练习的时候发现举重器械上的铁盘放置的非常混乱,并没有按照从轻到重的顺序摆放,这样非常不利于循序渐进的锻炼。他打算利用一个非常省力气的办法来整理这些铁盘,即每次都拿起最上面的若干个圆盘并利用器械的力量上下翻转,这样翻转若干次以后,铁盘将会按照从小到大的顺序排列好。那么你能不能帮小龙确定,最少翻转几次就可以使铁盘按从小到大排序呢?
例如:下面的铁盘经过如图所示的以下几个步骤的翻转后变为从小到大排列。
这个题首先要离散化。
估值函数:我们会发现每次翻转的整体里的个体差值不变,变得则是将翻转的头和没翻转部分的头连在了一起,他们的差值变了,可能归位了一对,所以我们统计有差值不对的对数,当做我们的估值函数。
最后不要忘了即使所有差值都对了,还要判断正反,既n和n+1的差值也要正确。
代码:
#include<bits/stdc++.h> using namespace std; const int maxn=20; int n,ans; int r[maxn]; int a[maxn]; int fl; int evaluate() { int cnt=0; for(int i=1;i<=n;i++) { if(abs(a[i]-a[i+1])!=1) cnt++; } return cnt; } void dfs(int sum,int pre,int maxdep) { if(fl||sum+evaluate()>maxdep) return ; if(!evaluate()) { fl=1; return ; } for(int i=1;i<=n;i++) { if(i==pre) continue ; reverse(a+1,a+1+i); dfs(sum+1,i,maxdep); reverse(a+1,a+1+i); } } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>r[i],a[i]=r[i]; sort(r+1,r+1+n); for(int i=1;i<=n;i++) a[i]=upper_bound(r+1,r+1+n,a[i])-r-1; a[n+1]=n+1; for(int i=0;;i++) { fl=0; dfs(0,0,i); if(fl) { cout<<i<<endl; break; } } return 0; }
总结:
推出估值函数,你就完成了IDA*的一半,就像推出递推式,你就完成了DP的一半。