【王道机试指南学习笔记】第九章 搜索

前言与提示

搜索是一种有目的地枚举问题的解,发现解空间的某一子集内不存在解时,会放弃对该子集的搜索。

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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值