一、 试验目的
用图搜索法:广度优先、深度优先和A*算法实现八数码问题。
二、 试验内容
八数码问题是:将分别标有数字1,2,3,…,8的八块正方形数码牌任意地放在一块3×3的数码盘上。放牌时要求不能重叠。于是,在3×3的数码盘上出现了一个空格。现在要求按照每次只能将与空格相邻的数码牌与空格交换的原则,将任意摆放的数码盘逐步摆成某种特殊的排列。
三、 试验流程图及程序
1
2
3
8
4
7
6
5
问题描述:例如下图
2
3
1
8
4
7
6
5
开始状态 目标状态
程序代码:
#include
#include
typedef unsigned long UINT64;
typedef struct
{char x; //位置x和位置y上的数字换位
char y; //其中x是0所在的位置
} EP_MOVE;
#define SIZE 3 //8数码问题,理论上本程序也可解决15数码问题,
#define NUM SIZE * SIZE //但move_gen需要做很多修改,输入初始和结束状态的部分和check_input也要修改
#define MAX_NODE 1000000
#define MAX_DEP 100
#define XCHG(a, b) { a=a + b; b=a - b; a=a - b; }
#define TRANS(a, b)
{ long iii; (b)=0; for(iii=0; iii < NUM; iii++) (b)=((b) << 4) + a[iii]; } //将数组a转换为一个64位的整数b
#define RTRANS(a, b) \
{ \
long iii; \
UINT64 ttt=(a); \
for(iii=NUM - 1; iii >= 0; iii--) \
{ \
b[iii]=ttt & 0xf; \
ttt>>=4; \
} \
} //将一个64位整数a转换为数组b
//
typedef struct EP_NODE_Tag
{ UINT64 v; //保存状态,每个数字占4个二进制位,可解决16数码问题
struct EP_NODE_Tag *prev; //父节点
struct EP_NODE_Tag *small, *big;
} EP_NODE;
EP_NODE m_ar[MAX_NODE];
EP_NODE *m_root;
long m_depth; //搜索深度
EP_NODE m_out[MAX_DEP]; //输出路径
//
long move_gen(EP_NODE *node, EP_MOVE *move)
{long pz; //0的位置
UINT64 t=0xf;
for(pz=NUM - 1; pz >= 0; pz--)
{if((node->v & t) == 0)
{ break; //找到0的位置
}
t<<=4;
}
switch(pz)
{case 0:
move[0].x=0;
move[0].y=1;
move[1].x=0;
move[1].y=3;
return 2;
case 1:
move[0].x=1;
move[0].y=0;
move[1].x=1;
move[1].y=2;
move[2].x=1;
move[2].y=4;
return 3;
case 2:
move[0].x=2;
move[0].y=1;
move[1].x=2;
move[1].y=5;
return 2;
case 3:
move[0].x=3;
move[0].y=0;
move[1].x=3;
move[1].y=6;
move[2].x=3;
move[2].y=4;
return 3;
case 4:
move[0].x=4;
move[0].y=1;
move[1].x=4;
move[1].y=3;
move[2].x=4;
move[2].y=5;
move[3].x=4;
move[3].y=7;
return 4;
case 5:
move[0].x=5;
move[0].y=2;
move[1].x=5;
move[1].y=4;
move[2].x=5;
move[2].y=8;
return 3;
case 6:
move[0].x=6;
move[0].y=3;
move[1].x=6;
move[1].y=7;
return 2;
case 7:
move[0].x=7;
move[0].y=6;
move[1].x=7;
move[1].y=4;
move[2].x=7;
move[2].y=8;
return 3;
case 8:
move[0].x=8;
move[0].y=5;
move[1].x=8;
move[1].y=7;
return 2;
}
return 0;
}
long mov(EP_NODE *n1, EP_MOVE *mv, EP_NODE *n2)
//走一步,返回走一步后的结果
{
char ss[NUM];
RTRANS(n1->v, ss);
XCHG(ss[mv->x], ss[mv->y]);
TRANS(ss, n2->v);
return 0;
}
long add_node(EP_NODE *node, long r)
{
EP_NODE *p=m_root;
EP_NODE *q;
while(p)
{ q=p;
if(p->v == node->v) return 0;
else if(node->v > p->v) p=p->big;
else if(node->v < p->v) p=p->small;
}
m_ar[r].v=node->v;
m_ar[r].prev=node->prev;
m_ar[r].small=NULL;
m_ar[r].big=NULL;
if(node->v > q->v)
{ q->big= &m_ar[r];
}
else if(node->v < q->v)
{ q->small= &m_ar[r];
}
return 1;
}
/*得到节点所在深度*/
long get_node_depth(EP_NODE *node)
{ long d=0;
while(node->prev)
{ d++;
node=node->prev;
}
return d;
}
/*返回值:成功-返回搜索节点数,节点数不够-(-1),无解-(-2)*/
long bfs_search(char *begin, char *end)
{ long h=0, r=1, c, i, j;
EP_NODE l_end, node, *pnode;
EP_MOVE mv[4]; //每个局面最多4种走法
TRANS(begin, m_ar[0].v);
TRANS(end, l_end.v);
m_ar[0].prev=NULL;
m_root=m_ar;
m_root->small=NULL;
m_root->big=NULL;
while((h < r) && (r < MAX_NODE - 4))
{ c=move_gen(&m_ar[h], mv);
for(i=0; i < c; i++)
{ mov(&m_ar[h], &mv[i], &node);
node.prev= &m_ar[h];
if(node.v == l_end.v)
{ pnode= &node;
j=0;
while(pnode->prev)
{ m_out[j]=*pnode;
j++;
pnode=pnode->prev;
}
m_depth=j;
return r;
}
if(add_node(&node, r)) r++; //只能对历史节点中没有的新节点搜索,否则会出现环
}
h++;
printf("\rSearch...%9d/%d @ %d", h, r, get_node_depth(&m_ar[h]));
}
if(h == r)
{ return -2; }
else
{return -1; }
}
long check_input(char *s, char a, long r)
{ long i;
for(i=0; i < r; i++)
{ if(s[i] == a - 0x30) return 0; }
return 1;
}
long check_possible(char *begin, char *end)
{ char fs;
long f1=0, f2=0;
long i, j;
for(i=0; i < NUM; i++)
{ fs=0;
for(j=0; j < i; j++)
{
if((begin[i] != 0) && (begin[j] != 0) && (begin[j] < begin[i])) fs++;
}
f1+=fs;
fs=0;
for(j=0; j < i; j++)
{ if((end[i] != 0) && (end[j] != 0) && (end[j] < end[i])) fs++;
}
f2+=fs;
}
if((f1 & 1) == (f2 & 1)) return 1;
else
return 0;
}
void output(void)
{ long i, j, k;
char ss[NUM];
for(i=m_depth - 1; i >= 0; i--)
{ RTRANS(m_out[i].v, ss);
for(j=0; j < SIZE; j++)
{ for(k=0; k < SIZE; k++)
{ printf("%2d", ss[SIZE * j + k]);
}
printf("\n");
}
printf("\n");
}
}
int main(void)
{ char s1[NUM];
char s2[NUM];
long r;
char a;
printf("请输入开始状态:");
r=0;
while(r < NUM)
{ a=getchar();
if(a >= 0x30 && a < 0x39 && check_input(s1, a, r))
{ s1[r++]=a - 0x30;
printf("%c", a);
}
}
printf("\n请输入结束状态:");
r=0;
while(r < NUM)
{ a=getchar();
if(a >= 0x30 && a < 0x39 && check_input(s2, a, r))
{ s2[r++]=a - 0x30;
printf("%c", a);
}
}
printf("\n");
if(check_possible(s1, s2))
{ r=bfs_search(s1, s2);
printf("\n");
if(r >= 0)
{ printf("查找深度=%d,所有的方式=%ld\n", m_depth, r);
output();
}
else if(r == -1)
{ printf("没有找到路径.\n");
}
else if(r == -2)
{printf("这种状态变换没有路径到达.\n");
}
else
{printf("不确定的错误.\n");
}
}
else
{ printf("不允许这样移动!\n");
}
return 0;
}
(1) 把起始节点s放到OPEN 表中,计算f(S) ,并把其值与节点S联系起来。
(2) 如果OPEN 是个空表,则失败退出,无解。
(3) 从OPEN 表中选择一个f值最小的节点i。结果有几个节点合适,当其中有一个为目标节点时,则选择此目标节点,否则就选择任意一个节点作为节点i。
(4) 把节点i从OPEN 表中移出,并把它放入CLOSED 的扩展节点表中。
(5) 如果i是目标节点,则成功退出,求得一个解。
(6) 扩展节点i,生成其全部后继节点。对于i的没一个后继节点j:
计算f ( j );
如果j既不在OPEN 表中,也不在CLOSED 表中,则用估价函数f 把它添加到OPEN 表中。从j 加一指向其父辈节点i的指针以便一旦找到目标节点时记住一个解答途径。
如果j已经在OPEN 表或CLOSED 表上,则比较刚刚对j计算过的f值和前面计算过的该节点在表中f值。如果新的f 值较小,则以此新值代替旧值。
从j指向i,而不是指向它的父辈节点。
如果节点j在CLOSED表中,则把它移回OPEN 表中。
(7) 转向(2)。
试验结果:
试验结论:
问题的求解过程就是搜索的过程,采用适合的搜索算法是很关键的,因为对求解过程的效率有很大的影响,包括各种规则、过程和算法等推理技术。八数码问题中,将牌的移动来描述规则,是一种相对较简单的方法。
通过这次试验使我对搜索算法有了一定的了解,也通过它解决了八数码问题,但在实际的过程中还存在很多问题,也看了一些辅助书籍,以后还要加强学习,加强理论与实际的练习。总之,这次试验使我受益匪浅。