问题描述:八数码问题即是找出一条状态路径,使初始状态(start)转换到目标状态(end),一般选取目标状态为:1 2 3 4 5 6 7 8 0(0代表空格)
0 1 2 1 2 3
3 4 5 ——> 4 5 6
6 7 8 7 8 0
下面通过几个问题来对求解策略进行讨论。
(Q1)任意给定初态和终态,是否存在路径(可解性)?
八数码问题的可解性,存在一个判定定理:八数码问题可解当且仅当初态数字(除空格)的逆序数之和与终态的逆序数之和奇偶性一致。详细课参考http://wenku.baidu.com/link?url=PtvqjecMh5AJNaXso2Lq2Yg_VSpyDZpj8-3noewRp0CEs_-NcMZTn61Nb2pwxKkVf1R3o-wC7YKDgUkiUajFoHeni73_wd3OStpTOEKGQoS
(Q2)如何存储状态节点?
采用3*3或者1*9矩阵来存储状态节点,笔者也不例外,因为该存储结构方便计算评价函数值以及打印路径等。但是在此基础上,笔者添加了一个mark域(一个int型),sizeof(int)=4*8=8*4,即能表示8个十六进制数,则能够标志唯一一个状态,这样两个状态是否相同的判断就从9个int型数据的比较变为1个int型的比较,因此能够节省大量的时间(虽然说,计算标志时也相当于一次矩阵比较,但是却能做到“一劳永逸”)。
(Q3)为什么选择链式存储结构?
与静态存储结构相比,链式存储结构是“量体裁衣”,不会造成空间的浪费;另一方面,对于不同的初态,搜索的深度不知,静态表的长度也不能确定一个合适的值(既不浪费也能够用);最重要的一点,搜索过程,经常要进行节点的删除,添加,若用静态链表存储必定会浪费大量的时间。
(Q4)搜索方法如何选取?
八数码问题的解空间树属排列树,用于排列树搜索的方法主要有两大类:一类是盲目搜索,如深度优先搜索DFS和广度优先搜索BFS;另一类是启发式搜索,如A*算法。对于八数码问题,深度优先搜索的状态空间搜索树的深度可能很深,或者可能至少要比某个可接受的解答序列的已知深度上限还要深。应用此策略很可能得不到解。宽度优先搜索的优势在于当问题有解时,一定能找到解,且能找到最优解。但其搜索时需要保存所有的待扩展结点,这样很容易扩展那些没有用的结点,造成状态的指数增长,甚至"组合爆炸"。这对宽度优先搜索很不利。这两种搜索方法的共同缺点是结点排序杂乱无章,往往在搜索了大量无关结点后才能得到目的结点,只适合于复杂度较低的问题的求解。启发式搜索利用特定问题自身所携带的启发信息在一定程度上避免了盲目搜索的不足。
(Q5)如何选取评价函数?
对于f(n)的考虑最简单的便是比较每个状态与目标状态相比错位的个数。这个启发意味着如果其他条件相同,那么错位的个数最少的状态可能最接近目标状态。然而这个启发没有使用从棋盘格局中可以得到的所有信息,因为它没有把数字必要的移动距离纳入考虑。一个“更好一点”的启发是对错位的牌必须要移动的距离求和,为了达到这个目的,每张牌必须要移动的每个方格算为一个距离单位。本文采用后者。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
/***************构造数据结构******************/