八数码之广搜

如何测试算法

首先要把所有可能的序列都输出来,其实也就是生成全排列了。这里直接给出代码。

用以下代码生成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);



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值