今天学习了IDA*算法,在这里总结一下:
IDA*算法是A*算法和迭代加深算法的结合。
迭代加深算法是在dfs搜索算法的基础上逐步加深搜索的深度,它避免了广度优先搜索占用搜索空间太大的缺点,也减少了深度优先搜索的盲目性。它主要是在递归搜索函数的开头判断当前搜索的深度是否大于预定义的最大搜索深度,如果大于,就退出这一层的搜索,如果不大于,就继续进行搜索。这样最终获得的解必然是最优解。
而在A*算法中,我们通过使用合理的估价函数,然后在获得的子节点中选择fCost最小的节点进行扩展,以此完成搜索最优解的目的。但是A*算法中,需要维护关闭列表和开放列表,需要对扩展出来的节点进行检测,忽略已经进入到关闭列表中的节点(也就是所谓的“已经检测过的节点”),另外也要检测是否与待扩展的节点重复,如果重复进行相应的更新操作。所以A*算法主要的代价花在了状态检测和选择代价最小节点的排序上,这个过程中占用的内存是比较大的,一般为了提高效率都是使用hash进行重复状态检测。
而IDA*算法具有如下的特点:(
综合了A*算法的人工智能性和回溯法对空间的消耗较少的优点,在一些规模很大的搜索问题中会起意想不到的效果。它的具体名称是 Iterative Deepening A*, 1985年由Korf提出。该算法的最初目的是为了利用深度搜索的优势解决广度A*的空间问题,其代价是会产生重复搜索。归纳一下,IDA*的基本思路是:首先将初始状态结点的H值设为阈值maxH,然后进行深度优先搜索,搜索过程中忽略所有H值大于maxH的结点;如果没有找到解,则加大阈值maxH,再重复上述搜索,直到找到一个解。在保证H值的计算满足A*算法的要求下,可以证明找到的这个解一定是最优解。在程序实现上,IDA* 要比 A* 方便,因为不需要保存结点,不需要判重复,也不需要根据 H值对结点排序,占用空间小。
而这里在IDA*算法中也使用合适的估价函数,来评估与目标状态的距离。
在一般的问题中是这样使用IDA*算法的,当前局面的估价函数值+当前的搜索深度 > 预定义的最大搜索深度时,就进行剪枝。
这个估计函数的选取没有统一的标准,找到合适的该函数并不容易,但是可以大致按照这个原则:在一定范围内加大各个状态启发函数值的差别。
下面是poj2286 the rotation game的一道题目,使用了IDA*算法:
#include #include using namespace std; //a:记录数字的数组 int a[25], cnt[25]; char out[1000]; int deep; //最后的局面中心的结果数字 int res; //判断是否到达目标状态 bool ok(int *a) { int tmp=a[7]; if( tmp!=a[8] || tmp!=a[9] || tmp!=a[12] || tmp!=a[13] || tmp!=a[16] || tmp!=a[17] || tmp!=a[18] ) return false; return true; } //旋转 inline void change(int* cur,int a,int b,int c,int d,int e,int f,int g) { int tmp=cur[a]; cur[a]=cur[b],cur[b]=cur[c],cur[c]=cur[d],cur[d]=cur[e],cur[e]=cur[f],cur[f]=cur[g],cur[g]=tmp; } //计算8个标准位置中数量最多的数字有多少个 inline int count(int* a) { memset(cnt,0,sizeof(cnt)); cnt[a[7]]++;cnt[a[8]]++;cnt[a[9]]++; cnt[a[12]]++;cnt[a[13]]++;cnt[a[16]]++; cnt[a[17]]++;cnt[a[18]]++; cnt[2]=cnt[1]>cnt[2]?cnt[1]:cnt[2]; return max(cnt[2],cnt[3]); } //pre:上一次递归移动的方向 //now:当前递归的步数 bool dfs(int *a, int now, int pre) { //剪枝:如果当前可以递归的深度已经小于局面中心数字所需量 if (deep - now < 8 - count(a)) return false; //超过当前的搜索深度 if (now >= deep) return false; int temp[25]; //备份当前的局面 for (int i = 0; i < 8; ++i) { //如果前后两次拉动的方向恰好相反,那么就剪枝 if( (pre==0 && i==5) || (pre==5 && i==0) ) continue; if( (pre==1 && i==4) || (pre==4 && i==1) ) continue; if( (pre==2 && i==7) || (pre==7 && i==2) ) continue; if( (pre==3 && i==6) || (pre==6 && i==3) ) continue; for (int j = 1; j <= 24; ++j) { temp[j] = a[j]; } switch(i) { case 0:out[now]='A';change(temp,1,3,7,12,16,21,23);break; case 1:out[now]='B';change(temp,2,4,9,13,18,22,24);break; case 2:out[now]='C';change(temp,11,10,9,8,7,6,5);break; case 3:out[now]='D';change(temp,20,19,18,17,16,15,14);break; case 4:out[now]='E';change(temp,24,22,18,13,9,4,2);break; case 5:out[now]='F';change(temp,23,21,16,12,7,3,1);break; case 6:out[now]='G';change(temp,14,15,16,17,18,19,20);break; case 7:out[now]='H';change(temp,5,6,7,8,9,10,11);break; } if (ok(temp)) { res = temp[7]; out[now+1] = '/0'; return true; } if (dfs(temp, now + 1, i)) return true; } return false; } int main() { while (scanf("%d", &a[1]) && a[1]) { for (int i = 2; i <= 24; ++i) scanf("%d", a+i); if (ok(a)) { //如果已经符合要求了 printf("No moves needed/n"); printf("%d/n", a[7]); } else { deep = 1; //clock_t time= clock(); while (1) { if (dfs(a, 0, -1)) break; ++deep; } printf("%s/n", out); printf("%d/n", res); } } return 0; }
有空找一下错误,发现错误的哥们可以给我留个言:(ps:错误解决了,在IDA*算法的递归调用if语句为true时,应该返回的是true,而不是false)。
/* * POJ2286 The Rotation Game * 解题思路:使用迭代加深的深度搜索算法,这里非常要注意还是剪枝的问题 * 只有较好的针对题目环境的剪枝,才能提高搜索效率 */ #include #include #include using namespace std; //使用数组表示游戏局面 int map[25],countArray[25]; //搜索深度 , 最终的局面中心数字 int DEPTH,res; char outPath[100]; //判断是否到达了目标局面 bool isOk(const int* state){ int tmp = state[7]; if( tmp!= state[8] || tmp!=state[9] || tmp!= state[12] || tmp!= state[13] || tmp!=state[16] || tmp!= state[17] || tmp!= state[18] ){ return false ; } return true ; } //统计局面的中心区域含有相同数字的最大数量 int countMaxSameNumber(const int* state){ memset(countArray , 0 ,sizeof(countArray) ) ; countArray[ state[7] ]++; countArray[state[8]] ++; countArray[state[9]]++; countArray[ state[12] ]++; countArray[ state[13] ]++ ; countArray[state[16]]++; countArray[state[17]] ++ ; countArray[state[18]]++; countArray[2] = (countArray[2]>countArray[1]) ? countArray[2]: countArray[1]; return max(countArray[2],countArray[3]); } void changeState(int *state,int a1,int a2,int a3,int a4,int a5,int a6,int a7){ int tmp = state[a1]; state[a1]=state[a2],state[a2]=state[a3],state[a3]=state[a4], state[a4]=state[a5],state[a5]=state[a6],state[a6]=state[a7],state[a7]=tmp; } //迭代加深搜索 //state:当前局面 currDepth :当前所处的搜索深度 preDir:当前搜索选择的旋转的方向 bool dfsSearch( int* state ,int currDepth , int preDir) { //剪枝 1 : 本质上使用的就是IDA*估价函数进行剪枝 if( DEPTH - currDepth < 8- countMaxSameNumber(state)) return false ; //超过了当前的搜索深度 if( DEPTH <= currDepth ) return false ; int tmp[25]; for(int i=1 ; i<=8 ; i++){ //剪枝2 :前后连续的相反方向的两次旋转是没有意义的 if( (1 == i && 6 == preDir) || (6==i && 1== preDir) ) continue ; if( (2 == i && 5 == preDir) || (5==i && 2== preDir) ) continue ; if( (3 == i && 8 == preDir) || (8==i && 3== preDir) ) continue ; if( (4 == i && 7 == preDir) || (7==i && 4== preDir) ) continue ; // memcpy( tmp , state , sizeof(state)) ; for(int k=1 ; k<=24 ; k++) tmp[k]=state[k]; switch(i){ //记录搜索路径 case 1 : outPath[currDepth] = 'A' ; changeState(tmp,1,3,7,12,16,21,23); break; case 2 : outPath[currDepth] = 'B' ; changeState(tmp,2,4,9,13,18,22,24); break; case 3 : outPath[currDepth] = 'C' ; changeState(tmp,11,10,9,8,7,6,5); break; case 4 : outPath[currDepth] = 'D' ; changeState(tmp,20,19,18,17,16,15,14); break; case 5 : outPath[currDepth] = 'E' ; changeState(tmp,24,22,18,13,9,4,2); break; case 6 : outPath[currDepth] = 'F' ; changeState(tmp,23,21,16,12,7,3,1); break; case 7 : outPath[currDepth] = 'G' ; changeState(tmp,14,15,16,17,18,19,20); break; case 8 : outPath[currDepth] = 'H' ; changeState(tmp,5,6,7,8,9,10,11); break; default : cout<<"ERROR!"<>map[1]; if( 0 == map[1]) break; for(int i=2 ; i<=24 ; i++) in>>map[i]; if( isOk(map)){ cout<<"No moves needed"<
下面是一道自己做的poj2870的搜索题,在这道题中,我也使用了IDA*算法来剪枝,比起纯粹的dfs,效果还是很明显的。
/* * POJ2870 Light Up */ //#include #include #include #include #define MAXSIZE 8 #define MAX_BARRIAR 50 #define NOT ! #define __DEBUG 0 using namespace std; char board[MAXSIZE][MAXSIZE]; int wid,height,barriarNum; //表示搜索的深度 int depth,minLamp; //记录有编号的障碍的位置的结构体 typedef struct numedbarriar{ int x; int y; int no; }NumedBarriar; NumedBarriar barriarArray[MAX_BARRIAR] ; static int dir[4][2]={ {0,-1},{-1,0},{0,1},{1,0} }; //打印当前局面 void print(char c[][MAXSIZE]){ for(int i=1 ; i<=height ; i++){ for(int j=1 ; j<=wid ; j++){ cout<height || y<1 || y>wid ) ? false : true ; } //标记被照亮的地方 void change(char map[MAXSIZE][MAXSIZE],int x,int y){ map[x][y]='L'; int m,n ; //往左 for(int j=0 ; j<4 ; j++){ for( m=x+dir[j][0] , n=y+dir[j][1]; isInBoard(m,n) ; m+=dir[j][0], n+=dir[j][1] ){ if( '.'== map[m][n] || 'm' == map[m][n]){ map[m][n]='m'; } else{ break; } } } } //计算有编号的障碍周围还缺少的Lamp数目 int gCost(char map[][MAXSIZE]){ int num=0 ,x,y , dist =0 ; for(int i=0 ; i barriarArray[i].no) return false ; } return true ; } bool dfsSearch(char map[MAXSIZE][MAXSIZE],int currDepth) { //IDA*剪枝 if( currDepth + gCost(map) > depth ) return false ; if( currDepth >=minLamp ) return false ; //剪枝1 if(NOT isAllowed(map)){ return false ; } int i,j; char tmpMap[MAXSIZE][MAXSIZE]; for(i=1 ; i<= height ; i++){ for(j=1 ; j<=wid ; j++){ //找到可放置的位置 if( '.' == map[i][j] ){ //memcpy(tmpMap , map , sizeof(map) ); for(int m=1; m<= height ; m++){ for(int n=1 ; n<=wid ; n++){ tmpMap[m][n]= map[m][n]; } } //改变当前的局面,标记当前lamp照亮的位置 change(tmpMap,i,j); if( isCompleted(tmpMap) ) { if( currDepth < minLamp ) minLamp = currDepth+1; // cout<<"find"<>height>>wid ; if( 0==wid && 0== height) break; //初始化局面 for(int i=0 ; i< MAXSIZE ; i++){ for(int j=0 ; j>barriarNum; for(int i=0 ; i>barriarArray[i].x>>barriarArray[i].y>>barriarArray[i].no; //普通的barriar if( -1 == barriarArray[i].no) board[ barriarArray[i].x ][barriarArray[i].y]='b'; //有编号的barriar else{ board[ barriarArray[i].x ][barriarArray[i].y]=barriarArray[i].no+'0'; } } depth = 0; clock_t time=clock(); while(1){ if( NOT dfsSearch(board , 0)) depth ++; else break; if(depth>100){ cout<<"No solution"<
在这里我总结一下IDA*搜索算法的一些注意点:
1、IDA*算法中,main函数一般使用逐步加大搜索深度来获得最优解,而如果使用dfs搜索策略的话,就需要对整个搜索空间进行搜索,在搜索函数中判断出所有合法解中的最优解(通过比较合法解的深度,选择深度最小的,其实在bfs搜索中,深度对应的就是题目中所要求的最优解量)
2、在IDA*的搜索核心算法中,前半部分就是剪枝,返回false的情况,这点和dfs是一样的。而在常规的搜索中,当找到最优解时,就返回true,而当后面的if语句中的dfs递归为true时,返回true,如此退出整个循环,否则会死循环。为了提高效率,可以把当前状态拷贝到临时状态,然后使用临时状态进行递归搜索,这样就不需要在递归函数返回时,还原原来的整个状态。
另外可以看到通过下面的代码中添加适当的语句就可以还原出整个最优解的路径:
if( isCompleted(tmpMap) ) { if( currDepth < minLamp ) minLamp = currDepth+1; // cout<<"find"<