一. 题意:
八数码问题,输入一个3*3的棋盘。其中包含一个x和1到8,用x跟上下左右交换。求到满足状态12345678x时x要怎么移动。
二. 解题方法:
我是用康托展开和逆展开,把棋盘9!种不同的状态映射到0到9!-1个整数,然后用整数代表不同的状态,(暂且理解为一个整数代表一个状态,这好像就是传说中的hash算法吧)比如说123456789是一个状态,987654321是另外一个状态。然后一个状态一个状态搜索。
定义数组visited[]标记不同状态是否被访问过,我直接把x那个位置替换为9,然后从9开始搜索(BFS),定义一个结构体Path,用来存放路径。里面的direction用来表示前一个状态是从那个方向来到当前状态的,pre用来表示当前状态是从前面的哪个状态过来的。当遇见所代表的整数等于0,即所代表的棋盘为123456789,就退出。搜索完还没遇到等于0的情况就是没有解。(透露一个秘密:测试数据根本没有没解这种情况,因为一开始没仔细看题,直接猜样例然后就AC了哈哈哈哈)
三. 预备知识:
1. 组合数学的知识:
康托展开:对于一个全排列,将每一个排列又小到大排序,他们的排列顺序代表它们。给出它的排列,求出它第几大。举个例子比较好理解:
比如对于3个数123,它们的排列有123,132,213,231,312,321共6种,用0,1,2,3,4,5分别代表它们。给出一个312,让你求出4。怎么求呢。有点高中数学知识就够了比3小的数有2个。第一位可以是1, 2。第二位为2!然后看1,比1小的有0个。然后如果后面有继续加,只看后面的子序列,不要往回找。
康托逆展开:给数字求排列。从最高位开始,取整得出有几个数比它当前位数小,取余然后再取整,直接的逆运算,仔细想想就明白了。
四. 本来想写一篇长长的,可以解释得比较清楚的,毕竟这题想了好久,也学了好多啊感觉,然后好像也说不出什么了。毕竟我只用了单向BFS,就完了。只能说,详细看代码:
#include <iostream> #include <algorithm> #include <cstdio> #include <queue> using namespace std; const int MAXSIZE = 362880; //The list of factorial: int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; //Mark whether this state have been tried: bool visited[MAXSIZE]; //The direction of moving: int movei[] = {-1, 1, 0, 0}; int movej[] = {0, 0, -1, 1}; char direction[] = {'u', 'd', 'l', 'r'}; struct Path { //how to move to current state(By up, down, left or right): char direction; //Remember how to reach current(from one state to another state): int pre; } path[MAXSIZE]; //Every state: struct state { int i, j; char grid[3][3]; }; //Whether it can be solve: bool flag; void order(char str[][3], int &ans) { int i, j, k = 0, numOfMin; char temp[9]; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++){ temp[k] = str[i][j]; k++; } ans = 0; for(i = 0; i < 8; i++){ numOfMin = 0; for(j = i + 1; j < 9; j++){ if(temp[i] > temp[j]){ numOfMin++; } } ans += fac[8 - i]*numOfMin; } } void getState(int num, state &ans) { int i, j, k = 0, numOfMin; char temp[9]; for(i = 8; i >= 0; i--){ numOfMin = num/fac[i]; temp[8-i] = '1' + numOfMin; num = num%fac[i]; } for(i = 0; i < 3; i++) for(j = 0; j < 3; j++){ ans.grid[i][j] = temp[k]; k++; if('9' == ans.grid[i][j]){ ans.i = i; ans.j = j; } } } void bfs(state start) { queue <state> que; state now, next; int i, j, dir, nextOrder, curOrder; que.push(start); order(start.grid, curOrder); path[curOrder].pre = -1; visited[curOrder] = true; flag = true; while(!que.empty()){ now = que.front(); que.pop(); order(now.grid, curOrder); for(dir = 0; dir < 4; dir++){ i = now.i + movei[dir]; j = now.j + movej[dir]; if(i >= 0 && i < 3 && j >= 0 && j < 3){ next = now; swap(next.grid[i][j], next.grid[next.i][next.j]); order(next.grid, nextOrder); if(!visited[nextOrder]){ visited[nextOrder] = true; path[nextOrder].direction = direction[dir]; next.i = i, next.j = j; path[nextOrder].pre = curOrder; if(0 == nextOrder) return; que.push(next); } } } } flag = false; } void printPath() { char way[1024]; int k = 0, curOrder = 0, preOrder; preOrder = path[0].pre; for(k = 0; path[curOrder].pre != -1; k++){ preOrder = path[curOrder].pre; way[k] = path[curOrder].direction; curOrder = preOrder; } for(k--; k >= 0; k--){ cout<<way[k]; } cout<<endl; } int main() { //freopen("in.txt", "r", stdin); int i, j; state start; for(i = 0; i < 3; i++) for(j = 0; j < 3; j++){ cin>>start.grid[i][j]; if('x' == start.grid[i][j]){ start.i = i; start.j = j; start.grid[i][j] = '9'; } } bfs(start); if(flag) printPath(); else cout<<"unsolvable\n"; }
G++250ms。
回头来看,康托逆展开写错了,仔细看下代码,根本没用过!- -。