本题知识点和基本代码来自《算法竞赛 入门到进阶》(作者:罗勇军 郭卫斌)
如有问题欢迎巨巨们提出
题意:八数码问题是在一个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代码:
#pragma comment(linker, "/STACK:102400000,102400000") #pragma GCC optimize(2) #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cmath> #include<queue> #include<set> #include<string> #include<map> #include<vector> #include<ctime> #include<stack> using namespace std; #define mm(a,b) memset(a,b,sizeof(a)) typedef long long ll; const int maxn = 362880; const int inf = 0x3f3f3f3f; struct node { int state[9]; int dis; string ans; }; int dir[4][2] = { {-1,0},{0,-1},{1,0},{0,1} }; char turn[4] = { 'l','u','r','d' }; int visited[maxn] = { 0 }; int start[9]; int goal[9] = {1,2,3,4,5,6,7,8,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; } bool check(int x, int y) { if (x >= 0 && x < 3 && y >= 0 && y < 3) return true; else return false; } queue<char>ans; int bfs() { node head; memcpy(head.state, start, sizeof(head.state)); head.dis = 0; queue<node>q; Cantor(head.state, 9); q.push(head); while (!q.empty()) { head = q.front(); q.pop(); int z; for (z = 0; z < 9; z++) { if (head.state[z] == 0) break; } int x = z % 3; int y = z / 3; for (int i = 0; i < 4; i++) { int newx = x + dir[i][0]; int newy = y + dir[i][1]; int nz = newx + 3 * newy; if (check(newx, newy)) { node newnode = head; swap(newnode.state[z], newnode.state[nz]); //0的交换 newnode.dis++; if (memcmp(newnode.state, goal, sizeof(goal)) == 0) { newnode.ans = newnode.ans + turn[i]; cout << newnode.ans << endl; return newnode.dis; } if (Cantor(newnode.state, 9)) { newnode.ans = head.ans + turn[i]; q.push(newnode); } } } } return -1; } int main() { char s[100]; cin.getline(s, 100); int pos = 0; for (int i = 0; s[i] != '\0'; i++) { if (s[i] == ' ') continue; else if (s[i] == 'x') start[pos++] = 0; else start[pos++] = s[i] - '0'; } int num = bfs(); //printf("%d\n", num); if (num == -1) printf("unsolvable\n"); return 0; }