如何测试算法
首先要把所有可能的序列都输出来,其实也就是生成全排列了。这里直接给出代码。
用以下代码生成12345678x, 至x87654321
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
#define MAX 100
using namespace std;
int rec[MAX];
char dst[MAX];
const int frac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
char *GetString(const int v, const int MAXN = 9) //给定序列值,生成一个字符串,这个值应该是从0~(9! - 1)
{
int idx = 1;
int value = v;
memset(rec, 0, sizeof(rec));
memset(dst, 0, sizeof(dst));
do
{
rec[idx] = value % (idx + 1);
value /= (idx + 1);
idx++;
} while (value);
for (int now_value = MAXN, iter = MAXN - 1; now_value > 0; --now_value, --iter)
{
const int pos = rec[iter];
int cont = -1;
for (int j = MAXN - 1; j >= 0; --j)
{
if (0 == dst[j])
{
++cont;
if (pos == cont)
{
dst[j] = now_value;
break;
}
}
}
}
return dst;
}
int GetNumber(const char* dst, const int MAXN = 9) //从相应的字符串,得到序列值。
{
int iNumber = 0;
for (int i = 0; i < MAXN - 1; ++i)
{
int temp = 0;
for (int j = i + 1; j < MAXN; ++j)
{
if (dst[j] < dst[i])
{
++temp;
}
}
iNumber += frac[dst[i] - 1] * temp;
}
return iNumber;
}
char _str[100];
int main(void)
{
for (int i = 0; i < frac[9]; ++i)
{
char *str = GetString(i), *p = str;
while (*str)
{
*str += '0';
if (*str == '9') *str = 'x';
++str;
}
printf("%s\n", p);
}
return 0;
}
如何验证正确性
当你的算法生成处理结果之后,那么比如得到字符串:
输入:
23415x768
输出:
ullddrurdllurdruldr如果能够按照这个序列进行移动,能够移动至0序号的状态,那么就是对的了。
下面给出移动的代码:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
using namespace std;
char map[3][3];
char str[100];
char oper[100];
void print()
{
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
printf("%c", map[i][j]);
}
printf("\n");
}
}
int main(void)
{
int iter = -1, sx = 0, sy = 0;
while (1)
{
gets(str);
gets(oper);
for (char *p = str; *p; ++p)
{
if (!('0' <= *p && *p <= '9') && 'x' != *p && 'X' != *p) continue;
++iter;
map[iter/3][iter%3] = *p;
if (*p == 'x')
{
sx = iter / 3; sy = iter % 3;
}
}
print();
printf("center = %d, %d\n", sx, sy);
for (char *p = oper; *p; ++p)
{
switch(*p)
{
case 'l':
std::swap(map[sx][sy], map[sx][sy-1]);
--sy;
break;
case 'r':
std::swap(map[sx][sy], map[sx][sy+1]);
++sy;
break;
case 'u':
std::swap(map[sx][sy], map[sx-1][sy]);
--sx;
break;
case 'd':
std::swap(map[sx][sy], map[sx+1][sy]);
++sx;
break;
}
}
print();
}
return 0;
}
可以根据需要进行更改。
设置Hash表
一般而言,给定的十进制数有987654321,如果直接使用一个数组来记录状态,会超出内存的,需要压缩状态。
在这里是把x当成9来处理。
由于每种排列与序号对应,可以把这个逆序数来做为hash函数。
int arr[20];
int GetNumber(const int value)
{ const int frac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
int iNumber = 0, temp = 0, i, j, xi, *t = (int *)p;
for (i = 0; i < 9; ++i, ++t)
arr[8 - i] = (value % *(t+1)) / *t;
for (i = 0; i < 8; ++i)
{
for (xi = arr[i], j = i + 1; j < 9; ++j)
{
(arr[j] < xi) ? iNumber += frac[xi - 1] : 0;
}
}
return iNumber;
}
那么可以构建hash表如下:
#define BITSPERWORD 32
#define SHIFT 5
#define MASK 0x1F
#define N 362890
int a[(N>>SHIFT) + 10];
inline void set(int i) { a[i>>SHIFT] |= (1<<(i & MASK)); }
inline void clr(int i) { a[i>>SHIFT] &= ~(1<<(i & MASK)); }
inline int test(int i){ return a[i>>SHIFT] & (1<<(i & MASK)); }
如何实现位变换
在这里,很多人都是用的是char 数组来实现位变换,我这里用的是直接用十进制来进行操作。
位数编码如下:
123456789 = 值
012345678 = 位数
const int _p10[] =
{
1, 1, 10, 100, 1000,
10000, 100000, 1000000,
10000000, 100000000, 1000000000, 1000000000
};
const int *p = _p10 + 1;
static void _swap(int *v, int x, int y)
{
int i = 8 - x, j = 8 - y, xi, xj;
int pi = p[i], pj = p[j], pi1 = p[i+1], pj1 = p[j + 1];
x = *v % pi1, y = *v % pj1;
xi = x / pi; xj = y / pj;
*v = *v - x + xj * pi + x % pi;
*v = *v - y + xi * pj + y % pj;
}
可以使用下面的测试程序进行测试:
int main(void)
{
int x = 987654321;
_swap(&x, 0,8);
printf("%d\n", x);
return 0;
}
如何广搜
我的办法是从0序号开始,把所有点走一下。然后再进行判断。
需要注意的是,大致思路是记录十进制的值。按十进制应该是从123456789开始。
然后把每个点的后继值都要入栈。在得到后继结点的时候,需要确定向哪个方向翻转,
我采用的是udlr的方向。那么这里需要一个数组dk[][4]来记录当中心位于i时,需要向dk[i]所指向的四个方向,如果为-1,则表示那个方面没有值。不用向那个方向翻转。
比如位于位置0,则需要向:
[位置图]
0 1 2
3 4 5
6 7 8
上 下 左 右
-1 3 -1 1
这么四个方向进行操作。
这里需要生成一个类似于:
const int dk[][4] = {
{-1, 3, -1, 1, },
{-1, 4, 0, 2, },
{-1, 5, 1, -1, },
{0, 6, -1, 4, },
{1, 7, 3, 5, },
{2, 8, 4, -1, },
{3, -1, -1, 7, },
{4, -1, 6, 8, },
{5, -1, 7, -1, },
};
这样的一个矩阵来记录不同的中心位置,应该向哪些方向翻转。
矩阵生成代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<math.h>
int dk[][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
char map[3][3];
void print()
{
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
printf("%c", map[i][j]);
}
printf("\n");
}
}
int main(void)
{
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 3; ++j)
{
int iter = i * 3 + j;
map[i][j] = iter + '0';
}
}
print();
for (int i = 0; i < 9; ++i)
{
printf("{");
for (int j = 0; j < 4; ++j)
{
int x = i / 3 + dk[j][0];
int y = i % 3 + dk[j][1];
if (x <0 || x > 2 || y < 0 || y > 2) printf("-1, ");
else printf("%c, ", map[x][y]);
}
printf("},\n");
}
return 0;
}
有了以上的基础,加上对于广搜的认识,我们应该可以写代码了。这里粘出大致代码:
int q[N]; int qv[N]; //qv是用来记录入栈所对应的hash值,这样的话,只有在入栈的时候需要计算一下hash值。
int front[N];
char oper[N];
char center[N];
void BFS(int t)
{
int head = -1, tail = -1, next = 0;
int old_value, old_center, new_value, new_center, i;
q[++tail] = t;
qv[tail] = GetNumber(t);
while (head != tail)
{
t = q[++head];
old_value = qv[head];
old_center = center[old_value];
for (i = 0; i < 4; ++i)
{
new_center = dk[old_center][i];
if (-1 != new_center)
{
next = t; _swap(&next, old_center, new_center);
new_value = GetNumber(next);
if (0 == test(new_value))
{
set(new_value);
front[new_value] = old_value;
oper[new_value] = i;
center[new_value] = new_center;
q[++tail] = next;
qv[tail] = new_value;
}
}
}
}
}
char _output[N];
void PrintPath(int start)
{
int idx = 0;
char *rec = _output;
if (!start) {puts(""); return;}
while (start != 0)
{
*rec++ = GetFrontOperChar(oper[start]);
start = front[start];
}
*rec = 0;
puts(_output);
}
主函数要进行如下初始化操作:
memset(oper, -1, sizeof(oper));
center[0] = 8; front[0] = -1; oper[0] = 0; set(0);
BFS(123456789);