八数码问题/搜索小结

cdcq讲的挺好的其实。之所以没有回应是因为真的想不出了。

八数码问题

这个问题可以用BFS和A*解决。

BFS

考虑按空格的位置搜索,每次操作相当于移动空格。

此处需要记忆化,不然会T飞。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string>
 4 #include<cstring>
 5 #include<queue>
 6 #include<map>
 7 using namespace std;
 8 int n;
 9 int c[4][4];
10 queue<int> q;
11 map<int,int> mp;
12 
13 int k[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
14 
15 inline bool check(int x,int y){
16     if(x<1||x>3||y<1||y>3)return false;
17     return true;
18 }
19 
20 inline int rebiu(){
21     int ans=0;
22     for(int i=1;i<=3;i++)
23         for(int j=1;j<=3;j++)
24         ans=ans*10+c[i][j];
25     return ans;
26 }//将矩阵转化成数 
27 
28 inline void bfs(int now){
29     q.push(now);
30     mp[now]=0;
31     while(!q.empty()){
32         int lr,ud;//存储空格的位置 
33         int x=q.front();q.pop();
34         if(x==123804765){
35             break;
36         }
37         int shp=x;
38         for(int i=9;i>=1;--i){
39             int k=shp%10;shp/=10;
40             c[(i-1)/3+1][((i-1)%3)+1]=k;
41             if(k==0) ud=(i-1)/3+1,lr=((i-1)%3)+1;
42         }//恢复成矩阵,swap要用的 
43         
44         for(int i=0;i<4;++i){
45             int dx=ud+k[i][0],dy=lr+k[i][1];
46             if(!check(dx,dy))continue;
47             swap(c[ud][lr],c[dx][dy]);
48             int val=rebiu();
49             if(!mp.count(val))
50             mp[val]=mp[x]+1,
51             q.push(val);
52             swap(c[ud][lr],c[dx][dy]);
53         }
54     }
55     
56 }
57 
58 
59 int main(){
60     scanf("%d",&n);
61     bfs(n);
62     cout<<mp[123804765];
63 }

双向BFS

双向BFS的特点:当前状态和目标状态都明确。

双向BFS大大降低了搜索层数,所以快的飞起。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string>
 4 #include<cstring>
 5 #include<queue>
 6 #include<map>
 7 using namespace std;
 8 int n;
 9 int c[4][4];
10 map<int,int> v;//被哪个点扫到 
11 map<int,int> ans;
12 queue<int> q;
13 int finl;
14 int end=123804765;
15 int dr[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
16 
17 inline int rebiu(){
18     int ans=0;
19     for(int i=1;i<=3;i++)
20         for(int j=1;j<=3;j++)
21         ans=ans*10+c[i][j];
22     return ans;
23 }
24 
25 inline void bfs(int now){
26     int flag=0;
27     if(now==end)return;
28     q.push(now);q.push(end);
29     v[now]=1;v[end]=2;ans[now]=0;ans[end]=1;//ans[end]是要等于1的。 
30     while(!q.empty()){
31         int x=q.front();q.pop();
32         int k=x,px,py;
33         for(int i=9;i>=1;i--){
34             c[(i-1)/3+1][(i-1)%3+1]=k%10;
35             if(k%10==0)px=(i-1)/3+1,py=(i-1)%3+1;
36             k/=10;
37         }
38         for(int i=0;i<4;i++){
39             int dx=px+dr[i][0],dy=py+dr[i][1];
40             if(dx<1||dy<1||dx>3||dy>3)continue;
41             swap(c[dx][dy],c[px][py]);
42             int val=rebiu();
43             if(v[val]==v[x]){
44                 swap(c[dx][dy],c[px][py]);
45                 continue;
46             }//利用v顺便记忆化一下 
47             if(v[val]+v[x]==3){
48                 finl=ans[val]+ans[x];
49                 flag=1;break;
50             }//两个不同类型相遇 就是答案 
51             v[val]=v[x],ans[val]=ans[x]+1;
52             q.push(val);
53             swap(c[dx][dy],c[px][py]);
54         }
55         if(flag)break;
56     }
57 }
58 
59 
60 int main(){
61     scanf("%d",&n);
62     bfs(n);
63     cout<<finl;
64 }

A*

对于一个状态n, 经过n的答案 f* [ n ] = g* [ n ] + h* [ n ].

g*表示从出发点到n,h*表示从n到终点。

我们可以设置估价函数 f [ n ] = g [ n ] + h [ n ]

h是我们对未来的预估,且 h [ n ] <=h* [ n ]

这样可以保证求出最优解且h越大跑的越快。

对于一个题有多种估价方式,只要正确,大胆跑就完事了

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<string>
 4 #include<cstring>
 5 #include<map>
 6 using namespace std;
 7 int n;
 8 int brd=0;
 9 int c[4][4];
10 int finl[4][4]={{0,0,0,0},{0,1,2,3},{0,8,0,4},{0,7,6,5}};
11 int dr[4][2]={{0,1},{1,0},{-1,0},{0,-1}};//方向需要特殊排列一下 
12 int px,py;
13 int flag=0;
14 int k=1;
15 inline bool lok(int s){
16     int dif=0;
17     for(int i=1;i<=3;i++)
18         for(int j=1;j<=3;j++)
19             if(c[i][j]!=finl[i][j]){
20                 ++dif;
21                 if(dif+s>k)return false;
22             }
23     return true;
24 }/*估价函数可以更优。目前我知道最优的
25 应该是每个点与目标位置的曼哈顿距离(不算空格
26 因为空格位置在点移动过程中就改变了,不用另算) 
27 */ 
28 inline bool check(){
29     for(int i=1;i<=3;i++)
30         for(int j=1;j<=3;j++)
31         if(c[i][j]!=finl[i][j])return false;
32     return true;
33 }
34 
35 inline void dfs(int x,int y,int s,int pre){
36     if(s==k){
37         if(check())flag=1;
38         return;
39     }
40     if(flag)return;
41     for(int i=0;i<4;i++){
42         int dx=x+dr[i][0],dy=y+dr[i][1];
43         if(dx<1||dx>3||dy<1||dy>3||i+pre==3)continue;//i+pre是为了防止搜回去,mp存状态T了 
44         swap(c[dx][dy],c[x][y]);
45         if(lok(s))
46             dfs(dx,dy,s+1,i);
47         swap(c[dx][dy],c[x][y]);
48     }
49 }
50 
51 int main(){
52     scanf("%d",&n);
53     if(n==123804765){
54         printf("0");return 0;
55     }
56     for(int i=8;i>=0;i--){
57         c[i/3+1][(i%3)+1]=n%10;
58         if(n%10==0)px=i/3+1,py=(i%3)+1;
59         n/=10;
60     }
61     while(k++){
62         dfs(px,py,0,-1);
63         if(flag){
64             printf("%d",k);
65             return 0;
66         }
67     }
68 }

IDA*

在搜索深度不确定,目标状态不明确(有多解)时使用IDA* 。

ID==迭代加深。

原文地址:https://www.cnblogs.com/chiyo/p/11182186.html