算法笔记#5:深搜(补题)

这篇文章是上一篇文章的延续:

算法笔记#4:深搜(DFS)_captainfly_的博客-CSDN博客


写在最前:学深搜的时候确实遇到了一些问题,这段时间也是去看了很多参考的资料,受益匪浅,我会将这些一起整理到文章之中。解决回溯问题,实际上就是一个决策树的遍历过程。那么在这个过程中我们就需要思考三个问题:

1 路径:也就是已经做出的选择。

2 选择列表:也就是你当前可以做的选择。

3 结束条件:也就是达到决策树底层,无法再做选择的条件。

这三个问题也就是决策树遍历过程的核心。

题目均来自TZOJ。


 1001  勘探油田

描述

某石油勘探公司正在按计划勘探地下油田资源。他们工作在一片长方形的地域中,首先将该地域划分为许多小正方形区域,然后使用探测设备分别探测每一块小正方形区域是否有油。若在一块小正方形区域中探测到有油,则标记为’@’,否则标记为’*’。如果两个相邻区域都为1,那么它们同属于一个石油带,一个石油带可能包含很多小正方形区域,而你的任务是要确定在一片长方形地域中有多少个石油带。所谓相邻,是指两个小正方形区域上下、左右、左上右下或左下右上同为’@’。

输入

输入数据将包含一些长方形地域数据,每个地域数据的第一行有两个正整数m和n,表示该地域为m*n个小正方形所组成,如果m为0,表示所有输入到此结束。否则,后面m(1≤m≤100)行数据,每行有n(1≤n≤100)个字符,每个字符为’@’或’*’,表示有油或无油。每个长方形地域中,’@’的个数不会超过100。

输出

每个长方形地域,输出油带的个数,每个油带值占独立的一行。油带值不会超过100。

样例输入

3 5
*@*@*
**@**
*@*@*
0 0

样例输出

1

这道题是上一篇文章中“Counting sheeps”一题的同类题,“玉树搜救行动”一题的加强版。我们从一个有油的地方开始,通过f数组,对四面八方进行遍历,符合条件就继续遍历,不符合条件就return。其中一些遍历过的地方,无论是有油还是没油,走过一遍之后都要做上标记。具体方法已经在代码之后的注释中写出,不过多赘述。

#include<bits/stdc++.h>
#define maxn 1001
using namespace std;
int visit1[maxn][maxn],sum=0,m,n;//如果用visit的话会出现不明确,原因是visit和保留字冲突了,后面加个1就可以了
int f[8][2]={{-1,0},{1,0},{0,-1},{0,1},{-1,-1},{1,-1},{-1,1},{1,1}};//下一步是四面八方
char maps[maxn][maxn];
void dfs(int i,int j)
{
	if(maps[i][j]=='*') return ;//回到之前的起点,换一个方向,递归终止的条件
	maps[i][j]='*';//如果说把这一行去掉,那么输出的结果就会变成5,想想时为什么
	//上面这一句话,说明如果遇到了一个有油的地方,就把这个地方标记成*,代表已经来过
	for(int k=0;k<8;k++)
	{
		int x=i+f[k][0];
		int y=j+f[k][1];
		if(x<0||x>=m||y<0||y>=n||visit1[x][y]==1||maps[x][y]=='*') continue;//回到上面那个for循环,改变k的值,即换个方向
		visit1[x][y]=1;//如果说找到了一个有油的地方标记为1,表示来过
		dfs(x,y);
		visit1[x][y]=0;
	}
}
int main()
{
	while(cin>>m>>n)
	{
		memset(visit1,0,sizeof(visit1));
		if(m==0) break;
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)
			{
				cin>>maps[i][j];//输入地图
			}
		}
		for(int i=0;i<m;i++)
		{
			for(int j=0;j<n;j++)
			{
				if(maps[i][j]!='*')//遇到了一个有油的地方,以这个地方为起点
				{
					sum++;
					dfs(i,j);//遍历,这一个循环的过程代表,当找到了一个有油的地方时,以这个地方为起点
				}
			}
		}
		cout<<sum<<endl;
		sum=0;//初始化,下一组数据
	}
	return 0;
}

 我此时再把“counting sheeps”一题的代码放在这里,是否可以找到很多相似之处呢?

#include<bits/stdc++.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);
	}
}

那么从这三道“地图搜索题”之后我们可以总结出一套做这种题的模版。在这个模版里包括几个因素:

用来输入地图都二维数组数组;

用来决定方向的二维数组(由0和1构成的二维数组,决定下一步往哪个方向走);

递归函数:


接下来我们来看两道有关全排列的问题。

       全排列也是回溯算法的一种经典运用。我们高中的时候就做过排列组合的数学题,我们也知道n个不重复的全排列共有n!个。那么一般我们都是这样穷举全排列的:

比如说给你三个数1、2、3。

先固定第一位为1,然后第二位可以是2,那么第三位只能是3;然后可以把第二位变成3,那么第三位就只能是2了;现在就只能变化第一位,变成2,然后再穷举后两位。。。。

1002 排列型枚举

描述

把 1~n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入

一个整数n(n<10)。

输出

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

样例输入

3

样例输出

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
全排列问题,可以用一个放盒子的例子来描述过程。那么你站在下一个盒子面前就会出现选择,把选择过的标记好,没选择过的按照从小到大的顺序(题目要求字典序排序)放入。递归终止的条件就是当你站在第n+1个盒子面前,就说明你已经完成了所有盒子的放置。具体见代码。

#include <bits/stdc++.h>
using namespace std;
int a[101];//盒子,目前只能放最多100个盒子,你可以自己加,但是要同时修改b的个数 
int b[101]={0};//标记数组,标记是否已经放入 
void dfs(int step,int n)//当前在第几个盒子上,总共有多少数 
{
	int i;
	if(step==n+1){//如果站在第n+1个盒子面前了,那说明,已经放好所有的了 
		for(i=1;i<=n;i++){//输出这个组合 
			if(i!=n)
			printf("%d ",a[i]);
			else
			printf("%d",a[i]);
		}
		printf("\n");
		return;//返回上一层,也就是调用它的地方 (回溯) ,然后才能输出更多的结果
	}
	for(i=1;i<=n;i++){//遵循最小原则  放入数字 
		//判断数字是否已经放入到之前的盒子
		if(b[i]==0){//数字并未放入 
			a[step]=i;//放入
			b[i]=1;//标记已经放入 
			//继续需要往下一个盒子走
			dfs(step+1,n); 
			//走完这个盒子之后的所有操作,已经又回溯到这个盒子了,马上就要在此盒子放入下一个数了,所以我们需要将刚才的数字收回 
		    	b[i]=0; //等于就是说,在这个起始点的基础上再往后退一步,多出一个盒子,进行新一轮的搜索
		} 
	} 
	return;
} 
int main(){
	int n,a,i;
	scanf("%d",&n);
	dfs(1,n);//step从1开始,内部会自动的进行递归,最后到n+1结束
    
	return 0;
}

1003 组合型枚举

描述

从 1~n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。n>0,  0<=m<=n,  n+(n-m)<=25。

输入

两个整数n,m。

输出

按照从小到大的顺序输出所有方案,每行1个。

首先,同一行内的数升序排列,每个数字后面有一个空格。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如1 3 9 12排在1 3 10 11前面)。

样例输入

5 3

样例输出

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

这道题是上一道题的加强版。

#include <bits/stdc++.h>
using namespace std;

int n, m;
int a[30];

// u是枚举起点,即本轮枚举是枚举u, u + 1, ... ,n这些数,cnt是已经枚举了多少个数
void dfs(int u, int cnt) {
	// 如果已经枚举足够了,则得到了一个组合,输出之
    if (cnt == m) {
        for (int i = 0; i < m; i++) cout << a[i] << ' ';
        cout << endl;
        return;
    }

	// 否则开始从u进行枚举。这里有个优化是,还能供枚举的数一共有n - i + 1个,
	// 还需要枚举m - cnt个数,所以有n - i + 1 >= m - cnt,可以定下i的上界
    for (int i = u; i <= n + 1 + cnt - m; i++) {
        a[cnt] = i;
        dfs(i + 1, cnt + 1);
    }
}

int main() {
    cin >> n >> m;

    dfs(1, 0);
    return 0;
}



   

        

                

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

captainfly_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值