算法2——八数码问题高效算法

生活充满乐趣算法让我快乐,代码使我癫狂,在写这次的博客之前先打几个汉字练练手感。开始喽!

  1. 首先让我看看什么是BFS算法,BFS全拼是Breadth-FirstSearch,即广度优先搜索,亦或者称为宽度/横向优先搜索,简称BFS,是一种图形搜索算法。本质上来说,BFS是从根节点开始,沿着树的宽度遍历树的节点,若发现目标,则停止搜索,BFS我们一般采用open-closed表来实现。百科上说它还可以用来做以寻找连接元件,测试是否二分图以及应用于电脑游戏中平面网络,我就默默想这是什么鬼(……希望再过二十天我可以将这些话偷偷删了),只希望在将这篇博客搞清楚之后我可以掌握BFS的基础,进而为详细学习打下不错的基础。
  2. 好了,进入正题,八数码问题就是前面所提bfs中的经典问题,解决它我们主要用的就是bfs+hash;主要是由于状态分散,所以无法直接用一个确定的数表示,所以我们在bfs时无法去判断一个数已经被搜过,自然也无法用数组去求解,此时就需用hash的方法判断当前状态是否已经被搜过,并按照搜索顺序给每个状态编号,将所有的状态存起来,供hash查找。在正式开始之前,不得不做的准备就是了解全排列的知识,百科上说这是组合数学的知识广义的组合数学就是离散数学,狭义的组合数学是离散数学除图论、代数结构、数理逻辑等的部分。但这只是不同学者在叫法上的区别,总之,组合数学是一门研究离散对象的科学。我们把从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。其最最最最重要的计算公式就是f(n)=n!(定义0!=1)…………                自然而然八数码问题的状态正好9的全排列问题,由此可对每个状态所对应的是第几个排列来进行编号,这样就可以做到一一对应,可以做到给定一个状态后就能直接计算出这个状态对应的编号,这样就可以做到直接用一个数组标记这个状态是否被搜索过,就不需要利用hash法来查找这个状态是否被搜过。其实就是借助全排列字典序法中通过递增进位制数法构建的中介数来构成数组之后遍历查询。
8—puzzle-problem                                                (初始状态)
2 8 3
1 6 4
7   5

                                                                             (目标状态)                                                              

1 2 3
8   4
7 6 5

上图就是所谓的八数码问题,闲话休提,下面来看这道算法题的真面目………………

1,Problem Description

The 15-puzzle has been around for over 100 years; even if you don't 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      r->     9 10 x 12    d->         9 10 11 12     r ->            9 10 11 12

13 14 11 15           13 14 11 15            13 14 x 15                       13 14 15 x    

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', '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).

INPUT

You will receive, several descriptions of configuration of the 8 puzzle. One 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 number 1 to 8, plus 'x'. 

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. Do not print a blank line between cases.

Sample Input
23415x768

Sample Output
ullddrurdllurdruldr

荒废多日,终将来写,先将于今晚将此程序吃透。(2018/7/18)

在阅读本文档前阅读了解一下数据结构中邻接表的相关知识以及图的遍历之广度优先搜索(bfs)的相关知识必定大为有益,同时此程序非常有利于学习加深上述知识点,且推荐关于头文件#include<queue>中队列类用法的博客。知识

#include <stdio.h>
#include <iostream>
#include <queue>
using namespace std;
//简要记录步信息
typedef struct Step_s
{
   //上一步到这一步的方向
    char dir;
    //上一步的hash值
    int parent;
} Step;
//倒推步法时记录的队列节点
typedef struct Node_s
{   
    int board[9];//魔板信息
    int x_index;//x所在位置
    int child; //下一步的hash值
} Node;
int dir[4][2] = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };//四个方向
int fac[10];//用于hash值计算
Step step_set[370000];//所有步法集合
void set_fac()//设置阶乘值,用于后面的hash值计算
{
    fac[0] = 1;
    for (int i = 1; i <= 8; ++i) {
        fac[i] = fac[i - 1] * i;
    }
}
//对于任意的board,算出对应的hash值
int board_hash(int board[])
{
    int i, j, ans = 0, k;
    for (i = 0; i < 9; ++i)
	{
        k = 0;
        for (j = i + 1; j < 9; j++)
		{
            if (board[i] > board[j]) 
			{
                k++;
            }
        }
        ans += k * fac[8 - i];
    }
    return ans;
}

//广度优先搜索
void bfs(int finish[])
{
    queue<Node> Q;			//类似于queue<int>q;是什么了一个Node的Q队列
    Node current;
    int tx, ty, temp, t = 0;
    for (int i = 0; i < 9; ++i)//记录这个节点的魔板信息
	{
        current.board[i] = finish[i];
    }
    current.x_index = 8; current.child = 0; //x在current.board[8]的位置上,child和parent的0表示结束和存在路径
    step_set[current.child].parent = 0;
    Q.push(current);
    while (!Q.empty())
    {
        current = Q.front(); Q.pop(); //取得队列的第一个元素
        for (int i = 0; i < 4; ++i) //从4个方向开始前进
		{
            Node next = current;
            tx = current.x_index % 3 + dir[i][0]; //取得该方向的下一个位置
            ty = current.x_index / 3 + dir[i][1];
            if (tx >= 0 && ty >= 0 && tx < 3 && ty < 3) //防止出界
			{
                next.x_index = ty * 3 + tx;
                temp = next.board[next.x_index];//交换x的位置
                next.board[next.x_index] = next.board[current.x_index];
                next.board[current.x_index] = temp;            
                next.child = board_hash(next.board); //计算hash值
                if (step_set[next.child].parent == -1)  //查看是否重复
				{
                    step_set[next.child].parent = current.child;  //上一步的hash值
                    //设置方向
                    if (i == 0)step_set[next.child].dir = 'l';
                    if (i == 1)step_set[next.child].dir = 'r';
                    if (i == 2)step_set[next.child].dir = 'u';
                    if (i == 3)step_set[next.child].dir = 'd';
                    Q.push(next);//每次前进则放进队列(广度优先搜索)
                }
            }
        }
    }
}
int main()
{
    int i, j, s, board_input[10], finish[9];
    char ch[50];
    for (i = 0; i < 9; ++i)  /*完成状态是[1,2,3,4,5,6,7,8,9]*/
	{
        finish[i] = i + 1;
    }
    for (i = 0; i < 370000; ++i)   //初始化步数集合的父hash值
	{
        step_set[i].parent = -1;
    }
    set_fac();//设置阶乘函数,用于后面的hash值计算
    bfs(finish); //开始倒推,设置所有的步法
    while (gets(ch))  //输入魔板,gets函数能够输入整行
	{
        for (i = 0, j = 0; ch[i] != '\0'; i++)
		{
            if (ch[i] == 'x') 
			{
                board_input[j++] = 9;
            }
            else if (ch[i] >= '0' && ch[i] <= '8') 
			{
                board_input[j++] = ch[i] - '0';
            }
        }     
        s = board_hash(board_input);//计算输入的魔板hash值     
        if (step_set[s].parent == -1)  //如果不存在到达这种魔板的路线
		{
            printf("unsolvable\n");
            continue;
        }
        while (s != 0) //输出所有的步法
		{
            printf("%c", step_set[s].dir);
            s = step_set[s].parent;
        }
        printf("\n");
    }
}

本篇文章转载自微信公众号ACM算法日常

猜你喜欢

转载自blog.csdn.net/qq_41345173/article/details/80779834