广度优先搜索(二)
【例5】 8数码难题
【问题描述】:
在 3 * 3 的棋盘上,摆有八个棋子,每个棋子上标有 1 至 8 的某一数字。棋盘中留有一个空格。空格周围的棋子可以移到空格中。要求解的 问题是,给出一种初始布局 [ 初始状态 ] 和目标布局 [ 目标状态 ] ,找到一种移动的方法,实现从初始布局到目标布局的转变。
【输入格式】
输入由两行组成,每行9个数,分别表示初始状态和目标状态:
【输出格式】
有若干行,第一行表示最小步数 ,接下来的每行输出一步的状态,如果在6步内无法达到目标状态,输出“No solution!”。
【输入输出样例】
输入文件名: 8num.in
283164705
123804765
输出文件名:8num.out
5
283
164
705
283104765
203184765
023184765
123084765
123804765
【问题分析】:
这是一道典型的搜索题目,搜索产生的结点可以达到9!,再加上判重,如果不优化的话,耗时严重,关于广搜得优化我们后面的学习中再作讨论,本题只要求输出6步以内可以达到的目标,所以使用一般的广搜即可解决。问题是,如何在扩展结点的同时计算出已经走的步数,而不是找到目标状态后再逆序跟踪输出步数和步骤?
两种方法,一种是在队列中增加一个域,来记录扩展出当前状态用的步数,该步数是由它的父节点步数加1得到。
另一种方法是双尾指针法。广搜是按层次搜索的,每搜完一层,即可视为走完一步,所以,只要记录下来当前搜索了多少层即可。我们使用tail2记录当前扩展层最后一个节点在队列中的位置,当head指向tail2并且扩展完tail2,说明完成一层的搜索,此时step+1,并且tail2指向tail,也就是扩展出的新的一层的尾结点。
记录步数的广搜算法框架如下:
1 Program Bfs;
2 初始化,初始状态存入OPEN 表;
3 队列首指针head:=0;尾指针 tail:=1;tail2:=1; step:=1;
4 repeat
5 指针head后移一位,指向待扩展结点;
6 for I=1 to max do {max为产生子结点的规则数}
7 begin
8 if 子结点符合条件 then
9 begin
10 if新结点是目标结点 then 输出 else
11 if新结点与原已产生结点不重复 then tail 指针增1,把新结点存入列尾;
12 end;
13 end;
14 if head=tail2 then begin inc(step);tail2:=tail;end;
15 until(head>=tail); {队列空}
另外我们需要考虑如何记录数码状态,可以使用3*3的二维数组,也可以使用长度为9的字符串。一般使用数组记录,实现起来比较简单,但是判重的时候比较麻烦,如果使用9维布尔数组来判重,那么需要很大的存储空间,不过速度会较快,如果使用字符串来记录,难以用布尔数组来判重,所以判重耗时巨大,但是字符串可以直接比较并且本题的规模也不大,只要判断6步以内是否能够达到目标状态,所以我们不妨使用字符串s来记录数码状态,用p来记录s中0的位置,0与其他数字的交换最多有四种情况:
与上方的数字交换则把p与p-3位置的字符交换位置;
与右边的数字交换则把p与p+1位置的字符交换位置;
与下方的数字交换则把p与p+3位置的字符交换位置;
与左边的数字交换则把p与p-1位置的字符交换位置;
注意特殊情况下,当p=4或7时,0不能与左边数字交换,当p=3或6时,0不能与右边数字交换,另外,所有的交换位置都不能超过1~9范围。
搜索算法_8数码难题1
AYYZOJ p1461