首页>少儿编程>c++八数码问题与宽度优先搜索

c++八数码问题与宽度优先搜索

八数码问题

在3行3列的9宫格中,有8个格子放有1~8之间不同的数字,余下一个空格,这个空格可以在格子内上下左右地移动位置。现在给你初状态和目标状态,请你找出最少的移动步数。

eight_number_problem_and_width_first_search_p1

如图所示,左边是初始状态,右侧为目标状态,这同样是noi官方培训视频中的一道题目。

解题思路

有点类似拼图游戏的逻辑,我们首先定位空格,因为允许上下左右移动,所以至多会出现4种新的结果,因为有边界的存在,4种结果有时会缩减至两种,又因为我们不希望又前进一格后又退回的骚操作,所以经过筛选,符合条件的结果可能会更少。

eight_number_problem_and_width_first_search_p2

数组的选择

一维数组或二维数组都可以解决这个问题,二维判断边界更为简单粗暴,但是涉及到值的交换,数据提取时,又不如一维数组直观,一维数组在空格的移动上需要更复杂的判断,但实际程序编制过程中个人感觉还是一维数组更为直观。

eight_number_problem_and_width_first_search_p3

程序逻辑

设置一个数组,用来存储每个阶段的数据,设置st,ed两个变量,分别代表数组的头部与尾部。比如第一步产生了4个状态,我们将这四个状态暂存,每加入一个新状态,尾部就后移一位。头部变量每增加一次,就检测一次4个方向上的可能性,继续将新状态存入。因为前期可允许的走法较多,st与ed的间隔会逐渐拉大,因为查重筛选的存在,后续新的状态会逐渐减少。

eight_number_problem_and_width_first_search_p4

将数据存入前先检测一下是否当前已有的结果重复,重复就中止存入。存入后再核对一下目标数组,如果与目标数组一致,则目的已达到,中止所有程序。

一维数组的方式

#include<iostream>
using namespace std;

int bfs(){
    //目标数组
    int target[9]={1,2,3,8,0,4,7,6,5};
    //存储结果,多一位存储步数,初始步数0 
    //注意二维数组的定义方式 
    //思考更多维数组的定义 
    int result[255][10]={{2,8,3,1,0,4,7,6,5,0}};
    //定义临时存储数组
    int next[10];
    //定义方向数组上下左右,9宫格,减3即向上,减1即向左
    //行数列数不定时则需要考虑使用变量输入 
    int arrow[4]={-3,3,-1,1};
    //定义起始中止的数组下标
    int st=0,ed=1;
    //定义space代指空格位置;
    int space=0; 
    //定义计数器count
    int count=0; 
    

    //每增加一个正确结果则ed+1,所以两者间距会先加大,因为有去重,后逐渐缩小 
    while(st<ed){
        //因为每个空格都可能有4个方向,先循环4次遍历arrow
        for(int i=0;i<4;i++){
            //提取结果中的第一组存入next
            //提取结果符合条件的需要进行数据交换 
            //需要保留原始数据
            //所以存入临时数组
            for(int j=0;j<10;j++){
                next[j]=result[st][j];
                //取值同时顺便找一找空格在哪 
                if(next[j]==0&&j!=9){
                    space=j;
                }
            }
            //注意这里的循环数i,与j不同,因为后续还需要用到,所以不能重新定义
            //space是指空格的位置,arrow[i]是指移动方向数组的对应值,比如空格在5,i是0
            //arrow[i]=-3,即向上移动一格,因为i会循环四次,即遍历4个方向
            //现在判断边界;
            //首先一维数组不能超限; 
            if( (arrow[i]+space)>=0 && (arrow[i]+space)<=8 ){
                //如果靠左,下标为0,3,6时禁用-1,因为过界了。  
                if( (arrow[i])%3==0 &&  arrow[i]==-1 )continue;
                //如果靠右,下标为2,5,8时禁用+1,因为过界了。 
                if( (arrow[i])%3==2 &&  arrow[i]==1 )continue;
                //判断结束执行移动
                next[space] = next[arrow[i]+space];
                next[arrow[i]+space] = 0;
                //步骤数加1 
                next[9]++;
                //去重操作,从resut第一组数据开始,到ed为止,设置一个计数变量count,
                //有一个相等就count+1,如果最终count数值为9,说明有完全重复的数组,
                //那么新数据就抛弃不用
                for(int k=0;k<ed;k++){
                    for(int l=0;l<9;l++){
                        if(next[l]==result[k][l])count++;
                    }
                    if(count==9){
                        break;
                    }else{
                        count=0;
                    }
                }
                //判断count最终值,不等于9则存进result数组
                //因为ed指向的是数组的尾部,所以直接存入ed指向的数组,存储结束ed+1 
                if(count!=9){
                    for(int m=0;m<10;m++){
                        result[ed][m]=next[m];
                    }
                    ed++;
                    //判断结果是否与target相同
                    //首先count清零
                    //结果对上就 break掉 
                    count=0;
                    for(int n=0;n<9;n++){
                        if(next[n]==target[n])count++;
                    } 
                    if(count==9){
                        cout << "get it!" << endl;
                        for(int m=0;m<10;m++){
                            cout << next[m] <<" ";
                            if((m+1)%3==0)cout << endl;
                        }
                        cout << endl;
                        break;
                    }else{
                        count=0;
                    }
                }
            } 
        } 
    st++;    
    } 
}

int main(){
    bfs();
}

二维数组的方式

尝试了一下二维数组的方式,感觉提取数据与数据交换时较为繁杂:

#include<iostream>

using namespace std;

int bfs(){
    //起始与中止下标 
    int st=0,ed=1;
    //查找空值
    int space[2]={0,0}; 
    //统计重复
    int count=0; 
    //储存结果数组
    int ranks[200][4][3]={{{2,8,3},{1,0,4},{7,6,5},{0,}}};
    //暂存数组 
    int next[4][3];
    //目标数组
    int tar[3][3]={{1,2,3},{8,0,4},{7,6,5}};
    //方向数组
    int arrow[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
    while(st<ed){
        for(int i=0;i<4;i++){
            //循环取数 
            for(int j=0;j<4;j++){
                for(int k=0;k<3;k++){
                    next[j][k]=ranks[st][j][k];
                    if(j<3&&next[j][k]==0){
                        space[0]=j;
                        space[1]=k;
                    }
                }
            }
            //添加方向
            if(arrow[i][0]+space[0]>=0&&arrow[i][0]+space[0]<=3&&arrow[i][1]+space[1]>=0&&arrow[i][1]+space[1]<=3){
                next[space[0]][space[1]]=next[arrow[i][0]+space[0]][arrow[i][1]+space[1]];
                next[arrow[i][0]+space[0]][arrow[i][1]+space[1]]=0;
            } 
            //检验数据
            for(int l=0;l<ed;l++){
                for(int m=0;m<3;m++){
                    for(int n=0;n<3;n++){
                        if(next[m][n]==ranks[l][m][n])count++;
                    }
                }
                if(count==9){
                    break;
                }else{
                    count=0;
                }
            }
            //检验count并入队 
            if(count!=9){
                for(int m=0;m<3;m++){
                    for(int n=0;n<3;n++){
                        ranks[ed][m][n]=next[m][n];
                    }
                }
                ranks[ed][3][0]=next[3][0]+1;
                ed++;
            }
            //检测结果
            count=0;
            for(int m=0;m<3;m++){
                for(int n=0;n<3;n++){
                    if(next[m][n]==tar[m][n])count++;
                }
            }
            if(count==9){
                cout << "get it !!!" << endl << endl;
                for(int m=0;m<3;m++){
                    for(int n=0;n<3;n++){
                        cout << ranks[ed-1][m][n] << "  ";
                    }
                    cout << endl ;
                }
                cout << endl << ranks[ed-1][3][0];
                return 0;
            }
             
        }
        st++;
    }
}
int main(){
    bfs();
}

标签: c++

移动端可扫我直达哦~

推荐阅读

cpp 2024-11-03

小鸟数据个人洛谷练习极简题解

P11242碧树:叶子越远,枝干越长,已有的枝干再长一片叶子不影响枝干长度,最终就是枝干的长度加上叶子的总数。枝干总长取决于最远的那片叶子,叶子的总数题目中已经提供。P11248矩阵移动:三层循环,最内层循环k表示分别修改0、1、2、...

少儿编程 c++

cpp 2024-10-26

宁波地区选手csp-j复赛一日游

2024年的CSP-J/S复赛依旧没有杭州以外的考点,全省的OIER齐聚杭州,也是盛况空前。我们家是被分到了杭州师范大学的下沙校区,全程约140+公里,高德导航显示2小时能到。考虑考试当天可能拥堵,过早起床也怕孩子考场犯困,所以订了前...

少儿编程 c++

cpp 2024-09-27

2024年 csp-j 以及 csp-s 初赛复赛分数查询接口

链接只是跳转到noi官网,并不是什么第三方的数据库,所以需要预先登陆noi官方网站哦。2024年复赛(2024年10月27日)成绩还没有出,仅尝试放链接备用。复赛接口csp-j复赛查分接口csp-s复赛查分接口2024年初赛(2024...

少儿编程 c++

cpp 2024-09-24

图解动态规划(一)-01背包和变体

在oiwiki学习01背包,虽然完成并通过了题目(洛谷P2871),但总感觉似懂非懂,干脆在画图软件上推演了一下,于是就有了这篇笔记。所谓01背包,一般是给定一个固定容量的容器(背包),并提供固定件数的物品,每件物品有各自的体积(或称...

少儿编程 c++

cpp 2024-09-17

c++中相爱相杀的cin与getline

在洛谷刷题,会遇到各种各种的输入情况,有的时候需要按个输入,而有时则需要按行输入,偶尔也有前一行按个输入,后一行按行输入这样的需求。Windows系统中,换行是由两个字符\r\n组成的。 \r为回车,其ASCII码是13,作用是回到当...

少儿编程 c++

cpp 2024-08-23

c++中的集合----set的使用方法

在C++中,set 是一个容器,用于存储唯一元素,且按特定顺序排序。其具备自动排序,快速查找,去重,插入效率高的特点。以下是定义和使用 set 的基本方法:#include<iostream> #include<se...

少儿编程 c++

cpp 2024-08-23

c++中的map库与它的遍历方式

map与unordered_mapC+提供 map 与unordered_map 两种关联容器,可以将key与value关联起来。 map 与unordered_map 区别:1.底层实现原理map:map内部实现了一个红黑树,该结构...

少儿编程 c++

cpp 2024-08-23

C++利用递归求全排列的笔记

这是一篇洛谷题号P1157题目的题解笔记,该题解的作者是feecle6418,自己写了一大段程序之后看到这么简洁的方式求组合,感觉还是挺挫败的。关键是,看完了题解还看不太懂......#include<bits/stdc++.h...

少儿编程 c++

cpp 2024-07-22

C++位运算的习题解析与若干技巧

洛谷刷题的时候遇到了一些位运算的题目,看得一头雾水,于是临时起意,单独开一篇习题集,用来记录刷题过程中遇到的位运算相关习题。文章准备分为两部分,前半部分为一些常用技巧,后半部分为习题记录,随时补充。位运算的若干技巧位运算的习题集习题部...

少儿编程 c++