八数码问题也称为九宫问题。(本想查查历史,结果发现居然没有词条= =,所谓的历史也就不了了之了)
在3×3的棋盘,摆有八个棋子,每个棋子上标有1至8的某一数字,不同棋子上标的数字不相同。棋盘上还有一个空格,与空格相邻的棋子可以移到空格中。
要求解决的问题是:
给出一个初始状态和一个目标状态,找出一种从初始转变成目标状态的移动棋子步数最少的移动步骤。
所谓问题的一个状态就是棋子在棋盘上的一种摆法。棋子移动后,状态就会发生改变。解八数码问题就是找出从初状态到目标状态所经过的一系列中间状态。
八数码问题一般使用搜索法来解,例如广度优先搜索法、深度优先搜索法、A*算法等。
用图像来表示就是:

POJ和HDU上面有两道相同的题目,不过测试数据是HDU比较强
先来看看POJ的解法
POJ上是单组数据即可,所以只需要输入一次,然后一次正向广度优先搜索-BFS(在线)就行了,至于每一个八数码的状态保存方法,用康托展开的方式保存是比较明智的(没有接触过的同学可以问问度娘,本质是使用十进制数保存八进制数),注意不要使用string类型,string的速度很慢,会TLE,直接使用字符串就行了
Code如下
1 //POJ1077-经典BFS-八数码
2 //单组数据(HDU1043多组数据较强)
3 //正向搜索(用string类型会TLE,所以改用字符串+父结点及方向标记)
4 //Time:313Ms Memory:9870K
5
6 #include<iostream>
7 #include<cstring>
8 #include<cstdio>
9 #include<queue>
10 using namespace std;
11
12 #define MAX 400000
13 #define AIM 46234 //123456780对应的康托Hash值
14
15 bool v[MAX];
16 char path[MAX]; //总路径
17 int len; //路径长
18
19 /*udlr*/
20 char *dir = "udlr"; //正向搜索
21 int mov[4][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
22
23 //八数码状态结构体
24 struct Node{
25 int s[9];
26 int loc; //空位
27 int status; //Hash值-排列值
28 int fa; //记录父状态
29 char d; //到此状态的移动方向
30 }n[MAX];
31
32 int fac[] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };
33 //对序列ncur.s[9]康托展开-返回Hash值
34 int cantor(int s[9])
35 {
36 int sum = 0;
37 for (int i = 0; i < 9; i++)
38 {
39 int num = 0; //逆序数计数器
40 for (int j = i + 1; j < 9; j++)
41 if (s[j] < s[i])
42 num++;
43 sum += num*fac[9 - i - 1];
44 }
45 return sum + 1;
46 }
47
48 /*反向记录路径*/
49 void count_path(Node end)
50 {
51 int f = end.fa;
52 len = 0;
53 path[len++] = end.d;
54 while (f)
55 {
56 path[len++] = n[f].d;
57 f = n[f].fa;
58 }
59 }
60
61 bool BFS()
62 {
63 memset(v, 0, sizeof(v));
64 Node next; //下一临时状态
65 int head = 0, tail = 0;
66
67 n[0].status = cantor(n[0].s);
68 v[n[0].status] = true;
69 while (head <= tail) //模拟队列
70 {
71 if (n[head].status == AIM) //达到AIM
72 {
73 count_path(n[head]);
74 return true;
75 }
76 //计算二维坐标
77 int x = n[head].loc / 3;
78 int y = n[head].loc % 3;
79 for (int i = 0; i < 4; i++)
80 {
81 int tx = x + mov[i][0];
82 int ty = y + mov[i][1];
83 if (tx < 0 || tx>2 || ty < 0 || ty>2)continue;
84 //新状态更新
85 next = n[head];
86 next.loc = tx * 3 + ty; //计算新空位
87 next.s[n[head].loc] = next.s[next.loc]; //原空位替换
88 next.s[next.loc] = 0; //新空位
89 next.fa = head;
90 next.d = dir[i];
91 next.status = cantor(next.s);
92 //判重并入队
93 if (!v[next.status])
94 {
95 v[next.status] = true;
96 if (next.status == AIM)
97 {
98 count_path(next);
99 return true;
100 }
101 n[++tail] = next;
102 }
103 }
104 head++;
105 }
106 return false;
107 }
108
109 int main()
110 {
111 /*input*/
112 char ch[3];
113 for (int i = 0; i < 9; i++)
114 {
115 scanf("%s",ch);
116 if (!strcmp(ch,"x"))
117 {
118 n[0].s[i] = 0;
119 n[0].loc = i;
120 }
121 else n[0].s[i] = ch[0] - '0';
122 }
123
124 /*output*/
125 if (BFS())
126 { //反向输出路径
127 for (int i = len - 1; i>=0; i--)
128 printf("%c", path[i]);
129 printf("
");
130 }
131 else
132 printf("unsolvable
");
133 return 0;
134 }
HDU解法如下:
HDU上是多组数据,因此 在线BFS 的方法会重复计算很多次,今天重点说说广度优先搜索,因此我们用 离线BFS 方法(也就是打表的思想)计算出所有路径并保存,
之后输入状态只需要计算出其康托逆展开的Hash值,就可以直接输出对应的路径了
1 //HDU1043-经典BFS-八数码
2 //多组数据-需要计算全部路径后直接输出(POJ1077数据较弱)
3 //反向搜索+打表(离线)
4 //Time:109Ms Memory:25412K
5
6 #include<iostream>
7 #include<cstring>
8 #include<cstdio>
9 #include<queue>
10 using namespace std;
11
12 #define MAX 400000
13 #define AIM 46234 //123456780对应的康托Hash值
14
15 bool v[MAX];
16 char path[MAX][40]; //总路径
17 int len; //路径长
18
19 /*udlr*/
20 char *dir = "durl"; //反向搜索
21 int mov[4][2] = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
22
23 //八数码状态结构体
24 struct Node{
25 int s[9];
26 int loc; //空位
27 int status; //Hash值-排列值
28 int fa; //记录父状态
29 char d; //到此状态的移动方向
30 }n[MAX];
31
32 int fac[10] = { 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 };
33 //康托逆展开-返回Hash值
34 int Inverse_cantor(int s[9])
35 {
36 int sum = 0;
37 for (int i = 0; i < 9; i++)
38 {
39 int num = 0; //逆序数计数器
40 for (int j = i + 1; j < 9; j++)
41 if (s[j] < s[i])
42 num++;
43 sum += num*fac[9 - i - 1];
44 }
45 return sum + 1;
46 }
47
48 /*反向记录路径*/
49 void count_path(Node end)
50 {
51 int status = end.status;
52 int f = end.fa;
53 len = 0;
54 path[status][len++] = end.d;
55 while (f)
56 {
57 path[status][len++] = n[f].d;//方向记录
58 f = n[f].fa; //查找父结点
59 }
60 }
61
62 void BFS()
63 {
64 memset(v, 0, sizeof(v));
65 Node next; //下一临时状态
66 int head = 0, tail = 0;
67 /*目标状态*/
68 for (int i = 0; i < 8; i++)
69 n[0].s[i] = i + 1;
70 n[0].s[8] = 0;
71 n[0].loc = 8;
72 n[0].status = AIM;
73 v[AIM] = true;
74 while (head <= tail) //模拟队列
75 {
76 //计算二维坐标
77 int x = n[head].loc / 3;
78 int y = n[head].loc % 3;
79 for (int i = 0; i < 4; i++) //遍历四方向
80 {
81 int tx = x + mov[i][0];
82 int ty = y + mov[i][1];
83 if (tx < 0 || tx>2 || ty < 0 || ty>2)continue;
84 //新状态更新
85 next = n[head];
86 next.loc = tx * 3 + ty; //计算新空位
87 next.s[n[head].loc] = next.s[next.loc]; //原空位替换
88 next.s[next.loc] = 0; //新空位
89 next.fa = head;
90 next.d = dir[i];
91 next.status = Inverse_cantor(next.s);
92 //判重并入队
93 if (!v[next.status])
94 {
95 v[next.status] = true;
96 count_path(next);
97 n[++tail] = next;
98 }
99 }
100 head++;
101 }
102 }
103
104 int main()
105 {
106 /* BFS-打表 */
107 BFS();
108 /*input*/
109 char ch[3];
110 Node cur;
111 while (scanf("%s", ch) != EOF)
112 {
113 if (!strcmp(ch, "x"))
114 cur.s[0] = 0, cur.loc = 0;
115 else cur.s[0] = ch[0] - '0';
116 for (int i = 1; i < 9; i++)
117 {
118 scanf("%s", ch);
119 if (!strcmp(ch, "x"))
120 cur.s[i] = 0, cur.loc = i;
121 else cur.s[i] = ch[0] - '0';
122 }
123 cur.status = Inverse_cantor(cur.s);
124
125 /*output*/
126 if (v[cur.status])
127 printf("%s
", path[cur.status]);
128 else
129 printf("unsolvable
");
130 }
131 return 0;
132 }