A*搜索 - 八数码问题

问题描述

八数码问题,简单地来描述是这样的:在一个九宫格内,填有1、2、3、4、5、6、7、8,八个阿拉伯数字,有一个格子为空白。就下面这样,这是一个没有归位的九宫格。

123
78
465
(未归位的九宫格)

对于上面这个九宫格,我们要通过移动数字来使之归位,每次移动都只能是与空白格相邻的数字移到空白格里面。最终得到归位的九宫格应该是像下面这样的。

123
84
765
(已归位的九宫格)

我们的目标是,给出一种初始状态,如果有解的话,就把移动的步骤记录(或者输出)下来。值得注意的是,有的初始状态,是无论如何都没有办法使九宫格归位的。

解题思路

问题抽象

对于这个问题,我们不妨这样来看:每一种状态,最多有四种移动方式,也就是说,一种状态最多可以衍生出四种子状态。我们把问题的整体看成一个图,一个状态是图中的一个节点,每个节点生成的自节点由一条有向边来指向。图中总有一个节点的状态是我们要的归位状态,我们要做的就是找到初始节点(即初状态)到这个归位节点的路径。

图的宽度优先搜索

为了找到我们要的路径,我们可以使用宽度优先搜索。我们需要三样东西:集合close,用于保存访问过的节点。集合open,用于保存待访问的节点。每次访问一个节点,就会产生子节点,那么有如下三种情况:

  1. 如果子节点存在于open中,就把该子节点的深度与open中的那个进行对比,若该子节点的深度较小,就替换。

  2. 如果子节点存在于close中,就把该子节点的深度与close中的那个进行对比,若该子节点的深度较小,就替换,并且该子节点的所有后代节点深度都要进行修改。(但是在该问题的A*搜索算法中,我们可以不用考虑这件事。把弱遇到子节点存在于close中的情况,直接将其扔掉就可以了)

  3. 如果子节点不在open中,也不在close,那么将该节点加入到open中等待访问。

如此进行下去,一直到找到我们想要的节点(说明有解),或者open变为空为止(说明无解)

A*搜索

如果问题有解的话,有没有什么办法可以让我们的搜索变得更快呢?
有,我们可以设置一个评估函数F(n) = G(n) + H(n)G(n)给出的是节点的深度。H(n)它给出的是当前状态下将所有数字归位所需的最小步数。每次要从open中取出一个节点的时候,按照F(n)给节点拍一个序,然后选取函数值最小的节点。评估函数H(n)所给出的步数,比实际让九宫格归位所需的步数要少,所以这种启发式搜索叫做A*搜索。

代码实现

我们使用一个整数表示一种状态,整数中的每一位对应了九宫格中的一位,这样可以缩减比较九宫格是否一样所使用的时间。
比如九宫格 :

123
78
654

被表示为整数123780654。

数据结构

每一个节点里面的信息包含:
state:当前的状态、
prev:父节点的指针、
next:一个存放子节点指针的向量、
cost:H(n)的函数值、
zeropos:空白格子的位置、
depth:节点深度。

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

#ifndef NODE_H
#define NODE_H
struct Node{
    int state;
    Node* prev;
    vector<Node*> next;
    int cost;
    int zeropos;
    int dept;
};
#endif

change函数

输入一个状态,以及空白格子的位置,以及空白格子的移动方向,如果移动方向合法,就可以产生子状态,否则返回-1.

int change(int state,int zeropos,int direction){
    int ansstate = 0;
    int usestate = state;
    int zerorow = (zeropos-1)/3;
    int zerocol = (zeropos-1)%3;
    int matrix[3][3] = {1,2,3,
                        8,0,4,
                        7,6,5};
    int mo = 100000000;
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++)
        {
            matrix[i][j] = usestate/mo;
            usestate = usestate%mo;
            mo = mo/10;
        }

    switch (direction) {   //0是左移,1是上移,2是右移,3是下移
        case 0:{
            if(zeropos==1||zeropos==4||zeropos==7) return -1;
            int target = matrix[zerorow][zerocol-1];
            matrix[zerorow][zerocol-1] = 0;
            matrix[zerorow][zerocol] = target;
            break;
        }

        case 1:{
            if(zeropos==1||zeropos==2||zeropos==3) return -1;
            int target = matrix[zerorow-1][zerocol];
            matrix[zerorow-1][zerocol] = 0;
            matrix[zerorow][zerocol] = target;
            break;
        }

        case 2:{
            if(zeropos==3||zeropos==6||zeropos==9) return -1;
            int target = matrix[zerorow][zerocol+1];
            matrix[zerorow][zerocol+1] = 0;
            matrix[zerorow][zerocol] = target;
            break;
        }

        case 3:{
            if(zeropos==7||zeropos==8||zeropos==9) return -1;
            int target = matrix[zerorow+1][zerocol];
            matrix[zerorow+1][zerocol] = 0;
            matrix[zerorow][zerocol] = target;
            break;
        }

        default:
            break;
    }
    int mul = 100000000;
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++)
        {
            ansstate = ansstate + matrix[i][j]*mul;
            mul = mul/10;
        }
    return ansstate;
}

H(n)函数

输入一个状态,返回H(n)函数值。

int Hn(int t){
    int crr[3][3];
    int brr[3][3]={1,2,3,
        8,0,4,
        7,6,5};
    for(int i=8;i>=0;i--){
        int mod=t%10;
        t=t/10;
        crr[i/3][i%3]=mod;
    }
    int count=0;
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            for(int p=0;p<3;p++){
                for(int q=0;q<3;q++){
                    if(crr[i][j]==brr[p][q]){
                        int sum1=p-i;
                        if(sum1<0) sum1=-sum1;
                        int sum2=q-j;
                        if(sum2<0) sum2=-sum2;
                        int sum=sum1+sum2;
                        count+=sum;
                        break;
                    }
                }
            }
        }
    }
    return count;
}

findZeroPos函数

输入一个状态,返回空白格子的位置。

int findZeropos(int state){ int ans = 0; int usestate = state; int matrix[3][3] = {1,2,3, 8,0,4, 7,6,5};
    int mo = 100000000;
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++)
        { matrix[i][j] = usestate/mo; usestate = usestate%mo; mo = mo/10; }
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++)
            if(matrix[i][j]==0){ ans = 3 * i + j + 1; }
    return ans;
}

mySearch函数

输入一个状态,用广度优先搜索找到使九宫格归位的路径,并返回结果节点的指针。路径用节点组成的链表来储存。

Node* mySearch(int inistate){
    vector<Node*> open;
    vector<Node*> close;
    //初始化第一个节点
    Node* ini = new Node;
    ini->zeropos = findZeropos(inistate);
    ini->state = inistate;
    ini->dept = 0;
    ini->cost = Hn(inistate);
    ini->prev = NULL;
    open.push_back(ini);
    //开始广搜
    while(!open.empty()){
        sort(open.begin(),open.end(),cmp);
        Node* temp = open.back();
        open.pop_back();
        if(temp->state==123804765) return temp;
        for(int i = 0;i < 4;i++){
            int nextstate = change(temp->state, temp->zeropos, i);
            if(nextstate == -1) continue;

            //如果存在于close中,本题不做此考虑
            bool inClose = false;
            for(int j = 0;j<close.size();j++){
                if(close[j]->state == nextstate) inClose = true;
            }

            //如果存在于open中
            bool inOpen = false;
            for(int j = 0;j<open.size();j++){
                if((open[j]->state == nextstate)&&(open[j]->dept>temp->dept+1)){
                    open[j]->prev = temp;      //修改它的父节点
                    inOpen = true;
                }
            }
            if(inOpen||inClose) continue; //如果不在open且不在close中,则创建新的节点
            Node *son = new Node;
            son->state = nextstate;
            son->dept = temp->dept+1;
            son->cost = Hn(nextstate);
            son->prev = temp;
            son->next.push_back(son);
            son->zeropos = findZeropos(nextstate);
            open.push_back(son);
        }
        close.push_back(temp);
    }
    return NULL;
}

主函数(用于测试)

int main(){
    //输入一个九宫格
    int testmatrix[3][3] = {0};
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++)
            cin>>testmatrix[i][j];

    //转换成整数
    int test = 0;
    int mul = 100000000;
    for(int i = 0;i<3;i++)
        for(int j = 0;j<3;j++){
           test = test + testmatrix[i][j]*mul;
            mul = mul/10;
        }

    //搜索
    Node* ans = mySearch(test);

    //按照九宫格的形式输出生成的链表
    while(ans){
        int usestate = ans->state;
        int matrix[3][3];
        int mo = 100000000;
        for(int i = 0;i<3;i++)
            for(int j = 0;j<3;j++)
            {
                matrix[i][j] = usestate/mo;
                usestate = usestate%mo;
                mo = mo/10;
            }
        for(int i = 0;i<3;i++){
            for(int j = 0;j<3;j++)
            {
                cout<<matrix[i][j]<<" ";
            }
            cout<<endl;
        }
        cout<<endl;
        ans = ans->prev;
    }
    return 0;

}

结果

  1. 对于有解的情况,该算法的确可以很快找到使九宫格归位的路径。
  2. 对于无解的情况,往往要跑很久,遍历很多节点。程序的运行时间长达十多分钟。

(完)

    原文作者:九宫格问题
    原文地址: https://blog.csdn.net/SherlockSheep/article/details/52725859
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞