算法笔记8.2BFS宽搜

问题 C: 【宽搜入门】8数码难题

在这里插入图片描述

思路为:将每种棋盘对应成一个整数,整数存进map表示已经出现过这种状态,对于每种状态,转化成3x3的矩阵,判断是否有下一种状态存进queue,,对于每种状态存取到达这步的步数;BFS宽搜。

提升 这道题做到这我就完了,但是其实它有很多优化问题我没想到,甚至是AI大作业;无解情况存在,但题目不涉及,也考虑是题目比较简单;对于阶乘计算9!;**DBFS**双向深搜,还不理解是在干啥

#include<bits/stdc++.h>
// #include<queue>
// #include<map>
// #include<iostream>
using namespace std;
#define pa pair<int,int>
int mat[3][3],des[3][3];
int srcNum,desNum;
int ans;
map<int,int> reach;
map<int,int> step;
int d_x[4]={-1,1,0,0};
int d_y[4]={0,0,-1,1};
int change(int a[3][3]){
    int rnt=0;
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            rnt=rnt*10+a[i][j];
        }
    }
    return rnt;
}
void num2mat(int a){
    for(int i=2;i>=0;i--){
        for(int j=2;j>=0;j--){
            mat[i][j]=a%10;
            a/=10;
        }
    }
    return;
}
bool same(int a[3][3],int b[3][3]){//n
    bool flag=true;
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            if(a[i][j]!=b[i][j]){
                flag=false;
                break;
            }
        }
    }
    return flag;
}
void dfs(){
    queue<int>q;
    q.push(srcNum);
    reach[srcNum]=1;
    step[srcNum]=1;
    while (!q.empty())
    {
        int now=q.front();
//        num2mat(now);
        if(now==desNum){
            ans=step[now];
            return;
        }
        q.pop();
        num2mat(now);
        int x,y;
        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                if(mat[i][j]==0){
                    x=i,y=j;
                    break;
                }
            }
        }
        int n_x,n_y;
        for(int i=0;i<4;i++){
            n_x=x+d_x[i];
            n_y=y+d_y[i];
            if(n_x>=0&&n_x<3&&n_y>=0&&n_y<3){
                mat[x][y]=mat[n_x][n_y];
                mat[n_x][n_y]=0;
                int after=change(mat);
                if(reach[after]!=1){
                    q.push(after);
                    reach[after]=1;
                    step[after]=step[now]+1;
                }
                mat[n_x][n_y]=mat[x][y];//恢复原矩阵
                mat[x][y]=0;
            }
        }
    }
    return;

}
int main(){
//    freopen("a.in","r",stdin);
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            cin>>mat[i][j];
//            cout<<mat[i][j]<<"-----";
        }
    }
    for(int i=0;i<3;i++){
        for(int j=0;j<3;j++){
            cin>>des[i][j];
        }
    }
    srcNum=change(mat);
    desNum=change(des);
    dfs();
    cout<<ans<<'\n';
}

小结

  • 状态转移:将每个棋盘布局看做一种状态,即为bfs搜索问题
  • 状态对应数值的互相转化
  • 细节:矩阵的频繁操作;二维转一维与一维转二维的对应

问题 D: 【宽搜入门】魔板

在成功地发明了魔方之后,鲁比克先生发明了它的二维版本,称作魔板。这是一张有8个大小相同的格子的魔板:
1 2 3 4
8 7 6 5
我们知道魔板的每一个方格都有一种颜色。这8种颜色用前8个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列(1,2,3,4,5,6,7,8)来表示。这是基本状态。
这里提供三种基本操作,分别用大写字母“A”,“B”,“C”来表示(可以通过这些操作改变魔板的状态):
“A”:交换上下两行;
“B”:将最右边的一列插入最左边;
“C”:魔板中央四格作顺时针旋转。
下面是对基本状态进行操作的示范:

#include<bits/stdc++.h>
// #include<vector>
// #include<queue>
// #include<map>
// #include<iostream>
// #include<string.h>
using namespace std;
// #define pa pair<int,int>
char mat[4][4];
string des;
map<string,string> route;
map<string,int> vis;
map<int,char>op={{0,'A'},{1,'B'},{2,'C'}};
void s2mat(string s){
    int cnt=0;
    for(int i=0;i<4;i++,cnt++) mat[0][i]=s[cnt];
    for(int i=3;i>=0;i--,cnt++) mat[1][i]=s[cnt];
    return;
}
string change(int mode){
    if(mode==0){
        char t[4];
        for(int i=0;i<4;i++) t[i]=mat[0][i],mat[0][i]=mat[1][i],mat[1][i]=t[i];
    }
    else if (mode==1)
    {
        char t[2];
        t[0]=mat[0][3],t[1]=mat[1][3];
        for(int i=3;i>=1;i--){
            mat[0][i]=mat[0][i-1];
            mat[1][i]=mat[1][i-1];
        }
        mat[0][0]=t[0];
        mat[1][0]=t[1];
    }
    else{
        char a[4];
        a[0]=mat[0][1],a[1]=mat[0][2],a[2]=mat[1][1],a[3]=mat[1][2];
        mat[0][1]=a[2],mat[0][2]=a[0],mat[1][1]=a[3],mat[1][2]=a[1];
    }
    string ans="";
    for(int i=0;i<4;i++) ans+=mat[0][i];
    for(int i=3;i>=0;i--) ans+=mat[1][i];
    return ans;
}
void bfs(string s){
    queue<string> q;
    q.push(s);
    while (q.size())
    {
        string now=q.front();
        vis[now]=1;
        q.pop();
        if(now==des){
            cout<<route[now].size()<<"\n";
            cout<<route[now]<<"\n";
            return;
        }
        else{
            s2mat(now);
            string after;
            for(int i=0;i<3;i++){
                s2mat(now);
                after=change(i);
                if(vis[after]==0){
                    vis[after]=1;
                    route[after]=route[now]+op[i];
                    q.push(after);
                }

            }
        }
    }
    return;
}
int main(){
    string src;
    while (getline(cin,src)){
        route.clear();
        vis.clear();

        char c;
        for(int i=0;i<15;i+=2){
            c=src[i];
            des+=c;
//            cin>>c;
        }
//        cout<<des<<"===\n";
        bfs("12345678");
    }

    return 0;
}

问题 E: 【宽搜入门】巧妙取量

题目描述】
  有三个容器,容量分别为 a,b,c(a> b > c ),一开始a装满油,现在问是否只靠abc三个容器量出k升油。如果能就输出“yes”,并且说明最少倒几次,否则输出“no”。例如:10升油在10升的容器中,另有两个7升和3升的空容器,要求用这三个容器倒油,使得最后在abc三个容器中有一个刚好存有5升油,问最少的倒油次数是多少?(每次倒油,A容器倒到B容器,或者A内的油倒完,或者B容器倒满。

思路对于每个状态,都可以从一个桶倒向另一个桶,产生新的状态,宽搜记录达到的状态和步数,其中一个倒向另一个的逻辑是:设AB两个容器,从A倒向B,里面分别含有v1,v2,最多为v1max,v2max,B还可以容纳vfree=v2max-v2;v1>vfree,B倒满;否则,v1全部导入B;

记录所有的状态和步数用结构体,注意不能直接将结构体作为mapkey,出现的问题是结构体里面含有数组,不能唯一作为key,使用数组原因是遍历时候方便遍历,不用谢很多的逻辑判断;(这里的结构体即使重定义“<”"=="也不行,自己可以试试有一定的黑盒性很难判断,不过最终证明这种写法会导致内存溢出,占内存太大);将每个状态只存一遍在vector中(push是深复制),然后使用索引在queue和vis中操作。

其他思路启发由于数据范围小,可以直接用三维数组标记是否到达过该状态

#include<bits/stdc++.h>
using namespace std;
// #define pa pair<int,int>
struct node{
    int a[3]={0};
    int step=0;
    bool operator <(const node &n) const{
        return step<n.step;
    }
    bool operator ==(const node &n) const{
        bool flag=true;
        for(int i=0;i<3;i++){
            if(a[i]!=n.a[i]){
                flag= false;
                break;
            }
        }
        return flag;
    }
//    void operator = (const node& n)const{
//        for(int i=0;i<3;i++){
//            a[i]=n.a[i];
//        }
//        step=n.step;
//    }
}node0;
vector<node>v;
int k;
int cnt=0;
int vmax[3];
map<int,int>vis;
queue<int> q;
node temp;
bool search(node& nn){
    for(auto item:v){
        bool flag=true;
        for(int i=0;i<3;i++){
            if(item.a[i]!=nn.a[i]){
                flag= false;
                break;
            }
        }
        if(flag) return true;
    }
    return false;
}
void bfs(int n){
    q.push(n);
    while (q.size()){
        int now=q.front();
        temp=v[now];//队列出入的大量操作
        vis[now]=1;//消失
        if(temp.a[0]==k||temp.a[1]==k||temp.a[2]==k){
            cout<<"yes\n"<<temp.step<<"\n";
            return;
        }
        for(int i=0;i<3;i++){
            for(int j=0;j<3;j++){
                if(j!=i){
                    for(int i=0;i<3;i++) node0.a[i]=temp.a[i];
                    int vfree=vmax[j]-temp.a[j];
                    if(temp.a[i]>=vfree){
                        node0.a[i]=node0.a[i]-vfree;
                        node0.a[j]=vmax[j];
                    }
                    else{
                        node0.a[j]+=temp.a[i];
                        node0.a[i]=0;
                    }
                    node0.step=temp.step+1;
                    if(search(node0)== false){
                        cnt++;
                        v.push_back(node0);
                        vis[cnt]=1;
                        q.push(cnt);
                    }
                }

            }
        }
        q.pop();
    }
    cout<<"no\n";
    return;
}
int main(){
//    freopen("a.in","r",stdin);
    while (cin>>vmax[0]>>vmax[1]>>vmax[2]>>k){
        while (q.size()){//bug 最后直接输出yes 会导致队列内还有元素
            q.pop();
        }
        vis.clear();
        v.clear();
        cnt=0;
        node0.a[0]=vmax[0];
        node0.a[1]=0;
        node0.a[2]=0;
        node0.step=0;
        v.push_back(node0);
        bfs(0);
    }
    return 0;
}
小结
  • map的key使用结构体要重定义<,但是对于结构体内还有数组的不管用,因此判断存在某个还是用自己写search判断
  • queue最后还会剩元素
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值