算法笔记#4:深搜(DFS)

写在最前:

       粉丝快要破百了,非常感谢大家的支持。每次在撰稿的时候,都感觉自己站在舞台的中央,都感觉台下始终有人在期待着我的表现,这种感觉,从小到大算是很少有了。。

       2022年12月3号,离这一年结束,也就只有二十来天了。作者不算是什么特别强的人,只是一个经历了高考的失利,去了一个不知名的大学,和周围的人一样普通的大一新生罢了。写文章的初衷是想在生活中找一个让我能够去坚持的事情,顺便记录一下学习的过程,给前进路上的每一个脚印存一个档。我制作了一个算法笔记更新的日程表,以后坚持一周至少发一篇,争取两篇。经历了高考的挫折之后,应该深刻的认识到什么叫患得患失。大学四年,哪怕是坚持去追一个女孩,都比漫无目的、四处乱撞、最后原地踏步来的好。

以上只是个人想法,不多说了。深搜部分也准备分两篇发。

题目均来自TZOJ。


何为搜索算法?

        搜索实质上是树的应用。初始状态对应着根节点,目标状态对应着目标节点。排在前面(上一级)的节点叫做父节点,其后的点叫做子节点,同一层中的节点是兄弟节点,由父节点产生子节点的过程叫扩展。完成搜索的过程就是找到一条从根节点到目标节点的路径,找出一个最优的解。

深搜(BFS):

        深搜的基本思想是:为了求得问题的解,先选择某一种可能情况向子节点进行探索,在探索过程中,一旦发现原来的选择不符合要求,就回溯到父节点重新选择另一节点,继续向前搜索(在分岔口选错了路,沿着错误的路走到头以后意识到了,再原路返回至分岔口,选择另外一条路重复上述过程)。如此反复进行,直至求得最优解。一般深搜的过程要使用递归来实现。

深搜的过程中还有一个重要的步骤是回溯。回溯一般就需要对走过的路径进行标记。具体操作看代码。


先来看两道类似的看地图找人类(我自己起的名字)题目

玉树搜救行动

描述

自从玉树受灾以来,有关部门一直在现场抢救落难的人。他们用个种方法搜救,用上了搜救犬,有了搜救犬找到生命迹象就容易了。
 假设现场用一个矩阵表示,抢救的有多条搜救犬,受灾的人也有多个可能。
例子:
#p.d#p#
#####.#
d……..#
######p
d表示搜救狗,p表示受灾的人,点表示可以通行的路,#表示石头挡住的路,不能通行。
搜救狗只能上下左右走,不能越过障碍物。
上面的那个例子最多可以救到2个人。因为第三个人被四周包围搜救狗无法到达。

输入

输入数据有多组。每组两个整数R,C, 2=<R,C<=100, R表示矩阵的行数,C表示矩阵的列数,然后输入相应矩阵,矩阵保证有搜救狗和受灾的人。当输入R=0且C=0时候输入结束。

输出

输出搜救狗最多能救多少受灾的人的数量。

样例输入

4 7
#p.d#p#
#####.#
d.....#
######p
0 0

样例输出

2

从数组的第一个元素开始搜索,如果遇到了边界或者遇到“#”号则不再递归直接返回。

把找到人的节点标记修改为“#”,下一次遍历的过程中就可以直接跳过了。

#include<stdio.h>
using namespace std;
#define maxn 1001
int dir[4][2]={{0,1},{0,-1},{-1,0},{1,0}};//四个方向,上下左右,下一步走向哪,直接加dir的数   组元素即可
int r,c,count=0;
char map[maxn][maxn];
void dfs(int x,int y){
    if(map[x][y]=='#'||x<0||x>=r||y<0||y>=c)//判断是否超出边界,超出就不再递归,直接返回,这也是递归函数的出口
    return;
    if(map[x][y]=='p')
    count++;
    map[x][y]='#';
    int i;
    for(i=0;i<4;i++)
    dfs(x+dir[i][0],y+dir[i][1]);
}
int main()
{
    int i,j;
    while(scanf("%d%d",&r,&c),r||c){
        count=0;
        for(i=0;i<r;i++)
        scanf("%s",map[i]);
        for(i=0;i<r;i++)
        {
            for(j=0;j<c;j++){
                if(map[i][j]=='d')
                dfs(i,j);
            }
        }
        printf("%d\n",count);
    }
    return 0;
}

counting sheep

描述

A while ago I had trouble sleeping. I used to lie awake, staring at the ceiling, for hours and hours. Then one day my grandmother suggested I tried counting sheep after I'd gone to bed. As always when my grandmother suggests things, I decided to try it out. The only problem was, there were no sheep around to be counted when I went to bed.


Creative as I am, that wasn't going to stop me. I sat down and wrote a computer program that made a grid of characters, where # represents a sheep, while . is grass (or whatever you like, just not sheep). To make the counting a little more interesting, I also decided I wanted to count flocks of sheep instead of single sheep. Two sheep are in the same flock if they share a common side (up, down, right or left). Also, if sheep A is in the same flock as sheep B, and sheep B is in the same flock as sheep C, then sheeps A and C are in the same flock.


Now, I've got a new problem. Though counting these sheep actually helps me fall asleep, I find that it is extremely boring. To solve this, I've decided I need another computer program that does the counting for me. Then I'll be able to just start both these programs before I go to bed, and I'll sleep tight until the morning without any disturbances. I need you to write this program for me.

输入

The first line of input contains a single number T, the number of test cases to follow.

Each test case begins with a line containing two numbers, H and W, the height and width of the sheep grid. Then follows H lines, each containing W characters (either # or .), describing that part of the grid.

输出

For each test case, output a line containing a single number, the amount of sheep flock son that grid according to the rules stated in the problem description.

Notes and Constraints
0 < T <= 100
0 < H,W <= 100

样例输入

2
4 4
#.#.
.#.#
#.##
.#.#
3 5
###.#
..#..
#.###

样例输出

6

3

"#"代表sheep,“.”代表grass,题目意思就是说如果羊是成群的,互相紧贴的,算一只,单个的羊也算一只。按照第一个例子来说,那五个挨在一起的羊就算作一只,再加上五个落单的,因此一共有6只。过程和上面的差不多。

#include<stdio.h> 
using namespace std;
int  sum, n, m, dx[4]={-1,1,0,0}, dy[4]={0,0,-1,1},i,j,k;
char map[110][110];
void dfs(int x, int y)
{
int d;	
map[x][y] = '.';
	for(d = 0; d < 4; d++)
	{
		int nx=x+dx[d], ny=y+dy[d];
		if(nx>=0&&nx<n&&ny>=0&&ny<m&&map[nx][ny]!='.')
		{
			map[nx][ny] = '.';
			dfs(nx,ny);
		}
	}
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		for(i = 0; i < n; i++)
		scanf("%s", map[i]);
		sum = 0;
		for(i = 0; i < n; i++)
		{
			for(j = 0; j < m; j++)
			{
				if(map[i][j] == '#')
				{
					dfs(i,j);
					sum++;
				}
			}
		}
		printf("%d\n", sum);
	}
}

八皇后问题

描述

在国际象棋棋盘上放置八个皇后,要求每两个皇后之间不能直接吃掉对方。

输入

输出

按给定顺序和格式输出所有八皇后问题的解(见样例,要求按照列优先的顺序输出各个解)。

样例输入

样例输出

No. 1
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 0 1 0 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 1 0 0 0 0 0 
No. 2
1 0 0 0 0 0 0 0 
0 0 0 0 0 0 1 0 
0 0 0 1 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 1 
0 1 0 0 0 0 0 0 
0 0 0 0 1 0 0 0 
0 0 1 0 0 0 0 0 
......

这是一道,深搜回溯递归结合的经典题。具体做法也是我上网课学来的。

八皇后的基本规则如下:在一个8*8的棋盘中放置8个皇后,要求每一横列,竖列,斜列只有一个皇后。那么当第col列被占领后同一列都不能再放置皇后,那么就需要把第c列设置为占领状态。这里比较麻烦的是对角线,有上下对角线之分。当第一个位置被皇后占领后,上下对角线都要设置为被占领状态。在这里1代表占领,0代表未占领。递归终止的条件是,皇后又地方可以放,或者皇后在全部遍历完了还是没地方放。

在递归之后要进行回溯。吧所有的数据都初始化,从头再来以寻找最多的可行放法。

我在代码后面写了注释,希望大家能看懂。

#include<bits/stdc++.h>
using namespace std;
int place[8]={0};//皇后摆放后所在的列
bool flag[8]={1,1,1,1,1,1,1,1};//标记占领,表示第col列是否可占用,1表示可
bool d1[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
bool d2[15]={1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};//初始化棋盘,全部都是1(可放),接下来0代表不可放。bool类型可以直接true or fluse
int number = 0;
void print();
void judge(int n);

int main()
{
    judge(0);
    return 0;
}
void judge(int n)
{
    int c;
    for(c=0;c<8;c++)
    {
        if(flag[c]&&d1[n-c+7]&&d2[n+c])
        {
            place[n]=c;//摆放皇后
            flag[c]=false;//宣布占领第col列
            d1[n-c+7]=false;//占领上对角线
            d2[n+c]=false;//占领下对角线
            if(n<7)//递归的出口就在这里,在第八行及第八行之前如果找到了可以放的地方就退出,执行打印
            judge(n+1);
            else 
            print();
            //接下来开始回溯(探求所有可能的解)
            flag[c]=true;//删除皇后,将所有数据初始化,再次从头开始
            d1[n-c+7]=true;
            d2[n+c]=true;


        }
    }
}
void print()
{
    int col,i,j;
    number++;
    printf("No. %d\n",number);
    int table[8][8]={0};
    for(i=0;i<8;i++)
      table[i][place[i]]=1;
      for(j=0;j<8;j++)//注意,按照列优先的顺序输出。如果是行优先,for循环的位置换一换就行
    {
        for(i=0;i<8;i++)
        {
            if(i==7)
            printf("%d",table[i][j]);
            else
            printf("%d ",table[i][j]);
        }
        printf("\n");
        
    }
    
}//这题提交了很多次,都错在最后一个数后面多了一个空格,要用if语句判断下标不能漏

未完待续

欢迎私信,评论区交流!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

captainfly_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值