题目描述;数独游戏的内核模拟
八数码问题;
编号为1到8的8个正方形滑块被摆成3行3列;(有一个格子留空);
每次可以把与空格相邻的滑块(有公共边才算相邻)移到空格中;
而它原来的位置就成为了新的空格;给定初始局面和目标局面(用0表示空格);
你的任务死计算出最少的移动步数;和移动过程;如果无法到达目标局面,则输出-1;
2 | 6 | 4 |
1 | 3 | 7 |
5 | 8 |
8 | 1 | 2 |
7 | 3 | 6 |
4 | 2 |
这个问题也是要分步骤实现的,自然也涉及到状态的问题,和我的前一篇博客一样,使用的是广度优先遍历
这样可以找到最少的步数;众所周知的是BFS算法之于树和之于图是不太一样的,
因为树一旦往下走便不必担心访问到已经访问过的结点,而图呢由于有环的存在,所以会回到之前访问过的状态;
这样就需要我们写一个判断是否重复的方法,上一篇博客里面的情况比较简单,因为可以直接用状态做下标进行随机访问;
这里的情况就不一样了;我们先用c语言的遍历来实现一下,代码如下;
使用头文件string.h因为这里要用到memcmp和memcpy函数和memset;
这两个函数比我们写的循环效率要高
为什么数组选择这么大呢?排列数9!;
这里我把所有的变量都声明成全局变量的,在c语言编程方面不是一个好的编程style;
但是在ACM这种对数据量要求极大的,情况下,数组申请在全局变量比在栈中申请要好;
另外也方便了函数之间数据的交互,不需要多写什么参数了,
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h 4 #define MAX 1000000 5 typedef struct Node 6 { 7 int state[9];//存放本状态各个位置的数码值; 8 int fa;//记录父节点的下标; 9 int deepth; 10 int last_x; 11 int last_y; 12 13 }Node; 14 Node q[MAX];//构成状态数组; 15 typedef struct Vis 16 { 17 int sequeue[9];//记录9个数码的位置; 18 int visited; 19 20 }Vis; 21 Vis vis[MAX];//判断是否访问过; 22 int vis_cur=0;//记录vis数组的当前索引位置; 23 const int dx[4]={-1,1,0,0};//左右上下; 24 const int dy[4]={0,0,-1,1}; 25 //bool has_vis(const int * state_to_decide); 26 int has_vis=0; 27 int bfs();//广度优先找到目标状态; 28 void print_path(int founded);//根据fa成员,通过递归技术实现状态依次输出; 29 Node destination;//存储目标状态; 30 int i,j,k; 31 int main() 32 { 33 /*首先输入起始状态和目标状态*/ 34 memset(q,0,sizeof q); 35 for(i=0;i<9;i++) 36 { 37 scanf("%d",&(q[0].state[i])); 38 } 39 for(i=0;i<9;i++) 40 { 41 scanf("%d",&destination.state[i]); 42 } 43 memset(vis,0,sizeof vis); 44 45 /*然后进行搜索并输出*/ 46 int founded=bfs(); 47 if(founded) 48 print_path(founded); 49 else 50 printf("-1\n"); 51 system("pause"); 52 return 0; 53 } 54 int bfs() 55 { 56 memcpy(vis[0].sequeue,q[0].state,sizeof q[0].state); 57 int front=0,rear=1;//用来模拟队列的先进先出,达到广度优先的目的; 58 vis[0].visited=1;//第一个结点访问; 59 vis_cur++; 60 while (front<rear) 61 { 62 Node &first=q[front]; 63 if (memcmp(first.state,destination.state,sizeof destination.state)==0) 64 {//找到了目标状态; 65 return front; 66 } 67 for(i=0;i<9;i++) 68 if (!first.state[i]) 69 {//找到空格处; 70 break; 71 } 72 73 for(j=0;j<4;j++) 74 {//向四个方向进行转换; 75 Node &new_Node=q[rear]; 76 memcpy(new_Node.state,first.state,sizeof first.state); 77 int new_x=i%3+1+dx[j]; 78 int new_y=i/3+1+dy[j]; 79 80 if (new_x>0&&new_y>0&&new_x<4&&new_y<4) 81 { 82 //位置合法 83 new_Node.state[i]=new_Node.state[i+dx[j]+3*dy[j]];//空格位置; 84 new_Node.state[i+dx[j]+3*dy[j]]=0;//新的状态形成了; 85 has_vis=0; 86 for(k=0;k<vis_cur;k++)//这里不用i,j因为i,j是全局变量且本函数是在循环里面的; 87 { 88 if((memcmp(vis[k].sequeue,new_Node.state,sizeof vis[k].sequeue))==0) 89 { 90 has_vis=1; 91 break; 92 } 93 } 94 if(!has_vis)//没有被访问; 95 { 96 new_Node.fa=front; 97 new_Node.deepth=first.deepth+1; 98 new_Node.last_x=dx[j]; 99 new_Node.last_y=dy[j]; 100 memcpy(vis[vis_cur].sequeue,new_Node.state,sizeof new_Node.state); 101 vis[vis_cur].visited=1; 102 vis_cur++; 103 rear++; 104 }//if 105 106 }//if 107 }//for 108 front++; 109 printf("%d %d\n",front,q[front].deepth); 110 }//while 111 return 0; 112 } 113 void print_path(int founded) 114 { 115 if(q[founded].fa!=founded) 116 { 117 print_path(q[founded].fa); 118 } 119 for(i=0;i<3;i++) 120 { 121 for(j=0;j<3;j++) 122 { 123 printf("%d ",q[founded].state[3*i+j]); 124 } 125 printf("\n"); 126 } 127 printf("\n"); 128 } 129 /*bool has_vis(const int * state_to_decide) 130 { 131 for(k=0;k<vis_cur;k++)//这里不用i,j因为i,j是全局变量且本函数是在循环里面的; 132 { 133 if(memcmp(vis[k].sequeue,state_to_decide,sizeof vis[k].sequeue)==0) 134 return true; 135 } 136 return false; 137 }*/
最后如你所见,我们的这个程序读取输入之后似乎没什么反应,其实它还是在工作的,我也是等了30多分钟之后才看到了曙光;
显然这种遍历判断是否访问过的想法不仅没有提高效率,反而增加了上万次的无辜操作;
那么该怎么提高效率呢,在下一个博客里面,我会用c++的stl库试一下;