dfs 回溯框架和应用的简单分析

首先明确回溯框架的使用范围

  • 求解方案(分为两种)

    1.求解方案的种数(楼梯,硬币找零)

    2.求解具体的方案 (硬币找零的方案,八皇后,全排列,子集,组合,数独等)

  • 求最优解(一般可以用dp优化或者有记录型参数sum>res return来剪枝)

    如果非要回溯的话,要用备忘录来优化!

框架介绍

1.方案数类

//一般都是这样再添加一些东西
void dfs(int n){
	//递归结束条件
	if(...) {
		res++;
		return;
	}
	for(int i=0;i<n;i++){
		//跳过不符合要求的
		if(...) continue;
		//做选择
		dfs(...);
	}

举个简单例子吧!

上楼梯的问题其实就是回溯问题(可以自己画出递归树,方便理解)

/*3089:爬楼梯

    查看
    提交
    统计
    提问

总时间限制:
    1000ms
内存限制:
    65536kB

描述

    树老师爬楼梯,他可以每次走1级或者2级,输入楼梯的级数,求不同的走法数
    例如:楼梯一共有3级,他可以每次都走一级,或者第一次走一级,第二次走两级
    也可以第一次走两级,第二次走一级,一共3种方法。
输入
    输入包含若干行,每行包含一个正整数N,代表楼梯级数,1 <= N <= 30
输出
    不同的走法数,每一行输入对应一行输出
样例输入

    5
    8
    10

样例输出

    8
    34
    89

*/
#include <iostream>
using namespace std;
void back(int m,int &res){
	//回溯条件,即到了楼底了
	if(m==0){
		// 方案++
		res++;
		return;
	}
	for(int i=1;i<=2;i++){
		// 不符合条件
		if(m-i<0) continue;
		// 做选择
		back(m-i,res);
	}
}

int main(){
	int m;
	while(cin >> m){
		int res = 0;
		back(m,res);
		cout<<res<<endl;
	}
	return 0;
}

值得注意的是这里dfs还需要注意,一般我们记录所有方案的话,就需要选择和撤销选择,来维护节点的属性!

2.具体方案类

举个简单例子就是全排列。

思考一下,选择之后不撤销的话就会对其他子问题的路径记录有影响!

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;
vector<vector<int> > res;
void dfs(vector<int> nums,vector<int>& track);
int main(){
	vector<int> nums;
	for(int i=1;i<=3;i++){
		nums.push_back(i);
	}
	vector<int> track;
	dfs(nums,track);
	vector<vector<int> >::iterator iter;
	for(iter=res.begin();iter!=res.end();iter++){
		vector<int> in = *iter;
		vector<int>::iterator iter2;
		for(iter2=in.begin();iter2!=in.end();iter2++){
			cout<<*iter2;
		}
		cout<<endl;
	}
}
// nums为选择,track为路径 
void dfs(vector<int> nums,vector<int>&track){
	if(track.size()==nums.size()){
		//将当前的路径加入到res 
		res.push_back(track);
		return;
	}
	
	for(int i=0;i<nums.size();i++){
		//用路径进行剪枝
		if(count(track.begin(),track.end(),nums[i])) continue;
		//选择
		track.push_back(nums[i]);
		dfs(nums,track);
		//撤销
		track.pop_back();
	}
}

再来一个李白喝酒问题

题目描述:

李白街上走,提壶去买酒,遇店加一倍,见花喝一斗”,途中,遇见5次店,见了10此花,壶中原有2斗酒,最后刚好喝完酒,要求最后遇见的是花,求可能的情况有多少种?

题解:

#include <iostream>
#include <vector>
#include <list>
using namespace std;
void dfs(int jiu,int hua,int dian);
int res = 0;
int main(){
	int jiu = 2;
	int hua = 10;
	int dian = 5;
	dfs(jiu,hua,dian); 
	cout<<res;
} 

void dfs(int jiu,int hua,int dian){
	//结束条件
	if(jiu==1&&hua==1&&dian==0){
		res++;
		return;
	}
	if(hua>0){
		dfs(jiu-1,hua-1,dian);
	}
	if(dian>0){
		dfs(jiu*2,hua,dian-1);
	}
}

这是我目前总结的经验,后续遇见其他的还会补充的!不懂的可以问我这个小菜鸡!


21年2月5日添加

还有一种大家特别常见的就是迷宫问题了
例如:

蒜厂有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。你站在其中一块黑色的瓷砖上,只能向相邻的黑色瓷砖移动。

请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式
第一行是两个整数 
W
 和 
H
,分别表示 
x
 方向和 
y
 方向瓷砖的数量。
W
 和 
H
 都不超过 
20
。
在接下来的 
H
 行中,每行包括 
W
 个字符。每个字符表示一块瓷砖的颜色,规则如下

1'.':黑色的瓷砖;
2'#':白色的瓷砖;
3'@':黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

输出格式
输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

Sample Input
6 9 
....#.
.....#
......
......
......
......
......
#@...#
.#..#.
Sample Output
45

思路: 仍然使用回溯法,只是解决不同的问题我们得稍微变通一下下!

题解:

#include <iostream>
using namespace std;
// 初始化为1,脚下的也算
int res = 1;
int row,col;
char maps[25][25];
int visited[25][25];
void dfs(int x,int y){
	// 不符合条件,回溯
	if(maps[x][y]=='#') return;
	// 越界回溯
	if(visited[x][y]==1||x<0||y<0||x>=row||y>=col) return;
	// 符合条件 res++ 继续递归
	if(maps[x][y]=='.'){
		res++;
		visited[x][y]=1;
	}
	// 尝试每种情况
	dfs(x-1,y);
	dfs(x,y+1);
	dfs(x+1,y);
	dfs(x,y-1);
}

int main(){
	cin >> col >> row;
	int startx,starty;
	for(int i=0;i<row;i++){
		for(int j=0;j<col;j++){
			cin >> maps[i][j];
			// 记录初始位置
			if(maps[i][j]=='@'){
				startx = i;
				starty = j;
			}
		}
	}
	dfs(startx,starty);
	cout<<res<<endl;
	return 0;
} 

20210206添加
再来补充一题,这题在基础dfs上需要改进一下

题目描述:

Description

下面的矩阵可以想象成鸟瞰一座山,矩阵内的数据可以想象成山的高度。

可以从任意一点开始下山。每一步的都可以朝“上下左右”4个方向行走,前提是下一步所在的点比当前所在点的数值小。

例如处在18这个点上,可以向上、向左移动,而不能向右、向下移动。

 1  2  3  4 5

16 17 18 19 6

15 24 25 20 7

14 23 22 21 8

13 12 11 10 9

问题是,对于这种的矩阵,请计算出最长的下山路径。
对于上面所给出的矩阵,最长路径为25-24-23-22-21-20-19-18-17-16-15-14-13-12-11-10-9-8-7-6-5-4-3-2-1,应输出结果25。

Input

输入包括多组测试用例。

对于每个用例,第一行包含两个正整数R和C分别代表矩阵的行数和列数。(1 <= R,C <= 100)

从第二行开始是一个R行C列矩阵,每点的数值在[0,10000]内。
Output
输出最长的下山路径的长度。
Sample Input

5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

Sample Output
25
Hint
深度优先搜索

思路:

我们要找出最长的路,我们尝试从每个点出发,找到最大的。接着尝试每个方向(当然这个方向也得符和条件,dfs返回值就是路径长度),使用备忘录来剪枝!

最最重要的就是!也是多组输入最易错又很难察觉到的。我们要重置记录型数据!

题解

#include <iostream>
using namespace std;
int row,col;
int maps[105][105];
int memo[105][105];

int MaxNum(int i,int j){
	return i>j ? i:j;
}

int dfs(int i,int j){
	// 边界 
	if(i<0||j<0||i>=row||j>=col) return 0;
	// 如果存在备忘录则直接返回
	if(memo[i][j]) return memo[i][j];
	int maxValue = 0;
	// 尝试符合条件的每个方向
	if(maps[i][j]>maps[i+1][j])
		maxValue = MaxNum(dfs(i+1,j),maxValue);
	if(maps[i][j]>maps[i-1][j])
		maxValue = MaxNum(dfs(i-1,j),maxValue);
	if(maps[i][j]>maps[i][j-1])
		maxValue = MaxNum(dfs(i,j-1),maxValue);
	if(maps[i][j]>maps[i][j+1])
		maxValue = MaxNum(dfs(i,j+1),maxValue);
	// 记录
	memo[i][j] = maxValue + 1;
	return memo[i][j];
}

// 多组测试时记住清空记录型数据 
int main(){
	while(cin>>row>>col){
		for(int i=0;i<row;i++){
			for(int j=0;j<col;j++){
				cin>>maps[i][j];
				// 重置备忘录
				memo[i][j] = 0;
			}
		}
		int res = 0;
		for(int i=0;i<row;i++){
			for(int j=0;j<col;j++){
				res = MaxNum(res,dfs(i,j));
			}
		}
		cout<<res<<endl;
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值