BFS

背景

输入n*m矩阵,然后从(1,1)搜索至(n,m)

输出最短路径

5 5

0 1 0 0 0

0 1 0 1 0

0 0 0 0 0

0 1 1 1 0

0 0 0 1 0

注意

DP一般能解决搜素问题

所有边等权重时,才能用BFS求最短路

算法核心

初始化一个queue

while(!queue.isempty()){

t=queue.front();

//拓展 t

}

插曲

C++ pair 用法

类模板:template<class T1,class T2> struct pair

参数:T1是第一个值的数据类型,T2是第二个值的数据类型。

功能:pair将一对值(T1和T2)组合成一个值,

        这一对值可以具有不同的数据类型(T1和T2),

        两个值可以分别用pair的两个公有函数first和second访问。

pair<T1, T2> p1;            //创建一个空的pair对象(使用默认构造),它的两个元素分别是T1和T2类型,采用值初始化。
pair<T1, T2> p1(v1, v2);    //创建一个pair对象,它的两个元素分别是T1和T2类型,其中first成员初始化为v1,second成员初始化为v2。
make_pair(v1, v2);          // 以v1和v2的值创建一个新的pair对象,其元素类型分别是v1和v2的类型。
p1 < p2;                    // 两个pair对象间的小于运算,其定义遵循字典次序:如 p1.first < p2.first 或者 !(p2.first < p1.first) && (p1.second < p2.second) 则返回true。
p1 == p2;                  // 如果两个对象的first和second依次相等,则这两个对象相等;该运算使用元素的==操作符。
p1.first;                   // 返回对象p1中名为first的公有数据成员
p1.second;                 // 返回对象p1中名为second的公有数据成员

C++ memset用法(初始化)

memset(dp,0,sizeof(dp));  

int类型的变量一般占用4个字节,对每一个字节赋值0的话就变成了“00000000 00000000 000000000 00000000” (即10进制数中的0)

赋值为-1的话,放的是 “11111111 11111111 11111111 11111111 ”(十进制的-1)

我们在很多程序中都会看到memset(a,127,sizeof(a));这样的代码,127是什么特别的数字呢?通过基础的进制转换可以得知127的二进制表示是01111111,那么在dp数组里放的内容就是“01111111 01111111 01111111 01111111”,(10进制的2139062143),这样就实现了将数组里的全部元素初始化为一个很大的数的目的了,在最短路径问题以及其他很多算法中都是需要用到的。值得注意的是,int类型的范围为2^31-1,大约是2147483647的样子(如果我没有记错的话),所以初始化int类型的数组也可以使用127这个数值。

如果是128呢?因为128的二进制是10000000,那么放的内容就是10000000 10000000 10000000 10000000,经过计算可得这个数是-2139062144。这样就可以将数组初始化为一个很小的数了。

题解(c++)

模拟队列版

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N=110;

typedef pair<int,int>PII;//数对,相当于二维数组 
int n,m;
int g[N][N];// map
int d[N][N];// distance
PII q[N*N];// 模拟队列 

int bfs(){
    int hh=0,tt=0;
    memset(d,-1,sizeof d);//全部初始化为-1 
    d[0][0]=0;
    int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
    while(hh<=tt){
        auto t=q[hh++];//出队 
        for(int i=0;i<4;i++){
            int x=t.first+dx[i],y=t.second+dy[i];//四个方向遍历 
            if(x>=0&&y>=0&&y<m&&x<n&&g[x][y]==0&&d[x][y]==-1){//非路障且未经过 
                d[x][y]=d[t.first][t.second]+1;//距离加一 
                q[ ++tt ]={x,y};//新元素入队 
            }
        }
    }
    return d[n-1][m-1];//返回终点最短路径长度 
} 


int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            cin>>g[i][j];
        }
    }
    cout<<bfs();
}

队列版

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
using namespace std;
const int N=110;
typedef pair<int,int> PII;
int g[N][N],d[N][N];
int m,n;
queue <PII> que;

int bfs(){
    memset(d,-1,sizeof d);//初始化地图 
    d[0][0]=0;//起点标记 
    que.push({0,0});//起点入队 
    int dx[4]={-1,0,1,0},dy[4]={0,-1,0,1};//定义四个方向
    while(que.size()){//当队列不为空时 
        auto t=que.front();//取出队头
        que.pop();//队头出队 
        for(int i=0;i<4;i++){
            int x=t.first+dx[i],y=t.second+dy[i];//四个方位遍历获取当前坐标 
            if(x<n&&y<m&&x>=0&&y>=0&&g[x][y]==0&&d[x][y]==-1){
                d[x][y]=d[t.first][t.second]+1;//距离+1 
                que.push({x,y});//将当前坐标入队 
            }
        } 
    } 
    return d[n-1][m-1];
}

int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
        cin>>g[i][j];    
        }
    }
    cout<<bfs();
    return 0;
}

DFS

普遍来说,BFS要比DFS简单很多,虽然两个都是常规的搜索方法,但是BFS通常用于求最短路之类的最优解,有属于它的模板,而且,只需要简单的进行剪枝(例如记录走过的路,下次不再走),

而DFS不一样,DFS不只是简单求最优解,而是求加上了不少限制条件的答案,因此难度系数剧增,很多时候我们最开始能想到的是通过暴力的枚举来进行求解,但是这样的时间成本过高,因此我们通常选择对不同的体型进行对应的剪枝,

例如本题

背景

小狗在一个古老的迷宫里发现了一根骨头,这让他非常着迷。然而,当他把它捡起来时,迷宫开始摇晃,小狗能感觉到地面在下沉。他意识到骨头是个陷阱,他拼命想走出这个迷宫。迷宫是一个长方形,大小为N乘M。迷宫里有一扇门。开始时,门是关闭的,它会在第T秒打开一小段时间(不到1秒)。因此,小狗必须准确地在第T秒到达门口。在每秒钟内,他可以将一个街区移动到上、下、左、右相邻街区中的一个。一旦他进入一个街区,这个街区的地面就会开始下沉,并在下一秒钟消失。他不能在一个街区停留超过一秒钟,也不能搬进去过的街区。可怜的小狗能活下来吗?请帮帮他。

输入

输入由多个测试用例组成。每个测试用例的第一行包含三个整数N、M和T(1<N,M<7;0<T<50),分别表示迷宫的大小和门打开的时间。接下来的N行给出了迷宫布局,每行包含M个字符。字符是以下之一:“X”:一块墙,小狗不能进入;“S”:小狗的起点;‘D’:门;或者“.”:一个空块。输入以三个0结束。此测试用例不被处理。

输出

对于每个测试案例,如果小狗能活下来,在一行中打印“是”,否则打印“否”。

样例

4 4 5

S.X.

..X.

..XD

....

3 4 5

S.X.

..X.

...D

0 0 0

->

NO

YES

题解

核心代码

void dfs(int si,int sj,int Tn){
    if(si>N||sj>M||si<=0||sj<=0)return ;
    if(Tn==T&&si==di&sj==dj)escape=1;
    if(escape)return ;//一旦找出解,全部返回,不再判断 
    int temp=T-Tn-abs(si-di)-abs(sj-dj);
    //检测时间和哈夫曼距离奇偶性是否相同
    if(temp<0||temp%2==1) return ;//奇偶性剪枝
    for(int i=0;i<4;i++){
        if(map[si+vec[i][0]][sj+vec[i][1]]!='X'){
            map[si+vec[i][0]][sj+vec[i][1]]='X';//常规剪枝
            dfs(si+vec[i][0],sj+vec[i][1],Tn+1);
            map[si+vec[i][0]][sj+vec[i][1]]='.';//回溯
        }
    }
    return;
}

剪枝与回溯解释

常规剪枝操作有

if(si>N||sj>M||si<=0||sj<=0)return ;

map[si+vec[i][0]][sj+vec[i][1]]='X';这里的剪枝,类似于BFS当中的记录是否访问过

if(M*N>=T)dfs(si,sj,0);

判断总距离最大值是否大于时间

特殊剪枝

奇偶性剪枝:

奇偶性剪枝.png

因此在迭代中可以随时检验奇偶性是否相同,以此实现剪枝目的

回溯

从DFS每次出发作为视角,每次路走不通的时候,我们都需要把走过的路设置为未走过,因此才不会影响下一次的搜索。(恢复现场)

只有当DSF->return之后才会执行刷新路径操作

附加(简单题):全排列问题

input:3

output: 123|321|132|213|231|312

n位数字,从第一位开始填,每一位不能和之前的相同

类似树形结构,但不是层层遍历,而是追求深度,一条路走到黑,如果走不通,就回溯,回到上一个状态,看看能不能走另一条路

#include <iostream>

using namespace std;
const int N=10000;
int m,n;
int map[N];
int re[N];
void dfs(int n,int m){
    if(m==n+1){
        for(int i=1;i<=n;i++){
            cout<<re[i]<<" ";
            re[N]=0;
        }
        cout<<endl;
        return;
    }else{
        for(int j=1;j<=n;j++){
        if(map[j]!=0){
            re[m]=j;
            map[j]=0;//标记走过
            dfs(n,m+1);
            map[j]=j;    //回溯
            }
        }
    }
}

int main(){
    while(cin>>n){
        for(int i=1;i<=n;i++)map[i]=i;//初始化
        dfs(n,1);
    }
    return 0;
}

题目很经典也很简单,适合了解回溯,了解DFS运行机制

最近发生了很多事情,有好有坏,庆幸在不开心的时候有人陪伴倾诉,庆幸在开心的时候没有过度喜悦。不管是科研、开发、绩点、比赛and so on,希望每件事都能够做到不让自己后悔

文章作者: P4ul
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 打工人驿站
算法 c++
喜欢就支持一下吧
打赏
微信 微信
支付宝 支付宝