BFS將近兩年沒練過題了,今天重新回憶下以前刷的藍橋杯題:九宮格重排
樣例輸入
123456780 //初始狀態 123046758 //終點狀態
樣例輸出
3 //最短步數
樣例輸入
135246780 //初始狀態
467581230 //終點狀態
樣例輸出
22 //最短步數
思路
以下圖為例,空格0可以走上下左右4步 :
當前0位於的位置是(1,1),所以可以移動到(1-1,1),(1,1+1),(1+1,1),(1,1-1)
然后通過康拓展開來校驗,移動的位置是否走過,是否已到終點,是否越界。
康拓展開
表示的是當前排列在n個不同元素的全排列中的名次.
公式為:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[2]*1!+a[1]*0! //a[i]為當前未出現的元素中是排在第幾個(從0開始 )
比如,3124 是1~4全排列的第幾個?(從0開始,比如1234是1~4全排列第0個)
X=2*3!+ 0*2!+ 0*1!+ 0*0!=12
- 第一位,3在未出現的元素(1,2,3,4)中,只有2個數(1和2)比它小,位2~4全排列有3!個,所以=2*3!
- 第一位3,第二位1, 1在未出現的元素(1,2,4)中,只有0個數比它小,位3~4全排列有2!個,所以=0*2!
- 第一位3,第二位1,第三位2, 2在未出現的元素(2,4)中,只有0個數比它小,所以=0*1!
- 第一位3,第二位1,第三位2, 第四位4,4在未出現的元素(4)中,只有0個數比它小,所以=0*0!
康拓展開代碼(s[4]={3,1,2,4},n=4)
long int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};//階乘表 int congtuo(int s[],int n) { int i,j,temp,num; num=0; for(i=0;i<n;i++) { temp=0; for(int j=i+1;j<n;j++) if(s[j]<s[i]) //判斷幾個數小於它 temp++; num+=fac[n-i-1]*temp; } return num; }
九宮格代碼如下,純手打勿噴:
#include <stdio.h> #include <string.h> long int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};//階乘表 unsigned char step[363880]={0}; // 9!=362880,所以數組要大點 char method[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; //4種移動方法 struct node_t{ unsigned char data[9]; }; struct node_t save_step[363880]; //保存移動數據,最多有 9!個移動數據 unsigned char end_p[9]; //終點位置 int congtuo(unsigned char s[],int n) { int i,j,temp,num; num=0; for(i=0;i<n;i++) { temp=0; for(int j=i+1;j<n;j++) if(s[j]<s[i]) //判斷幾個數小於它 temp++; num+=fac[n-i-1]*temp; } return num; } /*處理0函數 *返回值0:交換的位置非法,或被走過 *返回值1:沒被走過 *返回值2:找到終點 */ int handler_zero(int i,unsigned char data[]) { int x,y,temp,num; for(int j=0;j<9;j++) if(data[j]==0) { /*[x,y]:需要交換的位置*/ x=j%3+method[i][0]; y=j/3+method[i][1]; /*判斷交換位置是否越界*/ if(x<0||x>2) return 0; else if(y<0||y>2) return 0; /*判斷交換后的位置是否已被走過*/ data[j]= data[x+y*3]; data[x+y*3]=0; num=congtuo(data,9); if(step[num]==1) //已被走過 { return 0; } if(memcmp(end_p,data,9)==0) //找到終點 { return 2; } step[num]=1; return 1; } } int start=0,end=0; int bfs(void) { int x,y; int next_end=end; //存放下個位置的標志位 int err; unsigned char temp[9];
for(;start<=end;start++) { for(int i=0;i<4;i++) //4種走法 { memcpy(temp,(unsigned char *)save_step[start].data,9); err=handler_zero(i,temp); //處理數據里的0 if(err) //保存數據 { memcpy((unsigned char *)save_step[++next_end].data,temp,9); if(err==2) //找到終點 return 1; } } } start=end+1; end=next_end; return (1+bfs()); } int main() { int step_num=0; //步數 unsigned char s[9]; printf("please enter start point:\r\n"); for(int i=0;i<9;i++) { scanf("%1d",&save_step[0].data[i]); } printf("please enter end point:\r\n"); for(int i=0;i<9;i++) { scanf("%1d",&end_p[i]); } step_num=bfs(); printf("%d\n",step_num); }
如果想看走的步數具體內容,該怎么辦?
其實很簡單,因為具體內容都保存在save_step[]里,我們只需要在node_t結構體里多定義一個標志位step_p,來指向上次步數的位置在哪.
代碼如下:
#include <stdio.h> #include <string.h> long int fac[10]={1,1,2,6,24,120,720,5040,40320,362880};//階乘表 unsigned char step[363880]={0}; // 9!=362880,所以數組要大點 char method[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; //4種移動方法 struct node_t{ unsigned char data[9]; unsigned int step_p; }; struct node_t save_step[363880]; //保存移動數據,最多有 9!個移動數據 unsigned char end_p[9]; //終點坐標 unsigned int end_index; //記錄終點位置 int congtuo(unsigned char s[],int n) { int i,j,temp,num; num=0; for(i=0;i<n;i++) { temp=0; for(int j=i+1;j<n;j++) if(s[j]<s[i]) //判斷幾個數小於它 temp++; num+=fac[n-i-1]*temp; } return num; } /*處理0函數 *返回值0:交換的位置非法,或被走過 *返回值1:沒被走過 *返回值2:找到終點 */ int handler_zero(int i,unsigned char data[]) { int x,y,temp,num; for(int j=0;j<9;j++) if(data[j]==0) { /*[x,y]:需要交換的位置*/ x=j%3+method[i][0]; y=j/3+method[i][1]; /*判斷交換位置是否越界*/ if(x<0||x>2) return 0; else if(y<0||y>2) return 0; /*判斷交換后的位置是否已被走過*/ data[j]= data[x+y*3]; data[x+y*3]=0; num=congtuo(data,9); if(step[num]==1) //已被走過 { return 0; } if(memcmp(end_p,data,9)==0) //找到終點 { return 2; } step[num]=1; return 1; } } int start=0,end=0; int bfs(void) { int x,y; int next_end=end; //存放下次bfs的end標志位 int err; unsigned char temp[9]; for(;start<=end;start++) { for(int i=0;i<4;i++) //4種走法 { memcpy(temp,(unsigned char *)save_step[start].data,9); err=handler_zero(i,temp); //處理數據里的0 if(err) //保存數據 { memcpy((unsigned char *)save_step[++next_end].data,temp,9); save_step[next_end].step_p=start; //記錄上次步數位置. if(err==2) //找到終點 { end_index=next_end; //記錄終點步數位置. return 1; } } } } start=end+1; end=next_end; return (1+bfs()); } int main() { int step_num=0; //步數 unsigned char s[9]; printf("please enter start point:\r\n"); for(int i=0;i<9;i++) { scanf("%1d",&save_step[0].data[i]); } printf("please enter end point:\r\n"); for(int i=0;i<9;i++) { scanf("%1d",&end_p[i]); } step_num=bfs(); printf("%d\n",step_num); /**打印步數具體內容**/ for(;step_num>=0;step_num--) { printf("**step %d********\n",step_num); for(int i=0;i<3;i++) { printf("%d %d %d\n",save_step[end_index].data[i*3], save_step[end_index].data[i*3+1], save_step[end_index].data[i*3+2]); } end_index=save_step[end_index].step_p; printf("\n"); } }
打印結果