广度优先搜索与八字码问题
广度优先搜索算法(Breadth-First Search),又称为"宽度优先搜索"或"横向优先搜索"主要可以解决两类问题:
- 是否存在从起点到终点的路径
- 找出从起点到终点的最短路径
算法描述
int bfs(){
初始化 首结点入队(首指针为0,尾指针为1)
while(首指针小于尾指针){
首指针后移一位
for(;;){ 产生子结点
if(){ 判断子结点是否满足条件
if(){ 如果结点未与之前产生过重复
尾指针后移
存入新的结点 (提供结点的父结点和层数)
if(){ 如果新结点是目标节点
输出并退出
}
}
}
}
}
}
值得注意的是,广搜的过程中每生成一个子结点,都要提供指向他们的父结点的指针和层数,以免找到解的时候找不到来时的路径。
广度优先搜索在访问完该层的所有结点后,才开始访问下一层。
关于从搜索树通过队列存入数组的思想:
广搜一定要将生成的结点与之前的比较,以免出现重复的节点,浪费时间和空间,还有可能陷入死循环!

八字码问题
Description
The 15-puzzle has been around for over 100 years; even if you don't know it by that name, you've seen it. It is constructed with 15 sliding tiles, each with a number from 1 to 15 on it, and all packed into a 4 by 4 frame with one tile missing. Let's call the missing tile 'x'; the object of the puzzle is to arrange the tiles so that they are ordered as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 xwhere the only legal operation is to exchange 'x' with one of the tiles with which it shares an edge. As an example, the following sequence of moves solves a slightly scrambled puzzle:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 5 6 7 8 5 6 7 8 5 6 7 8 5 6 7 8 9 x 10 12 9 10 x 12 9 10 11 12 9 10 11 12 13 14 11 15 13 14 11 15 13 14 x 15 13 14 15 x r-> d-> r->The letters in the previous row indicate which neighbor of the 'x' tile is swapped with the 'x' tile at each step; legal values are 'r','l','u' and 'd', for right, left, up, and down, respectively.
Not all puzzles can be solved; in 1870, a man named Sam Loyd was famous for distributing an unsolvable version of the puzzle, and frustrating many people. In fact, all you have to do to make a regular puzzle into an unsolvable one is to swap two tiles (not counting the missing 'x' tile, of course).
In this problem, you will write a program for solving the less well-known 8-puzzle, composed of tiles on a three by three arrangement.
Input
You will receive a description of a configuration of the 8 puzzle. The description is just a list of the tiles in their initial positions, with the rows listed from top to bottom, and the tiles listed from left to right within a row, where the tiles are represented by numbers 1 to 8, plus 'x'. For example, this puzzle
1 2 3 x 4 6 7 5 8is described by this list:
1 2 3 x 4 6 7 5 8Output
You will print to standard output either the word
unsolvable, if the puzzle has no solution, or a string consisting entirely of the letters 'r', 'l', 'u' and 'd' that describes a series of moves that produce a solution. The string should include no spaces and start at the beginning of the line.Sample Input
2 3 4 1 5 x 7 6 8Sample Output
ullddrurdllurdr
思路
这道题的思路大概是这样子
- 读入到 a 数组内
- 输出一下 a 数组,做一个子程序
print2 - 定义一下宽搜的节点
- 首先有一个 a 棋盘(是一个二维数组)
- 移动空位,记住空位的位置 x,y
- 记录深度(你走了几步)叫 dep
- 父节点,打方案用,走法 dd
- 定义队列大数组结构体 data1 100000,一个头指针 op,尾指针 cl
- 偏移量,空位的移动方案 d[5] 结构体 d[1].x d[1].y 偏移
- 初始化队列,首节点入队(初始状态),写一个拷贝子程序
- 做一个棋盘之间的复制
copy(a,b),把 a 棋盘的值拷贝到 b 棋盘 - 判断两个棋盘的值是否相同
- 判断队列中棋盘是否存在
exist(a):a 棋盘在我的 data1 数组中从 1 到 cl 是否存在 - 输出方案的子程序
print1 - 写宽搜的框架
但这个时候问题来了,单项搜索的效率太低了!!!如果要走 20 步,程序需要运行 8 秒多!!!
这个时候,我们会发现题目中给出了终态!在这种情况下,同样的搜索从初态和终态出发各搜索一半状态,产生两棵深度减半的搜索树,在中间交会、组合成最终的答案。
这种算法的时间复杂度只有 O(2^(N/2) log 2^(n/2)) = O(N * 2^(N/2)) !!!
这道题是不是完美地解决了??
等等!还有 unsolvable 的判断!但是这道题不能用搜索来判断是否有解------无解的时候程序将无限的搜索下去!!
这个时候就需要事先判断八个数字的排列了。
我们会用到置换:
在组合数学中,置换一词的传统意义是一个有序序列,其中元素不重复,但可能有阙漏。例如 1,2,4,3 可以称为 1,2,3,4,5,6 的一个置换,但是其中不含 5,6。此时通常会标明为“从 n 个对象取 r 个对象的置换”。
轮换长度为偶数的轮换称为偶轮换,反之则为奇轮换;由此可定义任一置换的奇偶性,并可证明:一个置换是偶置换的充要条件是它可以由偶数个换位生成。偶轮换在置换群中构成一个正规子群,称为交错群。
(摘自百度百科)
也就是说,我们可以统计排列中逆序的次数(即后一项小于前一项,0 不计),如果有奇数次,则无解。
举个栗子叭
1 2 3
5 4 7
0 8 6
4<5 , 6<7 , 6<8 一共有三次逆序,所以无解.
双向搜索代码
#include <cstdio>
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
int a[4][4] = {} , c[4][4] = {} , x1 , y1 , x , y , op = 0 , cl = 1 , op1 = 0 , cl1 = 1 , dep = 0 , dep1 = 0; //op1 cl1表示由答案开始搜索的队列
int ii , jj;
int a1[10] = {}; //用于判断unsolvable
char k , answer[100];
int ans[4][4] = {{ 1 , 2 , 3 , 0 } ,{ 4 , 5 , 6 , 0 } ,{ 7 , 8 , 0 , 0 } ,{ 0 , 0 , 0 , 0 } };
void input( int a[4][4] ){
for( int i = 0; i < 3; i++ ){
for( int j = 0; j < 3; j++ ){
cin >> k;
if( k == 'x' ){
a[i][j] = 0;
x = i;
y = j;
} else{
a[i][j] = k - '0';
a1[i * 3 + j] = a[i][j];
}
}
}
}
void print2( int a[4][4] ){
for( int i = 0; i < 3; i++ ){
for( int j = 0; j < 3; j++ )
printf( "%d " , a[i][j] );
printf( "\n" );
}
}
void copy( int a[4][4] , int b[4][4] ){
for( int i = 0; i < 3; i++ ){
for( int j = 0; j < 3; j++ )
b[i][j] = a[i][j];
}
}
bool same( int a[4][4] , int b[4][4] ){
for( int i = 0; i < 3; i++ ){
for( int j = 0; j < 3; j++ )
if( b[i][j] != a[i][j] ) return false;
}
return true;
}
struct node1{
int x , y;
} d[5] = {{ 0 , 0 } ,{ -1 , 0 } ,{ +1 , 0 } ,{ 0 , -1 } ,{ 0 , +1 } };
struct node2{
int m[4][4];
int dep; //结点深度
int dd; //行走方向
int x , y; //坐标
int fa; //父结点下标
} data1[100001];
struct node3{
int m[4][4];
int dep; //结点深度
int dd; //行走方向
int x , y; //坐标
int fa; //父结点下标
} data2[100001];
bool exist( int a[4][4] ){
for( int i = 0; i <= cl; i++ ){
if( same( data1[i].m , a ) ) return false;
}
return true;
}
bool exist1( int a[4][4] ){
for( int i = 0; i <= cl1; i++ ){
if( same( data2[i].m , a ) ) return false;
}
return true;
}
void print1( int cl , int cl1 ){
int kk = cl , num = 0;
while( dep-- ){
if( data1[kk].dd == 1 ) answer[num++] = 'u';
else if( data1[kk].dd == 2 )
answer[num++] = 'd';
else if( data1[kk].dd == 3 )
answer[num++] = 'l';
else if( data1[kk].dd == 4 )
answer[num++] = 'r';
kk = data1[kk].fa;
}
for( int i = num - 1; i >= 0; i-- ) printf( "%c" , answer[i] );
kk = cl1;
num = 0;
while( dep1-- ){
if( data2[kk].dd == 1 ) answer[num++] = 'u';
else if( data2[kk].dd == 2 )
answer[num++] = 'd';
else if( data2[kk].dd == 3 )
answer[num++] = 'l';
else if( data2[kk].dd == 4 )
answer[num++] = 'r';
kk = data2[kk].fa;
}
for( int i = 0; i < num; i++ ) printf( "%c" , answer[i] );
printf( "\n" );
}
int main(){
// freopen("Eight.in","r",stdin);
//输入
input( a );
//特判
if( same( a , ans ) ){
printf( "%d\n" , 0 );
return 0;
}
int sum1 = 0;
for( int i = 1; i < 9; i++ )
if( a1[i] != 0 ){
for( int j = i + 1; j < 9; ++j )
if( a1[i] > a1[j] && a1[j] != 0 ){
++sum1;
}
}
if( sum1 % 2 == 0 ){
printf( "unsolvable\n" );
return 0;
}
//首结点入队
data1[1].dd = 0;
data1[1].dep = 1;
data1[1].x = x;
data1[1].y = y;
data1[1].fa = -1;
copy( a , data1[1].m );
data2[1].dd = 0;
data2[1].dep = 1;
data2[1].x = 2;
data2[1].y = 2;
data2[1].fa = -1;
copy( ans , data2[1].m );
//宽搜
while( op <= cl ){
op++;
op1++;
if( dep != data1[op].dep ){
for( int i = 1; i <= cl; i++ ){
for( int j = 1; j <= cl1; j++ ){
if( data1[i].dep == dep ){
if( same( data1[i].m , data2[j].m ) ){
print1( i , j );
return 0;
}
}
}
}
}
dep = data1[op].dep;
for( int i = 1; i <= 4; i++ ){
x = data1[op].x + d[i].x;
y = data1[op].y + d[i].y;
if( x >= 0 && x < 3 && y >= 0 && y < 3 ){
//printf("\n%d %d %d\n",op,x,y);
//移动后的棋盘存给c
copy( data1[op].m , c );
c[data1[op].x][data1[op].y] = c[x][y];
c[x][y] = 0;
if( exist( c ) ){
cl++;
data1[cl].dd = i;
data1[cl].dep = dep + 1;
data1[cl].x = x;
data1[cl].y = y;
data1[cl].fa = op;
copy( c , data1[cl].m );
}
}
}
dep1 = data2[op1].dep;
for( int i = 1; i <= 4; i++ ){
x = data2[op1].x + d[i].x;
y = data2[op1].y + d[i].y;
if( x >= 0 && x < 3 && y >= 0 && y < 3 ){
copy( data2[op1].m , c );
c[data2[op1].x][data2[op1].y] = c[x][y];
c[x][y] = 0;
if( exist1( c ) ){
cl1++;
data2[cl1].dd = i;
data2[cl1].dep = dep1 + 1;
data2[cl1].x = x;
data2[cl1].y = y;
data2[cl1].fa = op;
copy( c , data2[cl1].m );
}
}
}
}
return 0;
}

浙公网安备 33010602011771号