八数码问题的A*搜索算法实现

内容

设计估价函数(编程语言不限),以八数码为例演示A*算法的搜索过程,争取做到直观、清晰地演示算法,代码要适当加注释。

八数码问题:在3×3方格棋盘上,分别放置了标有数字1, 2, 3, 4, 5, 6, 7, 8的八张牌,初始状态S0根据题目要求设定,使用的操作有: 空格上移,空格左移,空格右移,空格下移。试采用A*算法写程序实现这一搜索过程。

要求

  1. 设置相同的初始状态和目标状态,针对不同的估价函数,求得问题的解,比较它们对搜索算法性能的影响,包括扩展节点数、生成节点数等,填入表1。
  2. 设置与上述1相同的初始状态和目标状态,用宽度优先搜索算法(即令估计代价h(n)=0的A*算法)求得问题的解,以及搜索过程中的扩展节点数、生成节点数,填入表1。

表1:不同启发函数h(n)求解8数码问题的结果比较

项目 启发函数h(n) 启发函数h(n) 启发函数h(n)
初始状态 不在位数 哈密顿距离 0
初始状态 283164705 283164705 283164705
目标状态 123804765 123804765 123804765
最优解 最佳路径长度为:5最佳路径为:第0层的状态:2 8 3 1 6 4 7 0 5第1层的状态:2 8 3 1 0 4 7 6 5 第2层的状态:2 0 3 1 8 4 7 6 5 第3层的状态: 0 2 3 1 8 4 7 6 5 第4层的状态: 1 2 3 0 8 4 7 6 5 第5层的状态: 1 2 3 8 0 4 7 6 5 最佳路径长度为:5 最佳路径为: 第0层的状态:2 8 3 1 6 4 7 0 5 第1层的状态: 2 8 3 1 0 4 7 6 5第2层的状态: 2 0 3 1 8 4 7 6 5 第3层的状态: 0 2 3 1 8 4 7 6 5 第4层的状态: 1 2 3 0 8 4 7 6 5 第5层的状态: 1 2 3 8 0 4 7 6 5 最佳路径长度为:5 最佳路径为: 第0层的状态: 2 8 3 1 6 4 7 0 5 第1层的状态: 2 8 3 1 0 4 7 6 5 第2层的状态: 2 0 3 1 8 4 7 6 5 第3层的状态: 0 2 3 1 8 4 7 6 5 第4层状态: 1 2 3 0 8 4 7 6 5 第5层的状态: 1 2 3 8 0 4 7 6 5
扩展节点数(不包括叶子节点) 13 11 51
生成节点数(包含叶子节点) 6 5 84
运行时间(迭代次数) 15ms 15ms 62ms

报告结果

  1. 画出[2, 8, 3], [1, 6, 4], [7, 0, 5]推导至[1, 2, 3], [8, 0, 4], [7, 6, 5]的图解。(拍照、截屏粘贴即可)
    下面三张图片分别为不在位数、哈密顿距离与无启发函数从[2, 8, 3], [1, 6, 4], [7, 0, 5]推导至[1, 2, 3], [8, 0, 4], [7, 6, 5]的的图解。
    图 1 不在位数推导图解图 1 不在位数推导图解
    图 2 哈密顿距离推导图解
    图 2 哈密顿距离推导图解
    图 3 无启发函数时的推到图解图 3 无启发函数时的推到图解

  2. 分析不同的估价函数对A*算法性能的影响。

本次实验设计了两类估价函数(不在位数与哈密顿距离)来探讨估价函数对A*算法性能的影响,同时也设计了估价函数为0的情况作为对照。

以八数码问题为例,要分析不同的估价函数对A*算法性能的影响,可以先确定八数码的起点与终点,并观察起点与终点相同时启发函数分别为0、不在位数和哈密顿距离时对试验的影响。

通过对表1中的数据进行对比可以发现,估价函数为0(即使用宽度优先算法)时其生成的节点数目与扩展节点数目明显多于使用另外两种估价函数的结果。因此可以推断在八数码问题上使用估价函数时A*算法性能明显优于不使用估价函数。

而在使用估价函数时可以看出,估价函数分别为哈密顿数和不在位数时,二者运行时间相近,但前者的生成节点数与扩展节点数都要小于后者,说明在八数码问题上采用哈密顿距离作为启发函数比不在位数为启发函数的性能要好。

可以总结出用哈密顿距离作为估价函数时A*算法要略优与使用不在位数作为股价和函数的情况,且两种估价函数性能远远优于不使用估价函数。

  1. 根据宽度优先搜索算法和A*算法求解8数码问题的结果,分析启发式搜索的特点。

根据表1的结果,我们可以发现使用A*算法求解八数码问题从时间与节点数两个角度分析都要优于宽度优先搜索算法。说明在解决八数码问题时选用合适的估价函数能够大大提升搜索效率。

启发式搜索相对于宽度优先搜索来说一般能减少搜索时间,但是从理论上来讲,当时即路径估计值大于实际值就会出现找不到最优解的情况。
说明对于A算法来说启发性的信息过强反而不利于算法的运行,使用A算法时需要认真考虑启发性函数的合理性。

源代码

#include "iostream"  
#include "stdlib.h"  
#include "conio.h"  
#include <math.h>
#include <windows.h>
#define size 3  
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 4703)
using namespace std;
//定义二维数组来存储数据表示某一个特定状态  
typedef int status[size][size];
struct SpringLink;
//定义状态图中的结点数据结构  
typedef struct Node
{
    status data;//结点所存储的状态 ,一个3*3矩阵 
    struct Node* parent;//指向结点的父亲结点  
    struct SpringLink* child;//指向结点的后继结点  
    struct Node* next;//指向open或者closed表中的后一个结点  
    int fvalue;//结点的总的路径  
    int gvalue;//结点的实际路径  
    int hvalue;//结点的到达目标的困难程度  
}NNode, * PNode;

//定义存储指向结点后继结点的指针的地址  
typedef struct SpringLink
{
    struct Node* pointData;//指向结点的指针  
    struct SpringLink* next;//指向兄第结点  
}SPLink, * PSPLink;

PNode open;
PNode closed;
//OPEN表保存所有已生成而未考察的节点,CLOSED表中记录已访问过的节点

int t = 0; //迭代次数,相当于运行时间 
int count_extendnode = 0;//扩展结点 
int count_sumnode = 0; //生成节点 
status startt = { 2,8,3,1,6,4,7,0,5 }; //实验报告
status target = { 1,2,3,8,0,4,7,6,5 };
//初始化一个空链表  
void initLink(PNode& Head)
{
    Head = (PNode)malloc(sizeof(NNode));
    Head->next = NULL;
}

//判断链表是否为空  
bool isEmpty(PNode Head)
{
    if (Head->next == NULL)
        return true;
    else
        return false;
}

//从链表中拿出一个数据  
void popNode(PNode& Head, PNode& FNode)
{
    if (isEmpty(Head))
    {
        FNode = NULL;
        return;
    }
    FNode = Head->next;
    Head->next = Head->next->next;
    FNode->next = NULL;
}

//向结点的最终后继结点链表中添加新的子结点  
void addSpringNode(PNode& Head, PNode newData)
{
    PSPLink newNode = (PSPLink)malloc(sizeof(SPLink));
    newNode->pointData = newData;

    newNode->next = Head->child;
    Head->child = newNode;
}

//释放状态图中存放结点后继结点地址的空间  
void freeSpringLink(PSPLink& Head)
{
    PSPLink tmm;

    while (Head != NULL)
    {
        tmm = Head;
        Head = Head->next;
        free(tmm);
    }
}

//释放open表与closed表中的资源  
void freeLink(PNode& Head)
{
    PNode tmn;

    tmn = Head;
    Head = Head->next;
    free(tmn);

    while (Head != NULL)
    {
        //首先释放存放结点后继结点地址的空间  
        freeSpringLink(Head->child);
        tmn = Head;
        Head = Head->next;
        free(tmn);
    }
}

//向普通链表中添加一个结点  
void addNode(PNode& Head, PNode& newNode)
{
    newNode->next = Head->next;
    Head->next = newNode;
}

//向非递减排列的链表中添加一个结点(已经按照权值进行排序)  
void addAscNode(PNode& Head, PNode& newNode)
{

    PNode P;
    PNode Q;

    P = Head->next;
    Q = Head;
    while (P != NULL && P->fvalue < newNode->fvalue)
    {
        Q = P;
        P = P->next;
    }
    //上面判断好位置之后,下面就是简单的插入了  
    newNode->next = Q->next;
    Q->next = newNode;
}

//计算结点的f,g,h值  
void computeAllValue(PNode& theNode, PNode parentNode)
{
    if (parentNode == NULL)
        theNode->gvalue = 0;
    else
        theNode->gvalue = parentNode->gvalue + 1;

       /* theNode->hvalue = computeHValue(theNode); */ 
    theNode->hvalue = 0;
    theNode->fvalue = theNode->gvalue + theNode->hvalue;
}

//初始化函数,进行算法初始条件的设置  
void initial()
{
    //初始化open以及closed表  
    initLink(open);
    initLink(closed);

    //初始化起始结点,令初始结点的父节点为空结点  
    PNode NULLNode = NULL;
    PNode Start = (PNode)malloc(sizeof(NNode));
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Start->data[i][j] = startt[i][j];
        }
    }
    Start->parent = NULL;
    Start->child = NULL;
    Start->next = NULL;
    computeAllValue(Start, NULLNode);

    //起始结点进入open表	  
    addAscNode(open, Start);

}

//将B节点的状态赋值给A结点  
void statusAEB(PNode& ANode, PNode BNode)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            ANode->data[i][j] = BNode->data[i][j];
        }
    }
}

//两个结点是否有相同的状态  
bool hasSameStatus(PNode ANode, PNode BNode)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (ANode->data[i][j] != BNode->data[i][j])
                return false;
        }
    }
    return true;
}

//结点与其祖先结点是否有相同的状态  
bool hasAnceSameStatus(PNode OrigiNode, PNode AnceNode)
{
    while (AnceNode != NULL)
    {
        if (hasSameStatus(OrigiNode, AnceNode))
            return true;
        AnceNode = AnceNode->parent;
    }
    return false;
}

//取得方格中空的格子的位置  
void getPosition(PNode theNode, int& row, int& col)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (theNode->data[i][j] == 0)
            {
                row = i;
                col = j;
                return;
            }
        }
    }
}

//交换两个数字的值  
void changeAB(int& A, int& B)
{
    int C;
    C = B;
    B = A;
    A = C;
}

//检查相应的状态是否在某一个链表中  
bool inLink(PNode spciNode, PNode theLink, PNode& theNodeLink, PNode& preNode)
{
    preNode = theLink;
    theLink = theLink->next;

    while (theLink != NULL)
    {
        if (hasSameStatus(spciNode, theLink))
        {
            theNodeLink = theLink;
            return true;
        }
        preNode = theLink;
        theLink = theLink->next;
    }
    return false;
}

//产生结点的后继结点(与祖先状态不同)链表  
void SpringLink(PNode theNode, PNode& spring)
{
    int row;
    int col;

    getPosition(theNode, row, col);

    //空的格子右边的格子向左移动  
    if (col != 2)
    {
        PNode rlNewNode = (PNode)malloc(sizeof(NNode));
        statusAEB(rlNewNode, theNode);
        changeAB(rlNewNode->data[row][col], rlNewNode->data[row][col + 1]);
        if (hasAnceSameStatus(rlNewNode, theNode->parent))
        {
            free(rlNewNode);//与父辈相同,丢弃本结点  
        }
        else
        {
            rlNewNode->parent = theNode;
            rlNewNode->child = NULL;
            rlNewNode->next = NULL;
            computeAllValue(rlNewNode, theNode);
            //将本结点加入后继结点链表  
            addNode(spring, rlNewNode);
        }
    }
    //空的格子左边的格子向右移动  
    if (col != 0)
    {
        PNode lrNewNode = (PNode)malloc(sizeof(NNode));
        statusAEB(lrNewNode, theNode);
        changeAB(lrNewNode->data[row][col], lrNewNode->data[row][col - 1]);
        if (hasAnceSameStatus(lrNewNode, theNode->parent))
        {
            free(lrNewNode);//与父辈相同,丢弃本结点  
        }
        else
        {
            lrNewNode->parent = theNode;
            lrNewNode->child = NULL;
            lrNewNode->next = NULL;
            computeAllValue(lrNewNode, theNode);
            //将本结点加入后继结点链表  
            addNode(spring, lrNewNode);
        }
    }
    //空的格子上边的格子向下移动  
    if (row != 0)
    {
        PNode udNewNode = (PNode)malloc(sizeof(NNode));
        statusAEB(udNewNode, theNode);
        changeAB(udNewNode->data[row][col], udNewNode->data[row - 1][col]);
        if (hasAnceSameStatus(udNewNode, theNode->parent))
        {
            free(udNewNode);//与父辈相同,丢弃本结点  
        }
        else
        {
            udNewNode->parent = theNode;
            udNewNode->child = NULL;
            udNewNode->next = NULL;
            computeAllValue(udNewNode, theNode);
            //将本结点加入后继结点链表  
            addNode(spring, udNewNode);
        }
    }
    //空的格子下边的格子向上移动  
    if (row != 2)
    {
        PNode duNewNode = (PNode)malloc(sizeof(NNode));
        statusAEB(duNewNode, theNode);
        changeAB(duNewNode->data[row][col], duNewNode->data[row + 1][col]);
        if (hasAnceSameStatus(duNewNode, theNode->parent))
        {
            free(duNewNode);//与父辈相同,丢弃本结点  
        }
        else
        {
            duNewNode->parent = theNode;
            duNewNode->child = NULL;
            duNewNode->next = NULL;
            computeAllValue(duNewNode, theNode);
            //将本结点加入后继结点链表  
            addNode(spring, duNewNode);
        }
    }
}

//输出给定结点的状态  
void outputStatus(PNode stat)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            cout << stat->data[i][j] << " ";
        }
        cout << endl;
    }
}

//输出最佳的路径  
void outputBestRoad(PNode goal)
{
    int deepnum = goal->gvalue;

    if (goal->parent != NULL)
    {
        outputBestRoad(goal->parent);
    }
    cout << "第" << deepnum-- << "层的状态:" << endl;
    outputStatus(goal);
}

void AStar()
{

    PNode tmpNode;//指向从open表中拿出并放到closed表中的结点的指针  
    PNode spring;//tmpNode的后继结点链  
    PNode tmpLNode;//tmpNode的某一个后继结点  
    PNode tmpChartNode;

    PNode thePreNode;//指向将要从closed表中移到open表中的结点的前一个结点的指针  
    bool getGoal = false;//标识是否达到目标状态  
    long numcount = 1;//记录从open表中拿出结点的序号  

    initial();//对函数进行初始化  
    initLink(spring);//对后继链表的初始化  
    tmpChartNode = NULL;
    //Target.data=target;
    cout << "1" << endl;
    PNode Target = (PNode)malloc(sizeof(NNode));
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            Target->data[i][j] = target[i][j];
        }
    }
    cout << "1" << endl;
    cout << "从open表中拿出的结点的状态及相应的值" << endl;

    while (!isEmpty(open))
    {
        t++;
        //从open表中拿出f值最小的元素,并将拿出的元素放入closed表中  
        popNode(open, tmpNode);
        addNode(closed, tmpNode);
        count_extendnode = count_extendnode + 1;

        cout << "第" << numcount++ << "个状态是:" << endl;
        outputStatus(tmpNode);
        cout << "其f值为:" << tmpNode->fvalue << endl;
        cout << "其g值为:" << tmpNode->gvalue << endl;
        cout << "其h值为:" << tmpNode->hvalue << endl;


         /*//如果拿出的元素是目标状态则跳出循环
         if(computeHValue(tmpNode) == 0)
         {  count_extendnode=count_extendnode-1;
             getGoal = true;
             break;
         }*/
         //如果拿出的元素是目标状态则跳出循环  
        if (hasSameStatus(tmpNode, Target) == true)
        {
            count_extendnode = count_extendnode - 1;
            getGoal = true;
            break;
        }

        //产生当前检测结点的后继(与祖先不同)结点列表,产生的后继结点的parent属性指向当前检测的结点  
        SpringLink(tmpNode, spring);

        //遍历检测结点的后继结点链表  
        while (!isEmpty(spring))
        {
            popNode(spring, tmpLNode);
            //状态在open表中已经存在,thePreNode参数在这里并不起作用  
            if (inLink(tmpLNode, open, tmpChartNode, thePreNode))
            {
                addSpringNode(tmpNode, tmpChartNode);
                if (tmpLNode->gvalue < tmpChartNode->gvalue)
                {
                    tmpChartNode->parent = tmpLNode->parent;
                    tmpChartNode->gvalue = tmpLNode->gvalue;
                    tmpChartNode->fvalue = tmpLNode->fvalue;
                }
                free(tmpLNode);
            }
            //状态在closed表中已经存在  
            else if (inLink(tmpLNode, closed, tmpChartNode, thePreNode))
            {
                addSpringNode(tmpNode, tmpChartNode);
                if (tmpLNode->gvalue < tmpChartNode->gvalue)
                {
                    PNode commu;
                    tmpChartNode->parent = tmpLNode->parent;
                    tmpChartNode->gvalue = tmpLNode->gvalue;
                    tmpChartNode->fvalue = tmpLNode->fvalue;
                    freeSpringLink(tmpChartNode->child);
                    tmpChartNode->child = NULL;
                    popNode(thePreNode, commu);
                    addAscNode(open, commu);
                }
                free(tmpLNode);
            }
            //新的状态即此状态既不在open表中也不在closed表中  
            else
            {
                addSpringNode(tmpNode, tmpLNode);
                addAscNode(open, tmpLNode);
                count_sumnode += 1;//生成节点			 
            }
        }
    }

    //目标可达的话,输出最佳的路径  
    if (getGoal)
    {
        cout << endl;
        cout << "最佳路径长度为:" << tmpNode->gvalue << endl;
        cout << "最佳路径为:" << endl;
        outputBestRoad(tmpNode);
    }

    //释放结点所占的内存  
    freeLink(open);
    freeLink(closed);
    //    getch();  
}

int main()
{
    double start = GetTickCount();
    AStar();
    printf("生成节点数目:%d\n", count_sumnode);
    printf("扩展节点数目:%d\n", count_extendnode);
    printf("运行时间:%f\n", GetTickCount() - start);
    return 0;
}


心得体会

本次实验是考察A算法的运用,初次接触编写A算法的时候我实在是无从下手,但好在A算法是一个热门且通用性很大的算法,在网上可以找到许多A算法的解决方案。通过在网上查找资料与朋友和同学们的热心帮助,我逐步编写出了八数码问题A*算法的解决方案代码。

在实验的过程中我遇见了一些困难,最有代表性的就是编写程序时指针的运用,这些指针和链表操作让编程水平不算高的我犯了难。好在我的朋友们里有熟悉链表操作的,经过他给我几个小时的讲解并通过远程桌面的方式为我进行举例演示后我总算能勉强去运用链表了。虽然我仍然不是很熟悉指针的运用,但我会在后续的学习生活中努力恶补这方面的知识,提升自我。

令我高兴的是我最终顺利的完成了实验,也通过实机操作并与书本上的理论讲解相结合的方式加深了对A算法的了解,掌握了A算法的使用方法与使用条件。果然实践才是提升自己的最好方法。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐