目录
前言
其实这题数据不算复杂,不用双向广搜也可以完成,仅仅是为了更直观展现双向广搜的编码方式。
传统bfs
bfs向来都是搜索问题的一把好手,但一直以来bfs都有一个痛点,就是随着bfs层数的增加,bfs所要入队的元素一般会增长很快,有时的指数形,去重做的比较好那就是线性增长,但如果数据庞大是,这个数量依旧惊人
(线性增长) (指数增长)
双向广搜
如果我们已知起点和终点,分别从起点和终点出发,那么就可以减少大量的状态数量
总结一点,如果去重都无法减少庞大的状态数量,那么就可以考虑双向广搜
open the lock
问题描述
有一个密码锁,总共四位数字,现已知四位数字的初始状态,和开锁密码,求最少几步可以开锁,密码锁上可以进行的操作,对于密码锁上的任意一位数字,都可以加一或减一,但如果数字原先为9,加一后为1,数字原先为1,减一后为9,也可以和该数字相邻的数字交换位置,最后一位数字和第一位数字不相邻,每次操作算一步
输入
第一行输入一个整数代表有多个测试,对于每个测试每行输入两个四位数,分别代表起始数字和密码锁密码
输出
对于每个测试,输出一个整数,代表所花费的最少步数
问题分析
本题其实没有特别的难点,最主要的就是处理四位数之间状态的转变
状态转变
我使用了一个函数来封装这个转变:
string Skip(string& ss, int i, int j) {
//注:字符-'0' 字符变数字,数字+'0' 数字变字符
/*i:操作目标四位数的位数
j:操作方法
ss:目标四位数
*/
int m;
if (j == 1) {
m = ss[i] - '0' + 1;
if (m == 10) m = 1;
ss[i] = m + '0';
}
if (j == 2) {
m = ss[i] - '0' - 1;
if (m == 0) m = 9;
ss[i] = m + '0';
}
if (j == 3 && i <= 2) { //j<=2,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它
Swap(ss, i);
}
return ss;
}
值得一提的是交换操作,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它
去重
这里我使用的是map去重,也可以用set去重,详情请看bfs与判重
单向搜索的bfs
其实本题单向搜索就可以完成了,因为判重操作后,每次进队的数量不会超过9999-1111,不算多,所以也可以通过:
#include<iostream>
#include<queue>
#include<map>
using namespace std;
string st, ed;
struct node {
string s;
int step;
node(){}
node(string ss,int st):s(ss),step(st){}
};
node now, nxt;
queue<node>que;
map<string, bool>mp;
void Swap(string& ss, int i) {
char tmp = ss[i];
ss[i] = ss[i + 1];
ss[i + 1] = tmp;
}
string Skip(string& ss,int i,int j) {
int m;
if (j == 1) {
m = ss[i] - '0' + 1;
if (m == 10) m = 1;
ss[i] = m + '0';
}
if (j == 2) {
m = ss[i] - '0' - 1;
if (m == 0) m = 9;
ss[i] = m + '0';
}
if (j == 3 && i<=2) {
Swap(ss, i);
}
return ss;
}
void bfs() {
que.push(node(st, 0));
mp[st] = true;
while (!que.empty()) {
now = que.front();
que.pop();
for (int i = 0; i < 4; i++) { //交换操作或数值改变操作
for (int j = 1; j <= 3; j++) {
nxt.s = now.s; //这个注意一下,每次修改都是在now.s的基础上该
nxt.s = Skip(nxt.s, i, j);
if (nxt.s == ed) {
cout << nxt.s << endl;
cout << now.step+1 << endl;
return;
}
if (!mp[nxt.s]) {
cout << nxt.s << endl;
nxt.step = now.step + 1;
que.push(node(nxt));
mp[nxt.s] = true;
}
}
}
}
}
int main() {
int test;
cin >> test;
while (test--) {
cin >> st;
cin >> ed;
//cout << st << ed;
if (st == ed) {
cout << 0 << endl;
continue;
}
mp.clear();
bfs();
}
}
双向广搜
使用双向广搜,可以减少一次进入队列的状态数量,也是个不错的算法
结束条件
单向搜索的结束条件可以根据是否到达终点判断,当时双向搜索是在中途就已经结束了,所以还需要对结束条件进行讨论
我们定义了两个map,如果找到一个位置,两个map都已经搜过,那么这个点就是相遇点,结束搜索。
输出步数
单向搜索的输出很简单,只需要输出now.step即可,但是双向搜索显然不行,因为两个now节点不一定同时指向同一个四位数,所以还需要一个dis数组保存步数
接下来就可以编写代码了(这里我使用双队列的双向广搜)
#include<iostream>
#include<queue>
#include<map>
using namespace std;
string st, ed;
struct node {
string s;
int step;
node() {}
node(string ss, int st) :s(ss), step(st) {}
};
node nowa, nxta;
node nowb, nxtb;;
queue<node>qa; //从起点开始搜
queue<node>qb; //从终点开始搜
map<string, bool>mp1; //起点队列判重
map<string, bool>mp2; //终点队列判重
map<string, int>dis; //记录距离
void Swap(string& ss, int i) {
//其实交换操作可以看成是单向的,因为双向和单向结果没区别
char tmp = ss[i];
ss[i] = ss[i + 1];
ss[i + 1] = tmp;
}
string Skip(string& ss, int i, int j) {
//注:字符-'0' 字符变数字,数字+'0' 数字变字符
int m;
if (j == 1) {
m = ss[i] - '0' + 1;
if (m == 10) m = 1;
ss[i] = m + '0';
}
if (j == 2) {
m = ss[i] - '0' - 1;
if (m == 0) m = 9;
ss[i] = m + '0';
}
if (j == 3 && i <= 2) { //j<=2,最后一位数字不交换,其实也不是说不交换,就是我们只考虑单项交换,所以最后一位数字相当于不交换,其实他还是可以和他的前一位数字交换的,只是发起交换的对象不是它
Swap(ss, i);
}
return ss;
}
void bfs() {
qb.push(node(ed, 0));
qa.push(node(st, 0));
dis[st] = 0;
dis[ed] = 0;
mp1[st] = true;
mp2[ed] = true;
while (!qa.empty() && !qb.empty()) {
//保证起点和终点同步扩散
if (qa.size() < qb.size()) {
//起点
nowa = qa.front();
qa.pop();
for (int i = 0; i < 4; i++) { //交换操作或数值改变操作
for (int j = 1; j <= 3; j++) {
nxta.s = nowa.s; //这个注意一下,每次修改都是在now.s的基础上该
nxta.s = Skip(nxta.s, i, j);
if (mp2[nxta.s] == true) {
//cout << nxta.s << endl;
cout << nowa.step + dis[nxta.s]+1 << endl; //注意这里要加1,因为起点和终点的step都是0
return;
}
if (!mp1[nxta.s]) {
//cout << nxta.s << endl;
nxta.step = nowa.step + 1;
qa.push(node(nxta));
dis[nxta.s] = nxta.step;
mp1[nxta.s] =true;
}
}
}
}
else {
//终点
nowb = qb.front();
qb.pop();
for (int i = 0; i < 4; i++) { //交换操作或数值改变操作
for (int j = 1; j <= 3; j++) {
nxtb.s = nowb.s; //这个注意一下,每次修改都是在now.s的基础上该
nxtb.s = Skip(nxtb.s, i, j);
if (mp1[nxtb.s]==true) {
cout << nowb.step +dis[nxtb.s]+1<< endl;
return;
}
if (!mp2[nxtb.s]) {
nxtb.step = nowb.step + 1;
qb.push(node(nxtb));
dis[nxtb.s] = nxtb.step;
mp2[nxtb.s] = true;
}
}
}
}
}
}
int main() {
int test;
cin >> test;
while (test--) {
cin >> st;
cin >> ed;
if (st == ed) {
cout << 0 << endl;
continue;
}
bfs();
//很多人喜欢用数组实现,但我还是倾向于用stl,更加简洁
mp1.clear();
mp2.clear();
dis.clear();
}
}