题目链接: 蓝桥杯历届试题-九宫重排
题目描述
如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面。
我们把第一个图的局面记为:12345678.
把第二个图的局面记为:123.46758
显然是按从上到下,从左到右的顺序记录数字,空格记为句点。
本题目的任务是已知九宫的初态和终态,求最少经过多少步的移动可以到达。如果无论多少步都无法到达,则输出-1。
输入格式:
输入第一行包含九宫的初态,第二行包含九宫的终态。 例如:
12345678.
123.46758
输出格式:
输出最少的步数,如果不存在方案,则输出-1。
题目分析
本题可以考虑搜索的方式解决。九宫格形式上是将空格周围的数字方块移动到空格的位置,但实际可以理解为将空格与周围某一个方块相互交换,即可获得搜索的分支方式。
在搜索过程中,所搜索到的结果位于树中的深度,恰好可以代表该结果所需的最小移动步数,因此可以推出,本题应当使用BFS进行解决。
由此可知,本题具体的方法为,将初始状态存入队列,每次循环中,列首元素出列,然后将该状态可以移动获得且未出现的元素入列,直到队列为空或者找到九宫的终态,前者即表示所求终态不可到达,后者在搜索树中深度即所需步数。
对于本题中输入的内容,如果将字符串代表的状态用一个二维数组进行存储直观模拟,则需要多次字符串与数组的转换,毫无疑问,所需代价极大。因此,可以考虑换一个思路,每次直接对该字符串中‘.’与其他字符相交换,对于交换进行一定限制避免出现非法结果。具体实现思路如下:
- ‘.’字符每次交换位置+3、+1、-1、-3,表示空格下移、右移、左移、上移;
- 如果‘.’字符交换结束后在字符串中坐标大于8或者小于0,则超界,非法;
- 如果‘.’字符坐标为0、3、6,则此时空格在九宫格最左侧,此时不可左移,坐标为2、5、8同理不可右移。
通过该方式模拟,可以实现各种情况均使用字符串表示。字符串表示的结果,同样可以借助于建立map实现string类型与int类型的映射检查结果是否重复,最终实现BFS。
另外,本题中检查,树深度为31,而遍历情况数为18万,通过数量级可知BFS可行。
解题代码
#include<cstdio>
#include<iostream>
#include<map>
#include<string>
#include<queue>
using namespace std;
typedef map<string, int> s_to_int;
typedef struct node {
string s;
int depth;
}node;
queue<node> q;
string s1,s2;
s_to_int visit;
int turn[4] = { 1,-1,3,-3 };//表示空白块各方向移动时字符'.'所移动相对位置。
int check(int ii, int place);
int main()
{
int i, flag, dep, place;
string s3, s4;
node first, temp, add;
char tmp;
cin >> s1 >> s2;
first.depth = 0;
first.s = s1;
q.push(first); //初态入列
visit[first.s] = 1;
flag = 0;
while (!q.empty()) {
temp = q.front();
q.pop(); //获取现态,现态出列
dep = temp.depth;
s3 = temp.s;
for (i = 0; i < 4; i++) { //向四个方向分别移动获取次态
for (int ii = 0; ii < s3.size() ; ii++) {
if(s3[ii]=='.')place = ii; //获取'.'位置
}
if (!check(i, place))continue; //非法移动,跳过
s4 = s3;
tmp = s4[place];
s4[place] = s4[place + turn[i]];
s4[place + turn[i]] = tmp; //合法移动,进行字符串字符交换
if (visit.count(s4)==0) {
visit[s4] = 1; //记录该状态已遍历,避免重复
add.s = s4;
add.depth = dep + 1; //次态深度即现态深度+1
q.push(add); //次态入列
}
if (s4 == s2) {
cout << add.depth << endl; //找到九宫格终态,输出所需的最少移动次数
flag = 1; //表明终态可找到
break;
}
}
if (flag == 1)break;
}
if (flag == 0)cout << -1 << endl; //终态不可实现,输出-1
return 0;
}
int check(int i, int place) //检查本次移动是否合法,合法返回1,否则返回0
{
if (place + turn[i] > 8 || place + turn[i] < 0)return 0;
else if (i == 0) {
if (place == 2 || place == 5 || place == 8)return 0;
}
else if (i == 1) {
if (place == 0 || place == 3 || place == 6)return 0;
}
return 1;
}
学习笔记(已经了解C++中map的同学可以退出了)
最开始做题时候,感觉应该要使用BFS解决,但是各个状态如何存储,如何检查重复搜索,没有什么思路。本蒟蒻学过C++,但好像没学(上课真的听不懂),容器map知识约等于0。之后问大佬,大佬指导可以考虑用map解决,于是稍微学习了一下map相关的知识,然后试着用map解题,还好没啥语法报错(大佬:你怎么map都不知道?我:
)
这里是map的一点学习笔记,大佬们可以点击左上角退出了。
1、map简介
map是STL(中文标准模板库)的一个关联容器。
- 可以将任何基本类型映射到任何基本类型。如int array[100]事实上就是定义了一个int型到int型的映射。
- map提供一对一的数据处理,key-value键值对,其类型可以自己定义,第一个称为关键字,第二个为关键字的值
- map内部是自动排序的
2、使用map
使用map,必须引入map类所在的头文件:
#include<map> //注意没有.h
map对象为模板类,需要关键字和存储对象两个模板参数:
map<type1name,type2name> maps;//第一个是键的类型,第二个是值的类型
例如,建立字符串到int型数据的映射,可以如下定义:
map<string,int> list;
之后即可获得该类型的map对象。为了简化对象创建,可以对其进行类型定义:
typedef map<string, int> s_to_int;
s_to_int list;
3、插入元素与元素访问
由于map已经对[]运算符进行了重载,插入元素可以使用如下方式:
list["123"]=123;
该方式比较直观,但存在性能问题。插入"123"时,先在list中查找键值为"123"的项,没有,于是插入"123",同时设置"123"的值为缺省值,之后再赋值为123。该过程存在多次设置缺省值后再赋值,元素为类对象时开销较大。可以使用函数insert()避免:
list.insert(s_to_int :: value_type("123",123));
而访问元素时,同样可以借助[]运算符:
int i;
i=list["123"];
但假如list中不存在该键值,则会自动插入该实例,值为初始化值。访问前可以使用find()或count()检查键值。list.find(key)返回类型是迭代器,成功查找时返回键值为key的迭代器,否则返回指向list尾部的迭代器。
s_to_int::iterator it=list.find("123");
if(it==list.end()){
...//未找到
}
else {
...//找到
}
4、删除元素
删除元素用erase()进行。删除指定元素方式:
it=list.find("123");
list.erase(it); //迭代器删除
int n=list.erase("123"); //如果刪除了返回1,否则返回0
list.erase(list.begin(),list.end());
//使用迭代器清空list,list.clear()同作用
5、map其他函数
map.size():获取map中映射次数
int len;
len=list.size();//返回list中映射个数
maps.begin():返回指向map头部的迭代器
maps.end():返回指向map末尾的迭代器
//使用迭代器
s_to_int::iterator it;
for(it = list.begin(); it != list.end(); it++)
cout<<it->first<<" "<<it->second<<endl;//输出key 和value值
maps.rbegin():返回指向map尾部的逆向迭代器
maps.rend():返回指向map头部的逆向迭代器
//使用迭代器
s_to_int::reverse_iterator it;
for(it = list.rbegin(); it != list.rend(); it++)
cout<<it->first<<" "<<it->second<<endl;//输出key 和value值
maps.empty()判断其是否为空,空值返回1,非空返回0。