本题知识点和基本代码来自《算法竞赛 入门到进阶》(作者:罗勇军 郭卫斌)
如有问题欢迎巨巨们提出
题意:八数码问题是在一个3*3的棋盘上放置编号为1~8的方块,其中有一块为控制,与空格相邻的数字方块可以移动到空格里。我们要求指定初始棋盘和目标棋盘,计算出最少移动次数,同时要输出数码的移动数列。初始棋盘样例已给出,目标棋盘为“1 2 3 4 5 6 7 8 x”
输入:
2 3 4 1 5 x 7 6 8
输出:
ullddrurdllurdruldr
详解:
八数码是经典的BFS问题,可以用“康托展开”判重。那什么事康托展开呢?
康托展开是一种特殊的哈希函数,针对八数码问题,康托展开完成了如表所示的工作。
状态 | 012345678 | 012345687 | 0123456768 | ...... | 876543210 |
Cantor | 0 | 1 | 2 | ...... | 362880-1 |
函数Cantor()实现的功能是:输入一个排序,即第一行的某个排序,计算它的Cantor值,即第二行的数。Cantor的时间复杂度为O(n*n),n是集合中元素的个数,利用CANTOR展开可以实现八数码的快速判重。
距离康托展开的实现原理:
例:判断2143是{1,2,3,4}的全排列中第几大的数。
计算排在2143前面的排列数目,可以转换成以下排列的和:
(1)首位小于2的所有排序,比2小的只有一个数,后面三个数的排序有3!个。
(2)首位为2,第2位小于1的所有排序,无,写成0*2!=0.
(3)前两位为21,第三位小于4的数,即2134,写成1*1!=1.
(4)前三位为214,第四位小于3的数,无,即0*0!=1.
sum=8.即2143是第八大的数。
把一个集合产生的全排列按字典序排序,第X个排序的计算公式如下:
X=a[n]*(n-1)!+a[n-1]*(n-2)!+....+a[i]*(i-1)!+...+a[2]*1!+a[1]*0![1].其中,a[i]为当前未出现的元素排在第几个。(从0开始)0<=a[i]<i.
康托展开的基础代码:
int visited[maxn] = { 0 }; //判断改装备是否被访问过 long int factory[] = { 1,1,2,6,24,120,720,5040,40320,362880 };//阶乘数 bool Cantor(int str[], int n) { long result = 0; for (int i = 0; i < n; i++) { int counted = 0; for (int j = i + 1; j < n; j++) { if (str[i] > str[j]) ++counted; } result += counted * factory[n - i - 1]; } if (!visited[result]) { visited[result] = 1; return 1; } else return 0; }
这道题看了很多博客,存步骤的答案方式很多,我是在结构体里设置string,然后在bfs过程中逐步保存步骤,最后输出达到最终状态的答案。看代码应该能理解。还有保存图的时候要注意,样例里空格不止一个,所以灵活点保存。我最后时间跑出来是750ms,比较慢,可用其他搜索方法优化。
AC代码:
1 #pragma comment(linker, "/STACK:102400000,102400000") 2 #pragma GCC optimize(2) 3 #include<iostream> 4 #include<algorithm> 5 #include<cstdio> 6 #include<cstring> 7 #include<cmath> 8 #include<queue> 9 #include<set> 10 #include<string> 11 #include<map> 12 #include<vector> 13 #include<ctime> 14 #include<stack> 15 using namespace std; 16 #define mm(a,b) memset(a,b,sizeof(a)) 17 typedef long long ll; 18 const int maxn = 362880; 19 const int inf = 0x3f3f3f3f; 20 21 struct node 22 { 23 int state[9]; 24 int dis; 25 string ans; 26 }; 27 28 int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} }; 29 char turn[4] = { 'l','u','r','d' }; 30 int visited[maxn] = { 0 }; 31 int start[9]; 32 int goal[9] = {1,2,3,4,5,6,7,8,0}; 33 34 long int factory[] = { 1,1,2,6,24,120,720,5040,40320,362880 }; 35 36 bool Cantor(int str[], int n) 37 { 38 long result = 0; 39 for (int i = 0; i < n; i++) 40 { 41 int counted = 0; 42 for (int j = i + 1; j < n; j++) 43 { 44 if (str[i] > str[j]) 45 ++counted; 46 } 47 result += counted * factory[n - i - 1]; 48 } 49 if (!visited[result]) 50 { 51 visited[result] = 1; 52 return 1; 53 } 54 else return 0; 55 } 56 57 bool check(int x, int y) 58 { 59 if (x >= 0 && x < 3 && y >= 0 && y < 3) 60 return true; 61 else return false; 62 } 63 64 queue<char>ans; 65 66 int bfs() 67 { 68 node head; 69 memcpy(head.state, start, sizeof(head.state)); 70 head.dis = 0; 71 queue<node>q; 72 Cantor(head.state, 9); 73 q.push(head); 74 while (!q.empty()) 75 { 76 head = q.front(); 77 q.pop(); 78 int z; 79 for (z = 0; z < 9; z++) 80 { 81 if (head.state[z] == 0) 82 break; 83 } 84 int x = z % 3; 85 int y = z / 3; 86 for (int i = 0; i < 4; i++) 87 { 88 int newx = x + dir[i][0]; 89 int newy = y + dir[i][1]; 90 int nz = newx + 3 * newy; 91 if (check(newx, newy)) 92 { 93 node newnode = head; 94 swap(newnode.state[z], newnode.state[nz]); //0的交换 95 newnode.dis++; 96 if (memcmp(newnode.state, goal, sizeof(goal)) == 0) 97 { 98 newnode.ans = newnode.ans + turn[i]; 99 cout << newnode.ans << endl; 100 return newnode.dis; 101 } 102 if (Cantor(newnode.state, 9)) 103 { 104 newnode.ans = head.ans + turn[i]; 105 q.push(newnode); 106 } 107 } 108 } 109 } 110 return -1; 111 } 112 113 int main() 114 { 115 char s[100]; 116 cin.getline(s, 100); 117 int pos = 0; 118 for (int i = 0; s[i] != '