【Nan's 王道机试指南学习笔记】第九章 搜索
前言与提示
搜索是一种有目的地枚举问题的解,发现解空间的某一子集内不存在解时,会放弃对该子集的搜索。
9.1 宽度优先搜索BFS
重点提醒
从搜索的起点开始,不断地优先访问当前结点的邻居,再按照访问邻居结点的先后顺序依次访问它们的邻居,直至搜遍或找到解。常用于搜索最优值的问题。
①状态:确定所求解问题中的状态,遍历所有的状态。
②状态扩展方式:尽可能地扩展状态,并对现扩展的状态进行下一次状态扩展。
③有效状态:有些状态不需要再次扩展就应直接舍弃。
④队列:先得到的状态先扩展。
⑤标记:为了判断哪些状态是有效的/无效的。
⑥有效状态数:有效状态数 与 时间复杂度 同数量级。
⑦最优:BFS常用于求解最优解问题(搜索到的状态总是按照某个关键字递增——最少/最短/最优关键字)
题目练习
例题9.1 Catch That Cow
题目OJ网址(牛客网):(最优路径优化问题)
https://www.nowcoder.com/questionTerminal/e41eb933dca64609a198d2659efac183
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
int const maxn = 100001;
bool visit[maxn];
struct status{
int n,t;
status(int n,int t):n(n),t(t){}
};
int BFS(int n,int k){
queue<status> myqueue;
myqueue.push(status(n,0)); //压入初始状态
visit[n] = true; //起始点已被访问
while(!myqueue.empty()){
status current = myqueue.front();
myqueue.pop();
if(current.n == k) //查找成功
return current.t;
for(int i=0;i<3;i++){ //转入不同状态
status next(current.n,current.t+1);//初始化
if(i == 0) next.n += 1;
else if(i == 1) next.n -= 1;
else if(i == 2) next.n *= 2;
if(next.n<0||next.n>=maxn||visit[next.n])
continue;//新状态不合法
myqueue.push(next); //压入新状态(尝试三次)
visit[next.n] = true; //该点已被访问
}
}
}
int main(){
int N,K;
cin>>N>>K;
memset(visit,false,sizeof(visit));
cout<<BFS(N,K);
}
例题9.2 Find The Multiple
题目OJ网址(牛客网):https://www.nowcoder.com/questionTerminal/ce8884fe5fe2483e89815bdd684953d4
可以枚举解,但是计算量大;改为BFS,搜索由0和1组成的数,并判断能否整除n来求解会更简单。0和1组成的num可扩展成两种状态:①10num②10num+1
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int BFS(int n){
queue<long long> myqueue;
myqueue.push(1); //压入初始状态
while(!myqueue.empty()){
long long current = myqueue.front();
myqueue.pop();
if(current % n == 0){ //查找成功
cout << current;
return 0;
}
myqueue.push(current*10);
myqueue.push(current*10+1);
}
}
int main(){
int n;
while(cin>>n){
if(n==0) break;
BFS(n);
}
return 0;
}
习题9.1 玛雅人的密码(清华复试)
题目OJ网址(牛客网):https://www.nowcoder.com/questionTerminal/761fc1e2f03742c2aa929c19ba96dbb0
#include <stdio.h>
#include <queue>
#include <string>
#include <iostream>
using namespace std;
bool judge(string a,int n) {
bool flag=false;
for(int i=0; i<n-3; i++) {
if(a[i]=='2'&&a[i+1]=='0'&&a[i+2]=='1'&&a[i+3]=='2') {
flag=true;
break;
}
}
return flag;
}
string swap(string a,int i,int j) {
char temp=a[i];
a[i]=a[j];
a[j]=temp;
return a;
}
struct E {
string a;
int t;
};
queue<E> Q;
int BFS(int n) {
while(!Q.empty()) {
E current=Q.front();
Q.pop();
if(judge(current.a,n)){
return current.t;
}
else{
for(int i=0; i<n-1; i++) {
string new_a = swap(current.a,i,i+1);//新状态
int new_t=current.t+1;
if(new_t>20) {
return -1;
}
else {
if(judge(new_a,n)) {
return new_t;
} else {
E tmp;
tmp.a=new_a;
tmp.t=new_t;
Q.push(tmp);//把产生的新状态压入队列 一会尝试两次移位
}
}
}
}
}
return -1;
}
int main() {
int n;
string str;
while(cin>>n>>str) {
while(!Q.empty()){
Q.pop();
}
E tmp;
tmp.a=str;
tmp.t=0;
Q.push(tmp);
int ans = BFS(n);
cout<<ans<<endl;
}
return 0;
}
9.2 深度优先搜索DFS
重点提醒
获得一个状态后,同时立即扩展这个状态,但需保证早得到的状态较后被扩展。即“先入后出”——用 栈 解决 / 递归策略。
不是求最优,而是找符合。
常用 递归策略 来求解深度优先搜索问题
题目练习
例题9.3 A Knight’s Journey
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=30;
int p,q;//棋盘参数
bool visit[maxn][maxn];
//日字形的8种走法
int direction[8][2]={
{-1,-2},{1,-2},{-2,-1},{2,-1},{-2,1},{2,1},{-1,2},{1,2}
};
bool dfs(int x,int y,int step,string ans)
{
if(step==p*q)
{
cout<<ans<<endl<<endl;
return true;
}
else
{
for(int i=0;i<8;i++){
int nx=x+direction[i][0];
int ny=y+direction[i][1];
char col=ny+'A';
char row=nx+'1';
if(nx<0||nx>=p||ny<0||ny>=q||visit[nx][ny]) continue;
visit[nx][ny]=true;//标记该点
if(dfs(nx,ny,step+1,ans+col+row)){//递归 能找到结果
return true;
}
visit[nx][ny]=false;//取消标记
}
}
return false;
}
int main(){
int n;
cin>>n;
int casenumber=0;
while(n--){
cin>>p>>q;
memset(visit,false,sizeof(visit));
cout<<"scenario #"<<++casenumber<<":"<<endl;
visit[0][0]=true;//标记A1点
if(!dfs(0,0,1,"A1"))
cout<<"impossible"<<endl<<endl;
}
return 0;
}
例题9.4 Square
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//sum是当前拼凑的木棍长度
//number是已拼好的木棍编号
//position为当前的木棍编号
const int maxn=30;
int side;//边长
int num;//树枝数目
int sticks[maxn];
bool visit[maxn];
bool dfs(int sum,int number,int position){
if(number == 3){
return true;
}
int sample = 0;//剪枝
for(int i=position;i<num;i++){
if(sum+sticks[i]>side||sticks[i]==sample||visit[i]) continue;
visit[i]=true;
if(sum+sticks[i] == side){
if(dfs(0,number+1,0)) return true;//sum+当前棍子已凑成一边
else sample = sticks[i];//记录失败棍子的长度
}
else{
if(dfs(sum+sticks[i],number,i+1)) return true;
else sample = sticks[i];//记录失败棍子的长度
}
visit[i]=false;
}
return false;
}
bool compare(int a,int b){
return a>b;
}
int main(){
int n;
cin>>n;
while(n--){
int length = 0;
cin>>num;
for(int i = 0;i<num;i++){
cin>>sticks[i];
length += sticks[i];
}
memset(visit,false,sizeof(visit));
//剪枝 除不开
if(length%4!=0){
cout<<"no"<<endl;
continue;
}
side = length/4;
sort(sticks,sticks+num,compare);
//剪枝 单个比边长还长
if(sticks[0]>side){
cout<<"no"<<endl;
continue;
}
if(dfs(0,0,0)) cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
return 0;
}
习题9.2 神奇的口袋(北大复试)
牛客网网址:https://www.nowcoder.com/questionTerminal/9aaea0b82623466a8b29a9f1a00b5d35
#include <iostream>
using namespace std;
int n;
int product[100];
int rec(int i,int sum){
if(sum==0) return 1;//剩余体积为0 找到一个装包方式
if(i==n||sum<0) return 0;
//count(i+1,sum-a[i]) 代表从第i+1个物品开始,剩余体积数为sum-a[i]的方案数
//count(i+1,sum) 代表从第i+1个物品开始,剩余体积数为sum的方案数
return rec(i+1,sum-product[i])+rec(i+1,sum);
//递归函数count(i,sum)=count(i+1,sum-a[i])+count(i+1,sum);
}
int main(){
while(cin>>n){
for(int i = 0;i<n;i++){
cin>>product[i];
}
cout<<rec(0,40)<<endl;
}
return 0;
}
习题9.3 八皇后(北大复试)
牛客网网址:https://www.nowcoder.com/questionTerminal/fbf428ecb0574236a2a0295e1fa854cb
//test9.3
#include <iostream>
using namespace std;
int ans[92][10];
int c[10];//存一个解
int total = 0;
//深度优先遍历
void dfs(int cur){//起始位置
if(cur == 8){
total++;
for(int i = 0; i < 8; i++){
ans[total][i] = c[i];
}
}
else{
for(int i = 0; i < 8; i++){
c[cur] = i;//标记位置
int ok = 1;//默认是所求的解
for(int j = 0; j < cur; j++){
//不同列 不在对角线上
if(c[j] == i || cur-i == j-c[j]||cur+i == c[j]+j){
ok = 0;
break;//此方案行不通
}
}
if(ok){//i循环中此路可行
dfs(cur + 1);//检测下一行
}
}
}
}
int main(){
int n;
while(cin>>n){
dfs(0);
for(int i = 0; i<8 ; i++){
cout<<ans[n][i]+1;
}
cout<<endl;
}
return 0;
}