广度优先搜索与八字码问题

广度优先搜索算法(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  x

where 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  8

is described by this list:

 1 2 3 x 4 6 7 5 8

Output

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  8

Sample Output

ullddrurdllurdr

思路

这道题的思路大概是这样子

  1. 读入到 a 数组内
  2. 输出一下 a 数组,做一个子程序 print2
  3. 定义一下宽搜的节点
    1. 首先有一个 a 棋盘(是一个二维数组)
    2. 移动空位,记住空位的位置 x,y
    3. 记录深度(你走了几步)叫 dep
    4. 父节点,打方案用,走法 dd
  4. 定义队列大数组结构体 data1 100000,一个头指针 op,尾指针 cl
  5. 偏移量,空位的移动方案 d[5] 结构体 d[1].x d[1].y 偏移
  6. 初始化队列,首节点入队(初始状态),写一个拷贝子程序
  7. 做一个棋盘之间的复制 copy(a,b),把 a 棋盘的值拷贝到 b 棋盘
  8. 判断两个棋盘的值是否相同
  9. 判断队列中棋盘是否存在 exist(a):a 棋盘在我的 data1 数组中从 1 到 cl 是否存在
  10. 输出方案的子程序 print1
  11. 写宽搜的框架

但这个时候问题来了,单项搜索的效率太低了!!!如果要走 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;
}
posted @ 2019-05-25 15:53  hélium  阅读(680)  评论(0)    收藏  举报