【DFS & BFS】 例题蓝桥2017C++A_2 跳蚱蜢 BFS

深度优先搜索(DFS

深度优先搜索在搜索过程中访问某个顶点后,需要递归地访问此顶点的所有未访问过的相邻顶点。

初始条件下所有节点为白色,选择一个作为起始顶点,按照如下步骤遍历:

 

a. 选择起始顶点涂成灰色,表示还未访问

b. 从该顶点的邻接顶点中选择一个,继续这个过程(即再寻找邻接结点的邻接结点),一直深入下去,直到一个顶点没有邻接结点了,涂黑它,表示访问过了

c. 回溯到这个涂黑顶点的上一层顶点,再找这个上一层顶点的其余邻接结点,继续如上操作,如果所有邻接结点往下都访问过了,就把自己涂黑,再回溯到更上一层。

d. 上一层继续做如上操作,知道所有顶点都访问过。

 

用图可以更清楚的表达这个过程:(注意:图画错了,请将3->4这条路径理解成4->3)

1.初始状态,从顶点1开始

2.依次访问过顶点1,2,3后,终止于顶点3(注意,是4->3)

3.从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5

4.从顶点5回溯到顶点2,并且终止于顶点2

5.从顶点2回溯到顶点1,并终止于顶点1

6.从顶点4开始访问,并终止于顶点4

 

从顶点1开始做深度搜索:

初始状态,从顶点1开始

依次访问过顶点1,2,3后,终止于顶点3(再次提醒,4->3)

从顶点3回溯到顶点2,继续访问顶点5,并且终止于顶点5

从顶点5回溯到顶点2,并且终止于顶点2

从顶点2回溯到顶点1,并终止于顶点1

从顶点4开始访问,并终止于顶点4

 

模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
char map[1000][1000];//用来存储图的信息

/*标记是否搜索过,本题目没有使用,因为是一次访问,直接将访问过的修改即可*/
int logo[1000][1000];

int m,n,sum;

/*表示八个方向,四个方向时,将后面四组删掉就可以了*/
int dir[8][2]= {0,1, 0,-1, 1,0, -1,0, 1,1, 1,-1, -1,1, -1,-1};

void DFS(int x,int y)
{
    if(x>=0&&y>=0&&x<n&&y<m)//这里是判断是否越界,根据题目要求改写
    {
        if(map[x][y]=='W')//如果符合条件就继续递归。
        {
            map[x][y]='.';//标记为‘.’防止多次访问
            for(int i=0; i<8; i++)//因为八个方向,所以循环八次。
                DFS(x+dir[i][0],y+dir[i][1]);
        }
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        sum=0;
        memset(logo,0,sizeof(map));
        getchar();
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                cin>>map[i][j];
            }
            getchar();
        }
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
                if(map[i][j]=='W')
                {
                    DFS(i,j);
                    sum++;//计数
                }
        }
        cout<<sum<<endl;
    }
}

  

 

广度优先搜索(BFS


广度优先搜索在进一步遍历图中顶点之前,先访问当前顶点的所有邻接结点。


  a .首先选择一个顶点作为起始结点,并将其染成灰色,其余结点为白色。
  b. 将起始结点放入队列中。
  c. 从队列首部选出一个顶点,并找出所有与之邻接的结点,将找到的邻接结点放入队列尾部,将已访问过结点涂成黑色,没访问过的结点是白色。如果顶点的颜色是灰色,表示已经发现并且放入了队列,如果顶点的颜色是白色,表示还没有发现
  d. 按照同样的方法处理队列中的下一个结点。
基本就是出队的顶点变成黑色,在队列里的是灰色,还没入队的是白色。

 


用一副图来表达这个流程如下:

1.初始状态,从顶点1开始,队列={1}

2.访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}

3.访问2的邻接结点,2出队,4入队,队列={3,4}

4.访问3的邻接结点,3出队,队列={4}

5.访问4的邻接结点,4出队,队列={ }


从顶点1开始进行广度优先搜索:

初始状态,从顶点1开始,队列={1}

访问1的邻接顶点,1出队变黑,2,3入队,队列={2,3,}

访问2的邻接结点,2出队,4入队,队列={3,4}

访问3的邻接结点,3出队,队列={4}

访问4的邻接结点,4出队,队列={ 空}


结点5对于1来说不可达。

 

模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#define MAX 0x3f3f3f3f
using namespace std;
int map[305][305];//存节点信息
int vis[305][305];//标记数组
int dir[4][2]= {-1,0, 1,0, 0,1, 0,-1};//上下左右四个方向
int end;
struct node
{
    int x,y;//两点表示节点位置
    int time;
} start;//入队列使用
queue<node> q;//队列,自己维护用来存储节点信息
int bfs(int x,int y)
{
    memset(vis,0,sizeof(vis));
    start.x=x,start.y=y,start.time=0;//将传递过来的0.0节点放入结构体
    vis[x][y]=1;//标记为已搜过
    q.push(start);//入队列
    while(!q.empty())
    {
        node now=q.front();//取队头元素
        q.pop();
        if(map[now.x][now.y]==MAX)
        {
            return now.time;//如果符合条件,返回;根据题意自己写符合的条件。
        }
        for(int i=0; i<4; i++)//四个方向入队列
        {
            start.x=now.x+dir[i][0],start.y=now.y+dir[i][1];//将第一个方向的入队列
            start.time=now.time+1;
            if(start.x>=0&&start.y>=0&&vis[start.x][start.y]==0&&start.time<map[start.x][start.y])//判断是否越界
            {
                vis[start.x][start.y]=1;
                q.push(start);
            }
        }
    }
    return -1;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        memset(map,MAX,sizeof(map));
        for(int j=0; j<n; j++)
        {
            int x,y,time;
            scanf("%d%d%d",&x,&y,&time);
            if(map[x][y]>time)
                map[x][y]=time;
            for(int i=0; i<4; i++)//自己建图过程,一般不需要自己建图
            {
                int cx,cy;
                cx=x+dir[i][0],cy=y+dir[i][1];
                if(cx>=0&&cy>=0)
                    if(map[cx][cy]>time)
                        map[cx][cy]=time;
            }
        }
        int ans=bfs(0,0);//从00点开始广搜,根据题目要求具体定
        cout<<ans<<endl;
    }

}

  

 

【eg】跳蚱蜢

如图 所示:

 

有9只盘子,排成1个圆圈。

其中8只盘子内装着8只蚱蜢,有一个是空盘。
我们把这些蚱蜢顺时针编号为 1~8

每只蚱蜢都可以跳到相邻的空盘中,
也可以再用点力,越过一个相邻的蚱蜢跳到空盘中。

请你计算一下,如果要使得蚱蜢们的队形改为按照逆时针排列,
并且保持空盘的位置不变(也就是1-8换位,2-7换位,...),至少要经过多少次跳跃?

注意:要求提交的是一个整数,请不要填写任何多余内容或说明文字。

 

固定套路模式的方法  运行3s左右

#include<iostream>
#include<string>
#include<set>
#include<queue>
//青蛙跳格子,采用裸广搜的方法,几秒可以出答案,但是有时间限制就不行了
//将青蛙跳看作是,圆盘跳动,这样就只有一个变量在变化了
//将圆盘看成是0,初始序列用012345678表示,在广搜的时候用set判一下重
using namespace std;

struct node {
    string str;//局面字符串
    int pos;//0的位置也就是空盘子
    int step;//到达这个局面的步数
    node(string str, int pos, int step) : str(str), pos(pos), step(step) {}

};

int N = 9;
set<string> visited;//已经搜索过的局面
queue<node> q;//用户来广搜的队列
void insertq(node no, int i)//node为新的局面,i为移动方式
{
    string s = no.str;
    swap(s[no.pos], s[(no.pos + i + 9) % 9]);//将0和目标位置数字交换
    //取模是为了模拟循环的数组
    //if (visited.count(s) == 0)//如果没有搜索过这个局面
    if(visited.end() == visited.find(s))
    {
        visited.insert(s);
        node n(s, (no.pos + i + 9) % 9, no.step + 1);
        q.push(n);
    }
}

int main() {
    //初始 0空盘在0位置  走过步数0
    node first("012345678", 0, 0);
    q.push(first);

    while (!q.empty()) {
        node temp = q.front();  //去第一个  因为之前的pop出去了
        if (temp.str == "087654321") {  //结束
            cout << temp.step;
            break;
        } else {
            //四种跳法  相当于有四个邻居
            insertq(temp, 1);
            insertq(temp, -1);
            insertq(temp, 2);
            insertq(temp, -2);
            q.pop(); //四个邻居添加完之后 就可以滚蛋了
        }

    }


}

 

优化方法,为了节省内存,使用 int 来存储数据

#include <iostream>
#include <queue>
#define Maxn 1000000000
 
using namespace std;
 
int s = 123456789,t = 876543219;
 
int di[4] = {-2,-1,1,2},a[10];
 
bool index[Maxn];
 
int get_val(int *a)
{
    int sum=0;
    for(int i=0; i<9; i++)
    {
        sum*=10;
        sum+=a[i];
    }
    return sum;
}
 
void bfs()
{
    int find = 0;
    queue<int> q;
    queue<char> qu;    //计算步数
    q.push(s);
    index[s] = 1;
    qu.push(1);
    while(find != 1)
    {
        int x = q.front(),cnt=8,now;
        int count = qu.front();
        while(x>0)    //将数据存入数组,方便换位置
        {
            if(x%10==9)now=cnt;
            a[cnt--]=x%10;
            x/=10;
        }
        for(int i = 0; i<4; i++)
        {
            swap(a[now],a[(now+di[i]+9)%9]);
            int num = get_val(a);
            if(!index[num])  //判重
            {
                if(num == t)
                {
                    find = 1;
                    cout<<count;
                }
                index[num] = 1;
                q.push(num);
                qu.push(count+1);
            }
            swap(a[now],a[(now+di[i]+9)%9]);  
        }
        q.pop();
        qu.pop();
    }
 
}
 
int main()
{
    bfs();
    return 0;
}

 

 

【eg2】标题:全球变暖


【题目描述】
你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

【输入格式】
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。

照片保证第1行、第1列、第N行、第N列的像素都是海洋。

【输出格式】
一个整数表示答案。

【样例输入】
7
.......
.##....
.##....
....##.
..####.
...###.
.......

【样例输出】
1


资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms


请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

注意:
main函数需要返回0;
只使用ANSI C/ANSI C++ 标准;
不要调用依赖于编译环境或操作系统的特殊函数。
所有依赖的函数必须明确地在源文件中 #include <xxx>
不能通过工程设置而省略常用头文件。

提交程序时,注意选择所期望的语言类型和编译器类型。

#include <iostream>
#include <stdio.h>
#include <queue>

using namespace std;

int n = 0;
string s[1000];
int a[1000][1000];
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int mark[1000][1000] = {0};//标记是否访问过
int ans = 0;
struct point {
    int x, y;
};

void show() {
    //输出看看
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < 7; ++j) {
            cout << a[i][j];
        }
        cout << endl;
    }

}

void bfs(int i, int j) {
    mark[i][j] = 1;
    queue<point> q;
    q.push({i, j});
    int cnt1 = 0, cnt2 = 0; //cnt1记录当前岛屿块的1数量  cnt2记录临海的1数量
    //如果这两个相等 那么这个岛屿将被淹没
    while (!q.empty()) {
        point first = q.front();
        q.pop();
        cnt1++;
        bool over = false;//岛屿是否会被淹没 后面判断是否临海就可以
        for (int k = 0; k < 4; ++k) {//四个方向bfs
            int x = first.x + dx[k];
            int y = first.y + dy[k];

            if (0 <= x && x < n && 0 <= y && y < n && a[x][y] == 0)over = true;//有临海 first那个岛屿会消失
            if (0 <= x && x < n && 0 <= y && y < n && a[x][y] == 1 && mark[x][y] == 0) {
                q.push({x, y});
                mark[x][y] = 1;  //还只能放这里  因为if里面要判断是否被标记过
                //如果把这句放在if上面的话  就无法在if里面判断有岛屿的地方是否已经在当前块里面
                //从而会导致一直在这个块里面循环
            }

        }
        if (over) { //四个方向走完后 这个点临海 将会消失
            cnt2++;
        }
    }
    //这一块岛屿点全部走完  就是一块的bfs完成
    if (cnt1 == cnt2) {
        ans++;
    }
}

int main() {
    //加快输入
    std::ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);


    cin >> n;

    string s[n];
    for (int i = 0; i < n; ++i) {
        cin >> s[i];
    }
    //存入int 中好操作
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (s[i][j] == '.') {
                a[i][j] = 0;
            }
            if (s[i][j] == '#') {
                a[i][j] = 1;
            }
        }
    }
    //show();
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (mark[i][j] == 0 && a[i][j] == 1) {
                bfs(i, j);
            }
        }
    }
    //show();
    cout << ans << endl;
    return 0;
} 

  

 

posted @ 2020-04-03 18:00  Stephen~Jixing  阅读(360)  评论(0编辑  收藏  举报