我的搜索-小结
以前写bfs、dfs都感觉这类题很弱,像什么八皇后问题,很简单的dfs,但是这个暑假做搜索题却发现,自己所学只是皮毛罢了。
遇到一道记忆化搜索直接挂,看了题解,研究了一整天才凑活搞明白,实际上呢!非常easy,算法乃需要悟之精髓,领其思想,才能以不变应万变,方能驾驭于万题之上。
首先是遇到记忆化搜索,woc看不懂啊,怎么这么高深(事实对我当时的确如此,因为还要hash)
那么记忆化搜索在我看来,是爆搜+偷懒(被众人称为DP思想跑我这儿成偷懒了),没挫,其实就是搜索,但是遇到之前搜过的情况我还费劲搜他干啥,直接拿来用啊。
此乃思想,然而算法不是用了思想就可以的,掌握一项算法或数据结构,还要能将它实现为代码。那么problem来了,我怎么知道我之前搜过,就算知道搜过,我又怎么能把原来的情况直接告诉现在的状态。如果能直接开数组那多简单,数组里面保存需要的信息就可以啦。
然而。。。往往没这么简单,即使实际状态数在空间允许范围之内,也不一定能开出合适的数组,比如9个数全排列,有9!种,但可能开数组就是九维9^9MLE,所以要能将冗余的情况剔除掉。
3种方法,在lrj的书上给出了 完美hash(即绝对的一一映射在一段连续整数内)、普通hash(需要处理冲突)、stl-set(有常数,大概经实际题目测试,用set 9秒,用hash 4秒)
其中完美hash比较难,目前仅知道 康拓展开 ,set很方便的(insert,count,clear,erase)操作能够简化代码,作为跳板保证主算法正确性。而主要要掌握的其实还是普通的hash,毕竟他的用途还是很广泛的。
其实我还是没能很好的悟懂hash算法的精髓,不过大体我在编码的时候遇到一个通项,也就是想要把状态转化成整数,然后通过取模映射到hashsize中,而这个就应该是它的hash值,而对应的hashlist一般都是存储一些需要用的东西,但也不一定,这个地方存什么需要先思考一下。至于处理冲突,我还是喜欢开放寻址+线性补偿。
然后总结一下,bfs这个基础的搜索算法。
bfs的具体步骤,强烈建议将其作为函数来写,即使这道题弱爆了(放心以后不会遇到这么弱的题目),毕竟我发现函数可以直接return 不需要break、bool等影响代码美观的东西。需要一个队列和一个判重数组。
设置初始状态入队,当队列不为空做:检测队头(考虑出队时机,在最开始还是在最后有讲究噢,要是一开始就出队就没法后悔了如果是queue)
处理结点:考虑是否结束()扩展结点
over 这是一个很简单很简单的算法。有一种把每个队列元素当做结点的思想over
然后是dfs,回溯是什么这种名词不懂啊,管它呢。如果遇到有条件的就返回,一般写在dfs最前面,剪枝写在扩展结点的地方。注意dfs完是否要恢复状态。over
两者都是一个要注意判重的问题,反正搜索自己看着办。
A* IDA* 另外两个搜索算法。
IDA* 就是怕dfs吊死在一棵树上,又怕bfs太能等状态保存不了,于是就有了ID算法,即dfs的限制版,你不能一棵树上吊死!要有个度,实在不行咱再挖深一点,整个过程会重复之前搜过的,然而这点对于新加深的一层而言还是差多了。这就是ID算法,非常easy,只要主程序对maxd循环,dfs遇到maxd判断返回即可。那么IDA*是啥,我靠好高级的样子啊,(⊙o⊙)…然而。。。其实很扯很扯,就是ID算法的剪枝罢了。不过这个的确用到了A*算法的思想也就是需要启发式函数,什么是启发式函数,好高级,其实我也不造,不过凭感觉大概是,你这个搜索树智商好低,你看那个东西离你那么远你还浪费时间去搜那个,我启发一下你好了,于是就用启发式函数让它少搜一些不必要的东西,在我看来就是剪枝嘛。不过也是有公式的,具体是啥不记得了,大概就是h(x)+g(x)这个h(x)就是你当前深度,同dfs,bfs的step,很low的,那这个g(x)就是对于当前状态的评估函数,不过是相对于终点的,IDA*算法也就是在这个地方每道题的g函数都不同罢了。但是有一点!这个g函数一定要比实际代价小,当然最好是相等,否则就会有问题。
这里附上IDA*算法的lrj书上的题目的我的代码(这句话好绕,懒得改了)
Uva 11212
1 #include <iostream> 2 #include <set> 3 #include <cstring> 4 using namespace std; 5 const int maxn=10; 6 const int maxStatus=1000007; 7 set<int>vis; 8 int n; 9 int hashlist[maxStatus]; 10 struct Node{ 11 int s[maxn]; 12 int stInt(){ 13 int res=0; 14 for(int i=0;i<n;i++)res=res*10+s[i]; 15 return res; 16 } 17 int hash(){ 18 int st=stInt(); 19 int h=st%maxStatus; 20 for(int i=0;i<maxStatus;i++){ 21 if(hashlist[h]==-1)break; 22 if(hashlist[h]==st)break; 23 h=(h+7)%maxStatus; 24 } 25 return h; 26 } 27 }start,goal; 28 int maxd; 29 bool dfs(Node u ,int dep){ 30 if(dep==maxd){ 31 if(u.stInt()==goal.stInt())return true; 32 return false; 33 } 34 int res=0; 35 for(int i=0;i<n-1;i++)if(u.s[i]!=u.s[i+1]-1)res++; 36 if(res>(maxd-dep)*3)return false; 37 for(int i=0;i<n;i++) 38 for(int j=i;j<n;j++) 39 for(int k=0;k+j-i<n;k++){ 40 Node v; 41 for(int x=0;x<=j-i;x++)v.s[k+x]=u.s[i+x]; 42 int poiU=0,poiV=0; 43 while(poiU<n){ 44 while(poiU>=i&&poiU<=j)poiU++; 45 while(poiV>=k&&poiV<=k+j-i)poiV++; 46 if(poiU<n)v.s[poiV++]=u.s[poiU++]; 47 } 48 int stInt=v.stInt(); 49 int h=v.hash(); 50 if(hashlist[h]==stInt)continue; 51 hashlist[h]=stInt; 52 if(dfs(v,dep+1))return true; 53 hashlist[h]=-1; 54 } 55 return false; 56 } 57 int main(){ 58 for(int i=0;i<10;i++)goal.s[i]=i; 59 int t=0; 60 while(cin>>n&&n){ 61 for(int i=0;i<n;i++){ 62 cin>>start.s[i]; 63 start.s[i]--; 64 } 65 for(maxd=0;;maxd++){ 66 memset(hashlist,-1,sizeof(hashlist)); 67 if(dfs(start,0))break; 68 } 69 cout<<"Case "<<++t<<": "<<maxd<<endl; 70 } 71 return 0; 72 }
对于变量名的命名,这里我还要提一下,竞赛中变量名要命得尽量简短但又能较直接表达其意义,更重要的是不能出现编译ambiguous的错误,这是很致命的,一旦出现ambiguous的编译错误会直接导致这道题0分,然而ambiguous的编译问题不一定会在本机上体现出来,所以对于命名一些没有把握的名称的时候可以添加前缀或者后缀,当然这里的前缀和后缀最好是不具备什么意义且容易区分的。习惯也是要培养的。
同样附上IDA*算法中的典型埃及分数问题的代码,这道题看网上的代码觉得不好理解,我也是思考了很久,其实发觉自己的思路是没有错的,重要在于实现代码,当然我的代码也是没有问题的,但是还是不太相信自己,所以遇到错误的时候没有坚持是因为小bug导致的而浪费了时间。
能写出简短而又高效的代码固然是好的,但是在不损失空间时间的情况下,竞赛中更应该写自己有把握的代码,即使多写了几行也是有把握的,易调试的,尤其在考场这种高压环境下,是至关重要的。
Uva - 12558
1 #include <iostream> 2 #include <map> 3 #include <cstring> 4 using namespace std; 5 typedef long long LL; 6 const int maxn=1000; 7 map<LL,bool>resNum; 8 int maxd; 9 LL v[maxn],ans[maxn]; 10 LL small(LL a,LL b){ 11 LL c=b/a; 12 while(a*c<b)c++; 13 return c; 14 } 15 LL gcd(LL a,LL b){ 16 if(b==0)return a; 17 return gcd(b,a%b); 18 } 19 bool better(int dep){ 20 for(;dep>=0;dep--)if(v[dep]!=ans[dep])break; 21 return !ans[dep] || v[dep]<ans[dep]; 22 } 23 bool dfs(int dep,LL from,LL a,LL b){ 24 from=max(from,small(a,b)); 25 if(dep==maxd){ 26 if(a!=1)return false; 27 if(b==from&&!resNum[b])v[dep]=b; 28 else return false; 29 // if(b%a)return false; ///// 30 // if(b!=from)return false; 31 // v[dep]=b/a; ///// 32 // if(resNum[v[dep]])return false; 33 // if(v[dep]==0)cout<<a<<' '<<b<<' '<<from<<endl; 34 if(better(dep))memcpy(ans,v,sizeof(v)); 35 return true; 36 } 37 bool ok=false; 38 for(int i=from;;i++){ 39 if(resNum[i])continue; 40 if(b*(maxd-dep+1)<i*a)break; 41 v[dep]=i; 42 LL aa,bb; 43 aa=a*i-b; 44 bb=b*i; 45 // if(aa<=0)continue; 46 LL g= gcd(aa,bb); 47 if(dfs(dep+1,i+1,aa/g,bb/g))ok=true; 48 } 49 return ok; 50 } 51 int main() 52 { 53 int T; 54 cin>>T; 55 for(int i=1;i<=T;i++){ 56 57 LL a,b,k; 58 cin>>a>>b>>k; 59 resNum.erase(resNum.begin(),resNum.end()); 60 for(int j=0;j<k;j++){ 61 LL x; 62 cin>>x; 63 resNum[x]=true; 64 } 65 for(maxd=0;;maxd++){ 66 // cout<<maxd<<':'<<endl; 67 memset(ans,0,sizeof(ans)); 68 if(dfs(0,small(a,b),a,b))break; 69 } 70 cout<<"Case "<<i<<": "; 71 cout<<a<<'/'<<b<<'='; 72 for(int j=0;j<maxd;j++)cout<<"1/"<<ans[j]<<'+'; 73 cout<<"1/"<<ans[maxd]<<endl; 74 } 75 }
说到c++的调试,让我很不习惯的是它不像pascal一样可以很容易的单步调试,当然可以调用gdb,不过还是很麻烦,一旦开始调试将会浪费很多时间。所以要做的是遇到错误,凭经验处理,尽量肉眼纠错,配合输出中间结果,这个能力是要培养的。而且不要过度依赖输出中间结果,这也是很浪费时间的,而且影响代码的美观。毕竟肉眼检查花的时间相对短,而且调试不一定总能发现一些细节上的错误。
然后讲一下双向BFS,这个算法凭着感觉打过一遍,而且还AC了那道题,然而我才知道原来我的那个写法是错的(无语O__O "…),双向bfs要一层一层交替地搜索,而不是一个节点一个节点之间的交替,一定要一层一层,方法其实很简单,两个函数,返回是否找到,再来一个主bfs,若找到返回步数,若没找到再往外扩,交替搜索。双向bfs的复杂度大概是原复杂度开根号,当然还是要再大一点。我在计算复杂度的时候发现了一个问题,我拿时间作为复杂度开根号了,这貌似不对,而应该拿计算次数开根号……
听说此题很经典,我发一下它的代码。
Uva - 1601
1 #include <iostream> 2 #include <map> 3 #include <queue> 4 #include <cctype> 5 #include <string> 6 #include <cstring> 7 using namespace std; 8 const int maxn=20; 9 const int baseM[]={1,1<<8,1<<16}; 10 const int maxsize=1<<24; 11 struct Node{ 12 int v[3][2],dist; 13 inline int stInt(){ 14 int sum=0; 15 for(int i=0;i<3;i++)sum+=(v[i][0]*16+v[i][1])*baseM[i]; 16 return sum; 17 } 18 }; 19 int Map[maxn][maxn]; 20 Node start,goal; 21 int w,h,n; 22 void read_data(){ 23 char c; 24 start.dist=0; goal.dist=0; 25 memset(start.v,0,sizeof(start.v)); 26 memset(goal.v,0,sizeof(goal.v)); 27 string line; 28 getline(cin,line); 29 for(int i=0;i<h;i++){ 30 getline(cin,line); 31 for(int j=0;j<w;j++){ 32 char c=line[j]; 33 if(c=='#')Map[i][j]=0; 34 if(c==' ')Map[i][j]=1; 35 if(isalpha(c)){ 36 Map[i][j]=1; 37 if(isupper(c))goal.v[c-'A'][0]=i,goal.v[c-'A'][1]=j; 38 else start.v[c-'a'][0]=i,start.v[c-'a'][1]=j; 39 } 40 } 41 } 42 //////////////////------------------------------------------------- debug --------- 43 /* for(int i=0;i<h;i++){ 44 for(int j=0;j<w;j++)cout<<Map[i][j]; 45 cout<<endl; 46 } 47 cout<<start.v[n-1][0]<<' '<<start.v[n-1][1]<<endl; 48 cout<<goal.v[n-1][0]<<' '<<goal.v[n-1][1]<<endl; 49 */ 50 } 51 const int stkindM[]={5,5*5,5*5*5}; 52 const int dir[5][2]={{1,0},{-1,0},{0,1},{0,-1},{0,0}}; 53 inline bool walk(Node u,int step,Node &v){ 54 int move[3]; 55 for(int i=0;i<n;i++){ 56 move[i]=step%5; 57 step/=5; 58 } 59 v=u; 60 for(int i=0;i<n;i++){ 61 int newx=v.v[i][0]=u.v[i][0]+dir[move[i]][0], 62 newy=v.v[i][1]=u.v[i][1]+dir[move[i]][1]; 63 /* if(newx<0||newx>=h||newy<0||newy>=w){ 64 cout<<"!!!"; 65 return false; 66 }////////////////////////////// 67 */ if(Map[newx][newy]==0)return false; 68 } 69 for(int i=0;i<n;i++)for(int j=i+1;j<n;j++) 70 if(v.v[i][0]==v.v[j][0]&&v.v[i][1]==v.v[j][1]|| 71 v.v[i][0]==u.v[j][0]&&v.v[i][1]==u.v[j][1]&& 72 v.v[j][0]==u.v[i][0]&&v.v[j][1]==u.v[i][1])return false; 73 ///////////-------------------------------------------------------------------debug -------- 74 /* if(n==1){ 75 cout<<"Dir : dir move(x,y) "<<dir[move[0]][0]<<' '<<dir[move[0]][1]<<endl; /////////// 76 } 77 */ return true; 78 } 79 Node q1[maxsize],q2[maxsize]; 80 bool vis1[maxsize],vis2[maxsize]; 81 int front1,back1,front2,back2; 82 bool bfs_fro(int step,int &front1,int &back1){ 83 while(front1<back1){ 84 Node u=q1[front1]; 85 if(u.dist>step)return false; 86 if(vis2[u.stInt()]){ 87 // cout<<dist1[u.stInt()]<<' '<<dist2[u.stInt()]<<endl; 88 return true; 89 } 90 Node v; 91 for(int i=0;i<stkindM[n-1];i++) 92 { 93 if(!walk(u,i,v))continue; 94 int stInt=v.stInt(); 95 if(vis1[stInt])continue; 96 vis1[stInt]=true; 97 v.dist=u.dist+1; 98 q1[back1]=v; back1++; 99 ///////////-------------------------------------------------------------------debug -------- 100 /* if(n==1){ 101 cout<<"Position v :(x,y) "<<v.v[0][0]<<' '<<v.v[0][1]<<endl; /////////// 102 cout<<"stInt v :(x,y) "<<v.stInt()<<endl;/////////// 103 } 104 */ } 105 front1++; 106 } 107 return false; 108 } 109 bool bfs_back(int step,int &front2,int &back2){ 110 while(front2<back2){ 111 Node u=q2[front2]; 112 if(u.dist>step)return false; 113 if(vis1[u.stInt()]){ 114 // cout<<dist1[u.stInt()]<<' '<<dist2[u.stInt()]<<endl;/////// 115 return true; 116 } 117 Node v; 118 for(int i=0;i<stkindM[n-1];i++) 119 { 120 if(!walk(u,i,v))continue; 121 int stInt=v.stInt(); 122 if(vis2[stInt])continue; 123 vis2[stInt]=true; 124 v.dist=u.dist+1; 125 q2[back2]=v;back2++; 126 ///////////-------------------------------------------------------------------debug -------- 127 /* if(n==1){ 128 cout<<"Position v :(x,y) "<<v.v[0][0]<<' '<<v.v[0][1]<<endl; /////////// 129 cout<<"stInt v :(x,y) "<<v.stInt()<<endl;/////////// 130 } 131 */ } 132 front2++; 133 } 134 return false; 135 } 136 int solve(){ 137 memset(vis1,0,sizeof(vis1));memset(vis2,0,sizeof(vis2)); 138 vis1[start.stInt()]=true; vis2[goal.stInt()]=true; 139 front1=1,back1=2;front2=1,back2=2; 140 q1[front1]=start;q2[front2]=goal; 141 int step1=0,step2=0; 142 while(front1<back1||front2<back2){ 143 if(bfs_fro(step1,front1,back1))return step1+step2; 144 else step1++; 145 if(bfs_back(step2,front2,back2))return step1+step2; 146 else step2++; 147 } 148 return -1; 149 } 150 int main() 151 { 152 freopen("input.txt","r",stdin); 153 while(cin>>w>>h>>n&&w&&h&&n){ 154 read_data(); 155 cout<<solve()<<endl; 156 } 157 return 0; 158 }
加油!↖(^ω^)↗ 继续Fighting!