A/A*算法解决八数码问题(C++实现)

一、实验原理

1.状态图搜索
1.1搜索树:搜索过程中经过的节点和边按原图的连接关系构成一个树型的有向图,称为搜索树。
1.2搜索方式
树式搜索:记录搜索过程中所经过的所有节点和边
1.3路径的获得
树式搜索:反向求解

2.搜索算法
2.1 CLOSED表和OPEN表
closed表对树式搜索来说存储的是正在成长的搜索树,对线式搜索来说存储的是不断伸长的折线,本身就是所求的路径。
open表存储当前待考查的节点。
2.2树式搜索算法
步1 把初始节点放入OPEN表;
步2 检查OPEN表,若为空,则问题无解,退出;
步3 移出OPEN表中第一个节点N并放入CLOSED表中,并编号为n;
步4 考察节点N是否为目标节点,若是,则搜索成功,退出;
步5 若N不可扩展,则转步2;
步6 扩展节点N,生成所有子节点,对这组子节点作如下处理:
(1)如果有节点N的先辈节点,则删除;
(2)如果有已存在于OPEN表的节点,也删除;但删除之前要比较其返回初始节点的新路径与原路径,如果新路径“短”,则修改这些节点在OPEN表中的原指向父节点的指针,使其指向新的父节点。
(3)如果有已存在于CLOSED表中的节点,则作与(2)同样的处理,并且再将其移出CLOSED表,放入OPEN表重新扩展;
(4)对其余子节点,配上指向父节点n的指针后放入OPEN表,对OPEN表按某种搜索策略排序后转步2。

3.启发函数
用来估计搜索树上节点X与目标节点Sg接近程度的函数,记为h(x)。

4.估价函数
f(x)=g(x)+h(x); 其中g(x)是代价函数,h(x)是启发函数。 或定义为:f(x)=d(x)+h(x); d(x)是x的深度。

二、实验内容

1.定义代价函数G(x)和启发函数H(X),以A算法进行求解。
2.输入初始状态和目标状态。
3.输出从初始状态到目标状态的路线。

三、实验结果

大苏打在这里插入图片描述

四、源代码

//此代码仅可实现简单的八数码问题
//把注释去掉即为A*算法
#include <iostream>
#include <queue>
#include <stack>
#include <vector>
#include <algorithm>
#include <memory.h>
using namespace std;

class Node{ //节点类
    public:
        int Node[3][3];         //每个节点用一个二维数组表示
        class Node *parent;     //节点的先辈节点
        class Node *next;       //节点的扩展节点
};

bool cmp(const Node &s1, const Node &s2){//sort函数的排序规则,返回估价函数值最大的节点
    int gx1 = 0, gx2 = 0;//gx为从初始节点到该节点已经付出的代价
    int hx1 = 0, hx2 = 0;//hx为该节点到达目的节点的接近程度估计值
    const Node *ptr1 = &s1;
    const Node *ptr2 = &s2;
    int Node[3][3] = {{1,2,3},{8,0,4},{7,6,5}};
    while(ptr1 != NULL){
        gx1 += 1;
        ptr1 = ptr1->parent;
    }
    while(ptr2 != NULL){
        gx2 += 1;
        ptr2 = ptr2->parent;
    }
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 3; j++){
            if(s1.Node[i][j] != Node[i][j]){
                hx1 += 1;
            }
            if(s2.Node[i][j] != Node[i][j]){
                hx2 += 1;
            }
        }
    }
    return (gx1+hx1) > (gx2+hx2);
}

bool IsEqual(Node n1,Node n2){
    for (int i = 0; i < 3;i++)
        for (int j = 0; j < 3;j++)
            if(n1.Node[i][j]!=n2.Node[i][j])
                return false;
    return true;
}
class A_algorithm{//A算法搜索
    public:
        A_algorithm(Node *S0, Node *Sg);        //构造函数
        int IsSg(Node *S) {return memcmp(&S->Node, &Sg.Node, sizeof(int) * 9);} // 判断是否是目标状态
        int Nextnode(Node *S);                  // 下一个可行的节点
        //int Cantor(int Node[][3]);            // 康托展开
        void algorithm();                       // A算法
        void PrintPath(Node *head);             // 打印路径
        void FreeCLOSED(Node *CLOSED);          // 释放close表
    private:
        //unsigned char allHash[362880];
        Node S0;                                //初始节点
        Node Sg;                                //目标节点
        int Nextnum;
        Node next[4];                           //扩展节点
};
A_algorithm::A_algorithm(Node *S0, Node *Sg){
    memcpy(&this->S0.Node, &S0->Node, sizeof(int)*9);
    this->S0.parent = NULL;
    this->S0.next = NULL;
    memcpy(&this->Sg.Node, &Sg->Node, sizeof(int)*9);
    this->Sg.parent = NULL;
    this->Sg.next = NULL;
}
int A_algorithm::Nextnode(Node *S){//返回可扩展节点的个数
    Nextnum = 0;
    int posi, posj;
    for (int i = 0; i < 3; i++)
        for (int j=0;j<3;j++)
            if(S->Node[i][j] == 0){
                posi = i;posj = j;//定位到S节点的空位置处
                break;
             }
    if(posi-1 >= 0){//向上扩展
        Node up = *S;
        up.Node[posi][posj] = up.Node[posi-1][posj];
        up.Node[posi - 1][posj] = 0;
        next[Nextnum] = up;
        next[Nextnum].parent = S;
        Nextnum++;
        /*
        if(allHash[Cantor(up.Node)] == 0){
            next[Nextnum] = up;
            next[Nextnum].parent = S;
            Nextnum++;
        }
        */
    }
    if(posi+1 <= 2){//向下扩展
        Node down = *S;
        down.Node[posi][posj] = down.Node[posi+1][posj];
        down.Node[posi + 1][posj] = 0;
        next[Nextnum] = down;
        next[Nextnum].parent = S;
        Nextnum++;
        /*
        if(allHash[Cantor(down.Node)] == 0){
            next[Nextnum] = down;
            next[Nextnum].parent = S;
            Nextnum++;
        }
        */
    }
    if(posj-1 >= 0){//向左扩展
        Node left = *S;
        left.Node[posi][posj] = left.Node[posi][posj-1];
        left.Node[posi][posj - 1] = 0;
        next[Nextnum] = left;
        next[Nextnum].parent = S;
        Nextnum++;
        /*
        if(allHash[Cantor(left.Node)] == 0){
            next[Nextnum] = left;
            next[Nextnum].parent = S;
            Nextnum++;
        }
        */
    }
    if(posj+1 <= 2){//向右扩展
        Node right = *S;
        right.Node[posi][posj] = right.Node[posi][posj+1];
        right.Node[posi][posj + 1] = 0;
        next[Nextnum] = right;
        next[Nextnum].parent = S;
        Nextnum++;
        /*
        if(allHash[Cantor(right.Node)] == 0){
            next[Nextnum] = right;
            next[Nextnum].parent = S;
            Nextnum++;
        }
        */
    }
    return Nextnum;
}
/*
int A_algorithm::Cantor(int Node[][3]){
    int fac[10] = {1,1,2,6,24,120,720,5040,40320,362880};
    int index = 0;
    for(int i = 7; i >= 0; i--){
        int irow = i / 3, icol = i - i / 3 * 3;
        int count = 0;
        for(int j = 8; j > i; j--){
            int jrow = j / 3, jcol = j - j / 3 * 3;
            if(Node[jrow][jcol] < Node[irow][icol]){
                count++;
            }
        }
        index += (count*fac[8-i]);
    }
    return index;
}
*/
void A_algorithm::algorithm(){
    int step = 0;
    //memset(allHash, 0, 362880);//把allHash数组的值全部赋为‘0‘字符
    vector<Node> OPEN;//生成一个node类型的数组OPEN
    Node *CLOSED = new Node;;
    Node *current = CLOSED;
    Node *N;
    OPEN.push_back(S0);//把初始节点S0放入OPEN表
    while(!OPEN.empty()){
        N = new Node;
        *N = OPEN[OPEN.size()-1]; OPEN.pop_back();//移出OPEN表的最后一个节点
        step++;
        current->next = N;//把N放入CLOSED表中
        current = current->next;
        if(IsSg(N) == 0){       //如果目标节点Sg=N,则搜索成功,结束
            PrintPath(N);       //打印出N
            //cout << step<<endl;
            FreeCLOSED(CLOSED); //释放CLOSED表
            return;
        }
        int Nextnum = Nextnode(N);//扩展N节点,并取N节点的可扩展节点数
        if(Nextnum == 0)//如果N不可扩展则继续
            continue;
        for(int i = 0; i < Nextnum; i++)
            /*
            for (int j = 0; j < OPEN.size();j++){
                if(IsEqual(OPEN[j], next[i])&&IsEqual(OPEN[j], *N->parent)){

                }
            }
            */
            OPEN.push_back(next[i]);
        sort(OPEN.begin(), OPEN.end(), cmp);//对OPEN表的节点按照估价函数的值进行从大到小排序,每次取估价函数值最小的节点即表中的最后一个节点
        /*
        cout << step << " OPEN TABLE:" << endl;
        for (int i = 0; i < OPEN.size();i++)
            PrintPath(&OPEN[i]);
        */
    }
    cout << "Failed." << endl;
    FreeCLOSED(CLOSED);
    return;
}
void A_algorithm::PrintPath(Node *head){
    if(head == NULL){
        return;
    }
    else{
        PrintPath(head->parent);
        for(int i = 0; i < 3; i++){
            for (int j = 0; j < 3; j++) {
                if (head->Node[i][j] == 0)
                    cout << "  ";
                else
                    cout << head->Node[i][j]<<" ";
            }
            cout << endl;
        }
        cout <<endl;
    }
}
void A_algorithm::FreeCLOSED(Node *CLOSED){
    Node *current;
    while(CLOSED != NULL){
        current = CLOSED->next;
        free(CLOSED);
        CLOSED = current;
    }
}
int main() {
/*
    Node S0; S0.next = NULL; S0.parent = NULL;
    Node Sg; Sg.next = NULL; Sg.parent = NULL;
    cout<<"Please input initial Node:"<<endl;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            cin >> S0.Node[i][j];
    cout<<"Please input goal Node:"<<endl;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            cin >> Sg.Node[i][j];
*/
    Node S0 = {{{2, 8, 3}, {1, 0, 4}, {7, 6, 5}}, 0, NULL};//初始化初始节点
    Node Sg = {{{1, 2, 3}, {8, 0, 4}, {7, 6, 5}}, 0, NULL};//初始化目标节点
    cout << "SearchPath: " << endl<<endl;
    A_algorithm(&S0, &Sg).algorithm();
    return 0;
}

  • 13
    点赞
  • 107
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
A*算法求解八数码问题 1、A*算法基本思想: 1)建立一个队列,计算初始结点的估价函数f,并将初始结点入队,设置队列头和尾指针。 2)取出队列头(队列头指针所指)的结点,如果该结点是目标结点,则输出路径,程序结束。否则对结点进行扩展。 3)检查扩展出的新结点是否与队列中的结点重复,若与不能再扩展的结点重复(位于队列头指针之前),则将它抛弃;若新结点与待扩展的结点重复(位于队列头指针之后),则比较两个结点的估价函数中g的大小,保留较小g值的结点。跳至第五步。 4)如果扩展出的新结点与队列中的结点不重复,则按照它的估价函数f大小将它插入队列中的头结点后待扩展结点的适当位置,使它们按从小到大的顺序排列,最后更新队列尾指针。 5)如果队列头的结点还可以扩展,直接返回第二步。否则将队列头指针指向下一结点,再返回第二步。 2、程序运行基本环境: 源程序所使用编程语言:C# 编译环境:VS2010,.net framework 4.0 运行环境:.net framework 4.0 3、程序运行界面 可使用程序中的test来随机生成源状态与目标状态 此停顿过程中按Enter即可使程序开始运行W(n)部分; 此停顿部分按Enter后程序退出; 4、无解问题运行情况 这里源程序中是先计算源状态与目标状态的逆序对的奇偶性是否一致来判断是否有解的。下面是无解时的运行画面: 输入无解的一组源状态到目标状态,例如: 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 8 7 0 运行画面如下: 5、性能比较 对于任一给定可解初始状态,状态空间有9!/2=181440个状态;当采用不在位棋子数作为启发函数时,深度超过20时,算法求解速度较慢; 其中启发函数P(n)与W(n)的含义如下: P(n): 任意节点与目标结点之间的距离; W(n): 不在位的将牌数; 源状态 目标状态 P(n) 生成节点数 W(n) 生成节点数 P(n) 扩展节点数 W(n) 扩展节点数 2 8 3 1 6 4 7 0 5 1 2 3 8 0 4 7 6 5 11 13 5 6 1 2 3 8 0 4 7 6 5 0 1 3 8 2 4 7 6 5 6 6 2 2 4 8 2 5 1 6 7 0 3 7 4 2 8 5 6 1 3 0 41 79 22 46 6 2 5 8 7 0 3 1 4 0 3 6 7 1 8 4 5 2 359 10530 220 6769 7 6 3 1 0 4 8 5 2 2 8 7 1 3 4 6 5 0 486 8138 312 5295 下图是解决随机生成的100中状态中,P(n)生成函数的生成节点与扩展节点统计图: 由上图可知,P(n)作为启发函数,平均生成节点数大约在1000左右,平均扩展节点数大约在600左右; 下图是解决随机生成的100中状态中,W(n)生成函数的生成节点与扩展节点统计图: 由上图可知,W (n)作为启发函数,平均生成节点数大约在15000左右,是P(n)作为启发函数时的平均生成节点的15倍;W (n)作为启发函数,平均扩展节点数大约在10000左右,是P(n)作为启发函数时的平均扩展节点的15倍; 下图是解决随机生成的100中状态中,两个生成函数的生成节点与扩展节点统计图: 由上述图表可以看到,将P(n)作为启发函数比将W(n)作为启发函数时,生成节点数与扩展节点数更稳定,相比较来说,采用P(n)作为启发函数的性能比采用W(n)作为启发函数的性能好。 6、源代码说明 1)AStar-EightDigital-Statistics文件夹:用来随机生成100个状态,并对这100个状态分别用P(n)与W(n)分别作为启发函数算出生成节点以及扩展节点,以供生成图表使用;运行界面如下: 2)Test文件夹:将0-8这9个数字随机排序,用来随机生成源状态以及目标状态的;运行界面如下: 3)AStar-EightDigital文件夹:输入源状态和目标状态,程序搜索出P(n)与W(n)分别作为启发函数时的生成节点数以及扩展节点数,并给出从源状态到目标状态的移动步骤;运行界面如下: 提高了运行速度的几处编码思想: 1、 在维护open以及close列表的同时,也维护一个类型为hashtable的open以及close列表,主要用来提高判断当前节点是否在open列表以及close列表中出现时的性能; 2、 对于每个状态,按照从左到右,从上到下,依次将数字拼接起来,形成一个唯一标识identify,通过该标识,可以直接判断两个状态是否是同一个状态,而不需要循环判断每个位置上的数字是否相等 3、 在生成每个状态的唯一标识identify时,同时计算了该状态的空格所在位置,通过空格所在位置,可以直接判断能否进行上移、下移、左移、右移等动作; 4、 只计算初始节点的h值,其它生成的节点的h值是根据当前状态的h值、移动的操作等计算后得出的,规则如下: a) 采用W(n)这种方式,不在位置的将牌数,共有以下3中情况: i. 该数字原不在最终位置上,移动后,在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值-1 ii. 该数字原在最终位置上,移动后,不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 +1 iii. 该数字原不在最终位置上,移动后,还是不在其最终位置上 这种情况下,生成的子节点的h值= 父节点的h值 iv. 该数字原在最终位置上,移动后,还在其最终位置 这种情况不存在 b) 采用P(n)这种方式,节点与目标距离,可通过下面3步完成 i. 首先计算在原位置时,与目标位置的距离,命名为Distance1 ii. 移动后,计算当前位置与目标位置的距离,命名为Distance2 iii. 计算子节点的h值: 子节点的h值 = 父节点的h值- Distance1+ Distance2 5、 在任意状态中的每个数字和目标状态中同一数字的相对距离就有9*9种,可以先将这些相对距离算出来,用一个矩阵存储,这样只要知道两个状态中同一个数字的位置,就可查出它们的相对距离,也就是该数字的偏移距离;例如在一个状态中,数字8的位置是3,在另一状态中位置是7,那么从矩阵的3行7列可找到2,它就是8在两个状态中的偏移距离。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值