A*算法实现九宫重排

基础知识介绍

  1. 问题描述

九宫重排问题即八数码问题,是在一个3*3的方格盘上放置1-8八个数字和一个宫格,开始的数字状态随机给出,通过控制空格的上下左右移动实现数字形成有序的或指定的顺序。
在这里插入图片描述

  1. A*算法

A算法与A算法的区别在于A算法使用了启发式搜索而不是盲目搜索,盲目搜索包括广度优先搜索、深度优先搜索等。

  1. 启发式搜索

给出了评价函数的定义: f(n) = g(n) + h(n)
其中,n为待评价的节点;g(n)为从初始节点s到节点n的最佳路径耗散值的估计值;h(n)为从节点n到目标节点t的最佳路径耗散值的估计值,称为启发函数;f(n)为从初始节点s经过节点n到达目标节点的最佳路径耗散值的估计值,成为评价函数,我们每次从叶节点选出f(n)最小的节点扩展。如果启发函数满足 h(n) <= h*(n), 则可以证明当问题有解时,A算法一定可以得到一个耗散值最小的结果。
取g(n)为层数,h(n)为不在位的数字的数量
在这里插入图片描述

代码说明

  1. 设置一个变量OPEN用于存放那些搜索图上的叶节点,也就是已经被生成出来,但是还没被扩展的节点;变量CLOSE用于存放图中的非叶节点,也就是不但被生成出来,还已经被扩展的节点。
  2. OPEN中的节点按照 f 值从小到大排列。每次从OPEN表中取出第一个元素 n 进行扩展,如果 n 是目标节点,则算法找到一个解,算法结束,否则扩展 n
  3. 对于 n 的子节点 m ,如果 m 既不在OPEN也不在CLOSE, 则将 m 加入OPEN; 如果 m 在CLOSE, 说明从初始节点到 m 有两条路径, 如果新路径耗散值大,什么都不做;如果较小,则将 m (新找到的)从CLOSE中取出放入OPEN中(删除原来在CLOSE的,将新找到的放入OPEN)
  4. 重复 1 ,知道找到一个解结束;或者OPEN为空算法以失败结束,说明无解

代码内容

C++.代码实现

#include <iostream>
using namespace std;

#define MAXLISTSIZE 10000
#define MAXSTEPSIZE 100

/*
定义八数码节点结构体
status存储节点的状态(即八数码的排列),G存储走的是第几步,H存储不在位的将牌数,F存储总耗散值,Zero存储‘0’将牌所在位置,
step存储该节点是上一节点通过怎样的移动得到的(1左2右3上4下)
*/
struct ENode
{
    int status[9];
    int G;
    int H;
    int F;
    int Zero;
    int step;
    ENode *Parent;
};

//最终状态
int FinalStatus[9] = { 1, 2, 3, 8, 0, 4, 7, 6, 5 };

//定义OPEN表和CLOSE表,open和close是表中最后一个内容的下一位序号
ENode OPEN[MAXLISTSIZE];
ENode CLOSE[MAXLISTSIZE];
int open = 0;
int close = 0;

ENode *Node;

/*
计算不在位的将牌数H
返回 H
*/
int CountH(int *status)
{
    int H = 0;
    int i;
    for (i = 0; i <= 8; i++)
    {
        if (FinalStatus[i] != status[i])
        {
            H++;
        }
    }
    return H;
}

/*
判断新生成的节点是否已经存在于OPEN表或CLOSE表中
返回 表征是否存在于OPEN或CLOSE的值,值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表,|值|-1表示所在列表中的位置
*/
int Exist(ENode *N)
{
    int i, j;
    int H = 0;                      //计算不在位的将牌数,如果为0,则证明给函数的节点在表中已存在
    int status[9];

    Node = new ENode;
    Node = N;

    for (i = 0; i <= 8; i++)
    {
        status[i] = Node->status[i];
    }

    for (i = 0; i <= open - 1; i++)     //判断是否在OPEN表
    {
        for (j = 0; j <= 8; j++)
        {
            if (status[j] != OPEN[i].status[j])
            {
                H++;
            }
        }

        if (H == 0)                 //H=0证明在表中找到该节点
        {
            return i + 1;           //如果在OPEN表中,返回i(节点在OPEN的位置)+ 1(在OPEN找到该节点)
        }
        H = 0;                      //扫描完一个节点后重置H
    }

    for (i = 0; i <= close - 1; i++)     //判断是否在CLOSE表
    {
        for (j = 0; j <= 8; j++)
        {
            if (status[j] != CLOSE[i].status[j])
            {
                H++;
            }
        }

        if (H == 0)                 //H=0证明在表中找到该节点
        {
            return (-i) - 1;        //如果在CLOSE表中,返回-i(i为节点在CLOSE的位置)- 1(在CLOSE找到该节点)
        }
        H = 0;                      //扫描完一个节点后重置H
    }

    return 0;
}

/*
初始化节点
返回 初始化后的节点Node
*/
ENode *ENodeInit(int *status, int zero, int g, ENode *parent, int step)
{
    int i;
    Node = new ENode;
    for (i = 0; i <= 8; i++)
    {
        Node->status[i] = status[i];
    }
    Node->Zero = zero;
    Node->G = g;
    Node->H = CountH(Node->status);
    Node->F = Node->G + Node->H;
    Node->Parent = parent;
    Node->step = step;
    return Node;
}

/*
左移后的变化
返回 左移后的状态
*/
int *Left(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z - 1];
    status[z - 1] = 0;
    status[z] = temp;
    return status;
}

/*
右移后的变化
返回 右移后的状态
*/
int *Right(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z + 1];
    status[z + 1] = 0;
    status[z] = temp;
    return status;
}

/*
上移后的变化
返回 上移后的状态
*/
int *Up(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z - 3];
    status[z - 3] = 0;
    status[z] = temp;
    return status;
}

/*
下移后的变化
返回 下移后的状态
*/
int *Down(int *s, int z)
{
    int temp, i;
    static int status[9];
    for (i = 0; i <= 8; i++)
    {
        status[i] = s[i];
    }
    temp = status[z + 3];
    status[z + 3] = 0;
    status[z] = temp;
    return status;
}

/*
判断子节点是否在OPEN或CLOSE中,并进行对应的操作
返回值 NULL
*/
void ExistAndOperate(ENode *N)
{
    int i;
    int inList;                 //定义表示新生成节点是否在OPEN表或CLOSE表中, 值为0 均不在,值>0 只在OPEN表,值<0 只在CLOSE表

    Node = new ENode;
    Node = N;

    if (Node->G == 1)           //如果是第一步的节点,直接加入OPEN中,返回
    {
        OPEN[open] = *Node;
        open++;
        return;
    }

    inList = Exist(Node);       //判断新节点是否在OPEN或CLOSE中

    if (inList == 0)            //如果均不在两个表中,将节点加入OPEN表中
    {
        OPEN[open] = *Node;      //将拓展出的新结点加入到OPEN表中
        open++;
    }

    else if (inList > 0)             //如果在OPEN中,说明从初始节点到该节点找到了两条路径,保留耗散值短的那条路径
    {
        if (OPEN[inList - 1].F > Node->F)    //如果表内节点F值大于新节点F值,用新节点代替表内节点
        {
            OPEN[inList - 1] = *Node;
        }
    }

    else if (inList < 0)             //如果在CLOSE中,说明初始节点到该节点有两条路径,如果新找到的路径耗散值大,什么都不做,如果较小,将其从CLOSE中取出放入OPEN中    
    {
        inList = -inList;
        if (CLOSE[inList - 1].F > Node->F)       //如果较小
        {
            OPEN[open] = *Node;       //将其取出放入OPEN
            open++;
        }
        for (i = inList - 1; i <= close - 1; i++)     //将其在CLOSE中释放
        {
            CLOSE[i] = CLOSE[i + 1];
        }
        close--;
    }
}

/*
寻找最佳路径函数
返回 最后的节点Node
*/
ENode *Search()
{
    int *status;
    int i, j;

    ENode *Temp;

    while (1)                   //一直循环知道找到解结束
    {
        Temp = new ENode;
       
        for (i = open - 1; i > 0; i--)    //用冒泡排序给OPEN表里面的节点按耗散值进行排序
        {
            for (j = 0; j < i; j++)
            {
                if (OPEN[j].F > OPEN[j + 1].F)
                {
                    *Temp = OPEN[j + 1];
                    OPEN[j + 1] = OPEN[j];
                    OPEN[j] = *Temp;
                }
            }
        }

        Node = new ENode;
        *Node = OPEN[0];                 //从OPEN表中取出第一个元素(F值最小)进行扩展

        if (!CountH(Node->status))      //判断该节点是否是目标节点,若是,则不在位的将牌数为0,算法结束
        {
            break;
        }

        Temp = Node;
        CLOSE[close] = *Node;            //将扩展过的节点放入CLOSE    
        close++;
        for (i = 0; i <= open - 1; i++) //将扩展的节点从OPEN中释放
        {
            OPEN[i] = OPEN[i + 1];
        }
        open--;

        if ((Temp->Zero) % 3 >= 1)        //如果能左移,则进行左移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Left(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero - 1, (Temp->G) + 1, Temp, 1);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if ((Temp->Zero) % 3 <= 1)        //如果能右移,则进行右移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Right(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero + 1, (Temp->G) + 1, Temp, 2);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (Temp->Zero >= 3)            //如果能上移,则进行上移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Up(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero - 3, (Temp->G) + 1, Temp, 3);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (Temp->Zero <= 5)            //如果能下移,则进行下移创造新结点    
        {
            Node = new ENode;                                           //创造新结点
            status = Down(Temp->status, Temp->Zero);                   //得到新的状态
            Node = ENodeInit(status, Temp->Zero + 3, (Temp->G) + 1, Temp, 4);    //初始化新结点
            ExistAndOperate(Node);      //判断子节点是否在OPEN或CLOSE中,并进行对应的操作
        }

        if (open == 0)                  //如果open=0, 证明算法失败, 没有解
            return NULL;
    }
    return Node;
}

/*
展示具体步骤
返回 NULL
*/
void ShowStep(ENode *Node)
{
    int STEP[MAXSTEPSIZE];
    int STATUS[MAXSTEPSIZE][9];
    int step = 0;
    int i, j;
    int totalStep = Node->G;
    while (Node)
    {
        STEP[step] = Node->step;
        for (i = 0; i <= 8; i++)
        {
            STATUS[step][i] = Node->status[i];
        }
        step++;
        Node = Node->Parent;
    }
    cout << "----------------------" << endl;
    cout << totalStep << endl;
    cout << "----------------------" << endl;
    for (i = step - 1; i >= 0; i--)
    {
        if (STEP[i] == 1)
            cout << "left";
        else if (STEP[i] == 2)
            cout << "right";
        else if (STEP[i] == 3)
            cout << "up";
        else if (STEP[i] == 4)
            cout << "down";
        else if (STEP[i] == 0)
            cout << "START";
        cout << " ";
    }
    cout << endl << "----------------------" << endl;
    for (i = step - 1; i >= 0; i--)
    {
        for (j = 0; j <= 8; j++)
        {
            cout << STATUS[i][j];
            if (j == 2 || j == 5 || j == 8)
                cout << endl;
            else
                cout << " ";
        }
        cout << "----------------------" << endl;
    }
}

/*
主函数
返回 0
*/
int main()
{
    int fstatus[9];
    int i;
    ENode *FNode;
    ENode *EndNode;

    for (i = 0; i <= 8; i++)                    //输入初始状态
    {
        cin >> fstatus[i];
    }

    for (i = 0; i <= 8; i++)                    //判断0节点位置
    {
        if (fstatus[i] == 0)
            break;
    }

    FNode = ENodeInit(fstatus, i, 0, NULL, 0);  //获得初始节点

    OPEN[open] = *FNode;                         //将初始节点放入OPEN中
    open++;
    EndNode = Search();                         //寻找最佳路径

    if (!EndNode)
        cout << "无解" << endl;
    else
        ShowStep(EndNode);                      //展示步骤

    return 0;
}

效果截图

在这里插入图片描述
第一行是输入内容是目前的棋盘状态,每一个数字之间用空格隔开,棋盘上的空格用0表示
第二行是输出内容,是走的最少的步数
第三行是输出的操作,start后是显示的是上下左右的集中操作
剩余展示的每走一步之后的状态

转载借鉴声明

本文借鉴https://www.codenong.com/cs105897291/,代码摘自上篇内容,为了有更好的理解,所以写此篇内容,如有侵权,联系删除,感谢上述博主的分享。

  • 12
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
九宫重排问题是一种经典的搜索问题,可以使用A*算法来解决。下面是使用C++实现A*算法解决九宫重排问题的代码示例: ```c++ #include <iostream> #include <queue> #include <vector> #include <map> using namespace std; const int N = 3; // 九宫格大小 const int MAX_STATE = 1e5; // 最大状态数 struct State { int a[N][N]; // 九宫格 int x, y; // 空格位置 int f, g, h; // f = g + h int id; // 状态编号 bool operator < (const State& rhs) const { return f > rhs.f; // 优先队列按 f 值从小到大排序 } }; int dx[4] = {-1, 0, 1, 0}; // 方向数组 int dy[4] = {0, 1, 0, -1}; int start[N][N] = { // 初始状态 {2, 8, 3}, {1, 6, 4}, {7, 0, 5} }; int goal[N][N] = { // 目标状态 {1, 2, 3}, {8, 0, 4}, {7, 6, 5} }; int get_h(const int a[][N]) { // 估价函数 int res = 0; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (a[i][j] == 0) continue; int x = (a[i][j] - 1) / N; int y = (a[i][j] - 1) % N; res += abs(x - i) + abs(y - j); } } return res; } int state_id; // 状态编号 map<int, int> vis; // 判重数组 State S[MAX_STATE]; // 状态数组 int get_id(const int a[][N]) { // 获取状态编号 int res = 0, p = 1; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { res += a[i][j] * p; p *= 10; } } return res; } void print(const State& s) { // 打印状态 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { cout << s.a[i][j] << " "; } cout << endl; } } bool is_goal(const State& s) { // 判断是否为目标状态 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (s.a[i][j] != goal[i][j]) return false; } } return true; } void A_star() { // A*算法 priority_queue<State> q; state_id = 0; vis.clear(); State start_state; start_state.x = 1, start_state.y = 2; // 空格位置 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { start_state.a[i][j] = start[i][j]; } } start_state.g = 0; start_state.h = get_h(start_state.a); start_state.f = start_state.g + start_state.h; start_state.id = state_id++; S[start_state.id] = start_state; q.push(start_state); vis[get_id(start_state.a)] = start_state.id; while (!q.empty()) { State t = q.top(); q.pop(); if (is_goal(t)) { cout << "找到目标状态" << endl; print(t); return; } for (int k = 0; k < 4; k++) { int nx = t.x + dx[k], ny = t.y + dy[k]; if (nx < 0 || nx >= N || ny < 0 || ny >= N) continue; State s = t; s.x = nx, s.y = ny; swap(s.a[t.x][t.y], s.a[nx][ny]); s.h = get_h(s.a); s.g = t.g + 1; s.f = s.g + s.h; int id = get_id(s.a); if (vis.count(id) == 0) { s.id = state_id++; S[s.id] = s; q.push(s); vis[id] = s.id; } else { int pre_id = vis[id]; if (S[pre_id].f > s.f) { S[pre_id] = s; vis[id] = s.id; q.push(s); } } } } } int main() { A_star(); return 0; } ``` A*算法的核心是估价函数,本例中使用的是曼哈顿距离,即当前状态与目标状态每个数码所在位置的曼哈顿距离之和。在代码实现中,使用了优先队列来维护搜索过程中的状态集合,采用了判重和状态编号的方法来优化算法效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值