大话大话数据结构——DFS?BFS?

在计院的保研选手推荐我算法笔记之后,我觉得我又找到了一本适合上机联系、学习知识的好书,所以一下内容是我学习《算法笔记》的读书笔记

目录

DFS

for example

对!你已经掌握DFS的真谛了

最后,我们再来一道题康康DFS如何下手

BFS

BFS的基本写法

for example

for another example

牢记这6步,BFSso easy


DFS

DFS 深度优先搜索,首先,DFS是一种算法,但是对于不同的问题,DFS的代码形式也不同,其次,DFS是一种思想,是有规律可循的代码架构思想。

以下走迷宫很好的说明了DFS是一种什么样的思想:(图片来自《算法笔记》)

假如你一个人来到了一个迷宫,如果你想要找出口,那么最简单,也一定可以找到出口的方法就是使用DFS。从上图可以看到我们是一直往右遍走,如果遇到墙就折返,然后继续向右。如果用DFS来描述,我们每次遇到岔道口就会进行选择,马上去探索我们选择的某个分支,如果遇到墙就折返,要是又遇到岔路口,那么就老母猪带胸罩——一套又一套,继续按照上面这一套动作,完成这个岔路口的所有分支,直至找到终点。

一套又一套,是不是就想到了递归嵌套了呢?

是的,DFS经常是通过递归来完成的,如果不用递归也可以,自己实现这样栈式的搜索,只不过比较麻烦,这里碍于篇幅(lan)便不与说明。

那么如何实现DFS呢,实现斐波那契数列的时候,我们也是F(n)=F(n-1)+F(n-2) 这就是上面我们加粗的岔道口,要是遇到n=1 0那么就返回1,这就是我们上面加粗的墙。

所以,实现DFS,需要明白的第一点,就是,什么是分支(递归式),什么是墙(递归边界)。

for example

这道题也是算法笔记的题目,对于这道题,岔路口便是对于每一个物体i,我应该选择放入还是不放入呢?那什么是墙呢,是背包中放入的物体总重量大于V了,那就是墙。以及我们需要一个全局变量,让每次递归到最后一个物体的时候进行对比,是否遇到了价值最大的方案。

所以,我们来写一个这个问题的DFS吧。

//先写一下函数的定义
void DFS(int index,int sumW,int sumC){...}

//为什么要这样写?每次递归我们就是处理完了上一个物品,准备看下一个物品要不要放嘛,所以index是必须的,来标记现在处理到第几个物品了
//sunW和sumC是题目中提到了唯二个会变化的变量,用来在最后进行比较是否超重是否价值最大。

其实细心的小伙伴会发现这里有一个怪怪的地方,不过我们先这样认为写一下代码吧。

#include <iostream>
#include <stdio.h>
using namespace std;
const int maxn=30;//最多30个物品可供选择
int n,V,maxValue = 0;
int w[maxn],c[maxn];
//DFS,index is the code of stuff
//sumW and sumC refer sum weight and value
void DFS(int index,int sumW,int sumC){
    //墙->处理完n个数
    if(index==n){
        //最优处理
        if(sumW<=V&&sumC>maxValue){
            maxValue=sumC;
        }
        return;
    }
    //岔道口
    DFS(index+1,sumW,sumC);//不选index这件商品
    DFS(index+1,sumW+w[index],sumC+c[index]);
}

int main()
{
    scanf("%d %d",&n,&V);
    for(int i=0;i<n;i++){
        scanf("%d",&w[i]);
    }
    for(int i=0;i<n;i++){
        scanf("%d",&c[i]);
    }
    DFS(0,0,0);
    printf("%d\n",maxValue);
    return 0;
}
测试集:
5 8
3 5 1 2 2
4 5 2 1 3
答案:
10

其实我们发现,这个就是把n个物品这个序列求出所有子序列(可以不连续),对于每一个子序列进行判断选取最优。

对!你已经掌握DFS的真谛了

(麦兜:啊 DFS也有真谛啊)

对的,深度优先之所以叫做深度优先,是因为这个探索的过程中,就可以抽象成对每一个子序列的结果进行判断,看一下这个序列满不满足条件。当然,我上面说怪怪的地方,就是这里其实可以不用列出所有的子序列,当你发现放入这一个物品就超重的时候,我们就可以直接结束操作了,不用再遍历那些没用的序列,这就叫做剪枝

void DFS(int index,int sumW,int sumC){
    if(index==n){
        return;//墙
    }
    //岔道口
    DFS(index+1,sumW,sumC);
    if(sumW+w[index]<=V){
        if(sumC+c[index]>ans){
            ans=sumC+c[index];//最优处理
        }
        DFS(index+1,sumW+w[index],sumC+c[index]);
    }
}

最后,我们再来一道题康康DFS如何下手

我们的传入参数 index ,为了便于了解已经选了几个数,nowK传入当前已经选择的数的个数,sum和sumSqu分别记录当前选择的整数的和,以及已经选择的整数的平方和。

此外,需要一个数组来保存给定的N个整数,需要一个数组来选择已选的整数,需要一个数组来保存最优方案,以及一个保存最大平方和。 

#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
const int maxn=30;
int n,k,X,maxSquareValue = -1;
int num[maxn];
vector<int> temp,ans;
void DFS(int index,int nowK,int sum,int squareSum){
    //墙
    if(nowK==k&&sum==X){
        if(squareSum>maxSquareValue){//最优处理
            maxSquareValue=squareSum;
            ans=temp;
        }
        return;
    }
    //墙
    if(index==n||nowK>k||sum>X)return;
    //岔道口
    temp.push_back(num[index]);
    DFS(index+1,nowK+1,sum+num[index],squareSum+num[index]*num[index]);
    temp.pop_back();
    //岔道口
    DFS(index+1,nowK,sum,squareSum);//不选index这个数
}

int main()
{
    scanf("%d %d %d",&n,&k,&X);
    for(int i=0;i<n;i++){
        scanf("%d",&num[i]);
    }
    DFS(0,0,0,0);
    printf("%d\n",maxSquareValue);
    for(vector<int>::iterator it=ans.begin();it!=ans.end();it++){
        cout<<*it<<' ';
    }
    return 0;
}

 

BFS

又是这样一个迷宫,不过,这一次是一大群人一起去了,并且这一次,在一个路口的下面所有分支大声说话,大家都能听到,除非有个人到了这个分支的分支(比如分支是A,但是D这边说话A听不到,只有BC可以传话到A),那么就听不见了,所以大家遇到路口决定分头行动,但是为了保证安全性,每个路口只探索这个路口的不同分支有没有遇到终点,如果没有遇到重点,那么最好还是再回到分支起点,然后共同探索这个分支的第一个路口,如果遇到墙就回来上个分支路口,遍历没有遇见的第二个路口。依次类推,最后直到找到终点。这就是BFS的迷宫策略。

所以上图的过程就是

A

BC

CDE

DEFG

EFGHIJ

FGHIJKLM

GHIJKLM

G是出口算法结束

所以你可以看到,这个BFS的过程很像一个队列,不断的出一个,然后将出去分支的下面所有分支传入,直到出去的是出口。

BFS的基本写法

void BFS(int s){
    queue<int> q;//定义队列,并且将起点s入队
    q.push(s);
    while(!q.empty()){//循环条件是q非空
        取出队首元素top;
        访问队首元素top;//访问可以是任何事情,例如将其输出
        将队首元素出队;//访问完将其出队
        将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
    }
}

for example

#include <iostream>
#include <queue>
#include <stdio.h>
using namespace std;
const int maxn=100;
struct node{
    int x,y;
}Node;
int n,m;//矩阵大小
int matrix[maxn][maxn];
bool inq[maxn][maxn]={false};
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
bool judge(int x,int y){//判断该位置是否入需要访问
//越界
if(x>=n||x<0||y>=m||y<0)return false;
//当前位置为0 或者已经入过队
if(matrix[x][y]==0||inq[x][y]==true)return false;
return true;
}
void BFS(int x,int y){
    queue<node> Q;//定义队列,并且将起点s入队
    Node.x=x,Node.y=y;
    Q.push(Node);
    inq[x][y]=true;
    while(!Q.empty()){//循环条件是q非空
        node top=Q.front();//取出队首元素top;
            //访问可以是任何事情,例如将其输出
        Q.pop();//访问完将其出队
        //将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
        for(int i=0;i<4;i++){
            int newX=top.x+X[i];
            int newY=top.y+Y[i];
            if(judge(newX,newY)){
                Node.x=newX,Node.y=newY;
                Q.push(Node);
                inq[newX][newY]=true;
            }
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int x=0;x<n;x++){
        for(int y=0;y<m;y++){
            scanf("%d",&matrix[x][y]);
        }
    }
    int ans=0;
    for(int x=0;x<n;x++){
        for(int y=0;y<m;y++){
            if(matrix[x][y]==1&&inq[x][y]==false){
                ans++;
                BFS(x,y);
            }
        }
    }
    cout << ans << endl;
    return 0;
}
测试代码
7 6
0 1 1 1 0 0 1
0 0 1 0 0 0 0
0 0 0 0 1 0 0
0 0 0 1 1 1 0
1 1 1 0 1 0 0
1 1 1 1 0 0 0
测试答案:
4

在这里,我们发现为了防止检测四周的时候回回头检测之间就已经检测过的,我们就设定一个inq数组来表示是否入队过,当然也可以在strude node的定义里定义。

for another example

给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而“.”代表平地,S表示起点,T代表终点。移动过程中,如果当前位置是(x,y)(下标从0开始),且每次只能前往上下左右(x,y+1) (x,y-1) (x-1,y) (x+1,y)四个位置的平地,求从起点S到达终点T的最少步数。

这是《算法笔记给出的代码》

#include <iostream>
#include <queue>
#include <stdio.h>
#include <cstring>
using namespace std;
const int maxn =100;
struct node{
    int x,y;
    int step;
}S,T,Node;
int n,m;
char maze[maxn][maxn];
bool inq[maxn][maxn]={false};
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
bool test(int x,int y){
    if(x>=n||x<0||y>=m||y<0)return false;
    if(maze[x][y]=='*')return false;
    if(inq[x][y]==true)return false;
    return true;
}
int BFS(){
    queue<node> q;//定义队列,并且将起点s入队
    q.push(S);
    while(!q.empty()){//循环条件是q非空
        node top =q.front();//取出队首元素top;
        //访问可以是任何事情,例如将其输出
        q.pop();//访问完将其出队
        if(top.x==T.x&&top.y==T.y){
            return top.step;
        }
        for(int i=0;i<4;i++){//将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;
            int newX=top.x+X[i];
            int newY=top.y+Y[i];
            if(test(newX,newY)){
                Node.x=newX,Node.y=newY;
                Node.step=top.step+1;
                q.push(Node);
                inq[newX][newY]=true;
            }
        }
    }
    return -1;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++){
        getchar();
        for(int j=0;j<m;j++){
            maze[i][j]=getchar();
        }
        maze[i][m+1]='\0';
    }
    scanf("%d%d%d%d",&S.x,&S.y,&T.x,&T.y);
    S.step=0;
    printf("%d\n",BFS());
    return 0;
}

这是我自己写的代码

#include <iostream>
#include <queue>
#include <stdio.h>
using namespace std;
const int maxn=100;
char matrix[maxn][maxn];
int STEP[maxn][maxn]={0};//相比于上面的inq 我使用步数来又存放这一步骤的步数又防止回走
int X[4]={0,0,1,-1};
int Y[4]={1,-1,0,0};
int n,m;
struct node{
    int x;
    int y;
}S,E,Node;
bool viable(int x,int y){//防止越接和撞墙,其实这里可以和防止回走在一起
    if(x<0||x>=n||y<0||y>=m)return false;
    if(matrix[x][y]=='*')return false;
    return true;
}
void BFS(){
    queue<node> q;
    while(!q.empty())q.pop();
    q.push(S);
    while(!q.empty()){
        node top=q.front();
        int x=top.x;
        int y=top.y;
        int step=STEP[x][y];
        cout<<x<<','<<y<<','<<step<<endl;
        q.pop();
        if(x==E.x&&y==E.y){
            cout<<"get the END!"<<endl;
            break;
        }
        for(int i=0;i<4;i++){
            int newx=x+X[i];
            int newy=y+Y[i];
            cout<<newx<<newy<<endl;
            if(viable(newx,newy)&&(STEP[newx][newy]==0)){//后面那个条件是防止回走
                cout<<newx<<newy<<"viable"<<endl;
                Node.x=newx;Node.y=newy;
                STEP[newx][newy]=step+1;
                q.push(Node);
            }
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    getchar();
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            scanf("%c",&matrix[i][j]);
            STEP[i][j]=0;
            if(matrix[i][j]=='S'){
                S.x=i;S.y=j;STEP[i][j]=1;
            }
            else if(matrix[i][j]=='E'){
                E.x=i;E.y=j;
            }
        }
        getchar();
    }
    BFS();
    return 0;
}

稍作修改就可以了呢

测试数据:
5 5
.....
.*.*.
.*S*.
.***.
...T*
2 2 4 3
结果:
11

牢记这6步,BFSso easy

  • 定义队列,并且将起点s入队
  • 循环条件是q非空
  • 取出队首元素top;
  • 访问可以是任何事情,例如将其输出
  • 访问完将其出队
  • 将top的下一层结点中未曾入队的结点全部入队,并设置为已入队;

 

 

 

 

 

本文纯属本人瞎编,如果错误,欢迎指出。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值