第八篇博客——搜索

宽度搜索 BFS

BFS的核心的数据结构是队列,访问完当前的数据(myqueue.front())后将其子状态压入队列中,每次访问都是访问队列头部的数据。下面是模版。

bool visit[MAXN];

struct status{
    int n,k;
    status(int n,int k):n(n),k(k){}
};

int BFS(int n,int k){
    queue<int> myq;
    myq.push(status(n,0));
    visit[n]=true;
    while(!myq.empty()){
        status cur=myq.front();
        myq.pop();
        if(cur.n==k)
            return cur.k;
        for(int i=0;i<3;i++){
            status next(cur.n,cur.k+1);
            if(i==0)
                next.n+=1;
            else if(i==1)
                next.n-=1;
            else
                next.n*=2;
            myq.push(next);
            visit[next.n]=true;
        }
    }
}

总结:

  • 状态。确定所需的状态。通过状态的扩展,遍历所有的状态,从中寻找需要的答案。
  • 状态扩展方式。在BFS中,需要尽可能地扩展状态,并对先拓展得到的状态进行下一次状态拓展。
  • 有效状态。对有些状态,不需要再次扩展,直接舍弃即可(剪枝)
  • 队列。新队列压入栈尾,每次取队头元素扩展。
  • 标记。为了判断什么状态是有效的,什么是无效的。
  • 有效状态数。时间复杂度和有效状态数是同一个数量级的,所以在搜索之前必须估算是否能够在接受范围内。
  • 最优。一旦问题中出现最少、最短、最优等关键字的时候就需要考虑宽度优先搜索可否解决。

练习:玛雅人的密码

#include <iostream>
#include<cstdio>
#include<string>
#include<queue>
#include<set>

using namespace std;

struct node{
    string str;
    int step;
    node(string s,int st):str(s),step(st){}
};

set<string> inq;

void BFS(string origin){
    queue<node> q;
    q.push(node(origin,0));
    inq.insert(origin);
    while(!q.empty()){
        node current=q.front();
        q.pop();
        if(current.str.find("2012")!=string::npos){
            //查找成功就可以输出
            cout<<current.step<<endl;
            return;
        }
        for(int i=0;i<current.str.size()-1;i++){
            string temp=current.str;
            temp[i]=current.str[i+1];
            temp[i+1]=current.str[i];
            if(inq.find(temp)==inq.end()){
                q.push(node(temp,current.step+1));
                inq.insert(temp);
            }
        }
    }
    cout<<"-1"<<endl;
}
int main(){
    int N;
    string str;
    while(cin>>N>>str){
        if(N<4)
            cout<<"-1"<<endl;
        else
            BFS(str);
    }
    return 0;
}

深度优先搜索

深度优先搜索强调问题是否得到解。在问题中如果不是强调要得到问题的最优解,则可以使用深度优先搜索来解决这个问题。

习题1:神奇的口袋

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

#include<iostream>
using namespace std;
int a[21];
int res=0;
int n;


void dfs(int now,int j){
    for(int i=j;j<n;j++){

        if(a[j]+now>40)
            dfs(now,j+1);
        if(a[j]+now<40){
            dfs(now+a[j],j+1);
        }
        //不用更新形式参数,因为now+a[j]自动变成形式参数
        if(now+a[j]==40){
            res++;
        }
    }
}
int main(){
    while(cin>>n){
        res=0;
        //方式数目=0
        for(int i=0;i<n;i++){
            cin>>a[i];

        }
        dfs(0,0);
        cout<<res<<endl;
    }
    return 0;
}

习题2:八皇后

会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。 对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2...b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。 给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。

/* DFS
 * 行从0到7逐层增加,不会存在重复放到同一行的情况
 * 列需要用bool col[8]记录某行是否已经放置
 * 对角线则需要用一个bool matrix[8][8](代码里用到矩阵名是djx),并放到Judge(int x,int y)中判断
 * 然后思路就比较简单了,按照DFS对可扩展的状态进行递归
 * 每层的可扩展状态满足:
 *    (1)列上没有其它棋子
 *    (2)对角线上没有棋子
 * 好像把DFS中的now换成row更容易理解?
 */
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int maxN = 8;
bool djx[maxN][maxN] = {false};
bool col[maxN] = {false};

vector<int> vi;        //可以用string

bool Judge(int x,int y){    
    //用于判断当前条件下(djx数组目前的状态下)对角线上是否已经有棋子了
    int y2 = y;
    for(int i=x;i>=0;i--){
        if(y>=0 && djx[i][y--]==true)
            return false;
        if(y2<8 && djx[i][y2++]==true)
            return false;
    }
    return true;
}
void DFS(int now,int ans){
    if(now==8){
        vi.push_back(ans);
        return ;
    }
    for(int j=0;j<8;j++){        //完全的DFS思路
        if(!col[j] && Judge(now,j)){
            col[j] = true;
            djx[now][j] = true;           //注意:题目里的所有数字都是从1开始计数,所以使用j+1记录answer
            DFS(now+1,ans*10+j+1);        //可以用string,由于最长8为十进制数,所以用int了
            col[j] = false;
            djx[now][j] = false;
        }
    }
}
int main(){
    DFS(0,0);
    sort(vi.begin(),vi.end());      //对answer进行排序
    int index;
    while(cin>>index){
        cout<<vi[index-1]<<endl;    //注意题目中所有数字均为从1计数
    }
    return 0;
}