【蓝桥杯】算法很美 ----7 深搜+递归+回溯剪枝

https://blog.csdn.net/xcsxchen/article/details/104856178

1.1逐步生成结果类问题之数值解

cc9.1上楼梯
cc9.2机器人走方格
cc9.8硬币表示

cc9.1

在这里插入图片描述

自上而下取看,更直观,直接写出递推公式
分析:
一层:1种(1)
两层:2种(1+1,2)
三层:4种(1+1+1,1+2,2+1,3)
四层:先走一层,还剩三层:4
			先走两层,还剩两层:2
			先走三层,还剩一层:1
			f(4) = f(3)+f(2)+f(1)
	得到递归式:f(n) = f(n-1) + f(n-2) + f(n-3)

cc9.2

在这里插入图片描述
在这里插入图片描述
递推:
在这里插入图片描述

cc9.8

在这里插入图片描述
以1 5 10 25为例
在这里插入图片描述

f(100) = { 0 个 25, 用1,5,10组合100
				1个25,用1,5,10组合100
				2个25,用1,5,10组合75
				3个25,用1,5,10组合25
				4个25,用1,5,10组合0}
			每个老板(每次递归迭代)决定由一个面额的数量,从大面额到小

找到递归规律后,找参数(每次变化的量)和出口
参数:还剩多少面额需要组,可以使用什么面额
#include<iostream>
#include<cstring>
#include<algorithm> 
using namespace std;

int cnt;  

/*
*	n : residual value
*	coins : coins can be use  
*	cur : value available for each iteration
*/
int countWaysCore(int n, int coins[], int cur) {
    
    
	if(cur == 0){
    
    
		return 1;
	}
	int res = 0;
	/*
	*	i 当前面额使用数量 
	* 	从2分到多分支 
	*/
	for(int i = 0; i * coins[cur] <= n; i++) {
    
    
		int leave = n - i * coins[cur];
		res += countWaysCore(leave , coins, cur - 1);
	}
	return res;
} 

int main(){
    
    
	int n;
	cin>>n;
	int coins[] = {
    
    1, 5, 10, 25};
	cout<<countWaysCore(n, coins, 3);
}

1.2逐步生成结果类问题之非数值解

此时需要使用容器

  • 生产一点装一点,所谓迭代就是慢慢改变

cc9.6合法括号
cc9.4非空子集
cc9.8字符串(集合)全排列

cc9.6在这里插入图片描述

在这里插入图片描述
s(n) = { 对s(n-1)中的每一个元素添加左侧、右侧、包含括号 }

#include<iostream>
#include<cstring>
#include<algorithm> 
#include<set>
using namespace std;

set<string> parenthesis(int n) {
    
    
	set<string> newSet;
	if(n == 1) {
    
    
		newSet.insert("()");
		return newSet;
	}
	set<string> oldSet = parenthesis(n-1);
	set<string>::iterator it;
	for(it = oldSet.begin(); it != oldSet.end(); it++) {
    
    
		newSet.insert("()" + (*it));
		newSet.insert("(" + (*it) + ")");
		newSet.insert((*it) + "()");
	}
	return newSet; 
}

int main(){
    
    
	set<string> Set = parenthesis(3);
	set<string>::iterator it;
	for(it = Set.begin(); it != Set.end(); it++) {
    
    
		cout<<(*it)<<endl;
	} 
}

cc9.4

编写一个方法,返回某集合的所有子集。

子集的生成,就是选和不选的问题
在这里插入图片描述难点问题:那个空集怎么在集合中表达,因为他要占据位置—>直接定义了一个空的小集合

#include<iostream>
#include<cstring>
#include<set>
using namespace std;

set<set<char> > getSubset(char arr[], int cur) {
    
    
	set<set<char> > newSet;
	if(cur == 0) {
    
    
		// 定义两个小集合 空集和含一个元素的加入其中 
		set<char> empty;
		set<char> first;
		first.insert(arr[cur]);
		newSet.insert(empty);
		newSet.insert(first);
		return newSet;
	}
	
	set<set<char> > oldSet = getSubset(arr, cur - 1);
	
	// back
	set<set<char> >::iterator it;
	for(it = oldSet.begin(); it != oldSet.end(); it++) {
    
    
		set<char> temp = (*it);
		newSet.insert(temp);
		temp.insert(arr[cur]);
		newSet.insert(temp);
	}
	return newSet;
}

int main(){
    
    
	char arr[] = {
    
    'A', 'B', 'C'};
	set<set<char> > subSet = getSubset(arr, 2);
	set<set<char> >::iterator it;
	set<char>::iterator itchild;
	for(it = subSet.begin(); it != subSet.end(); it++) {
    
    
		set<char> temp = (*it);
		cout<<"{ ";
		for(itchild = temp.begin(); itchild != temp.end(); itchild++) {
    
    
			cout<<(*itchild)<<" ";
		}
		cout<<"}"<<endl;
	}
	return 0;
}

cc9.8

全排列【回溯】
递归树
递归树

回溯基本模板:


```javascript
function backtrace(已选解集合,每个阶段可选解){
    
    
	if(已选解集合满足条件) {
    
    
		结果集.add(已选解集合);
		return;
	}
	
	// 遍历每个阶段的可选解集合  (横向)
		for(可选解 in 每个阶段可选解) {
    
    
			
			//选择此阶段其中一个解,将其加入到已选解集合
			已选解集合.add(可选解);
			
			//进入下一阶段  (纵向)
			backtrace(已选解集合,下个阶段可选的解集合)
			
			//[回溯] 换个解再遍历      到达这个阶段时,一个深度纵向分支已经走完
			已选解集合.remove(可选解)
		}
}
#include<iostream>
#include<cstring>
#include<set>
using namespace std;

int n, a[1000], vis[1000];
char arr[] = {
    
    'A', 'B', 'C'};

void dfs(int step){
    
    
	if(step > n) {
    
    
		for(int j = 1; j <= n; j++) {
    
    
			cout<<arr[a[j]-1]<<' ';
		}
		cout<<endl; 
		return ;
	}
	int i;
	for(i = 1; i <= n; i++) {
    
    
		if(vis[i] == 1) {
    
    
			continue;
		}
		a[step] = i;
		vis[i] = 1;
		
		dfs(step + 1);
		vis[i] = 0;
	
	}
}


int main(){
    
    
//	cin>>n;
n = 3;
	memset(vis, 0, sizeof(vis));
	dfs(1);
	return 0;
}

1.3封闭式

汉诺塔
上楼梯
斐波那契数列

-------------->直接写公式

DFS 深度优先搜索

1- 数独游戏

在这里插入图片描述

#include<iostream>
#include<cstring>
#include<set>
using namespace std;


int a[10][10] = {
    
    
	{
    
    0,0,0,0,0,0,0,0,0,0},
	{
    
    0,5,3,0,0,7,0,0,0,0},
	{
    
    0,6,0,0,1,9,5,0,0,0},
	{
    
    0,0,9,8,0,0,0,0,6,0},
	{
    
    0,8,0,0,0,6,0,0,0,3},
	{
    
    0,4,0,0,8,0,3,0,0,1},
	{
    
    0,7,0,0,0,2,0,0,0,6},
	{
    
    0,0,6,0,0,0,0,2,8,0},
	{
    
    0,0,0,0,4,1,9,0,0,5},
	{
    
    0,0,0,0,0,8,0,0,7,9} };

void printA(){
    
    
	for(int i = 1; i <= 9; i++) {
    
    
		for(int j = 1; j <= 9; j++) {
    
    
			cout<<a[i][j]<<" ";
		}
		cout<<endl;
	}
}

bool checked(int x, int y, int n) {
    
    
	for(int i = 1; i <= 9; i++) {
    
    
		if(a[x][i] == n) {
    
    
			return false;
		}
		if(a[i][y] == n) {
    
    
			return false;
		}
	}
	int x1 = ((x-1)/3)*3+1; 
	int y1 = ((y-1)/3)*3+1;
	for(int i = x1; i <= x1 + 2; i++) {
    
    
		for(int j = y1; j <= y1 + 2; j++) {
    
    
			if(a[i][j] == n)
				return false;
		}
	}
	return true;
}

void dfs(int x, int y) {
    
    
	
	if(y == 10) {
    
     		
		if(x == 9) {
    
    
			printA();
			exit(0);
		} else {
    
    
			y = 1;
			x++;
		} 
	}
	
	if(a[x][y] == 0) {
    
    
		for(int i = 1; i <= 9; i++)	{
    
    
			if(checked(x, y, i)){
    
    
				a[x][y] = i;
				dfs(x, y+1);
				a[x][y] = 0;
			}
		}
	} else {
    
    
		dfs(x, y+1);
	}
}

int main(){
    
    
	dfs(1, 1);
	return 0;
}

2- 部分和

在这里插入图片描述

int n;
int a[1000];
int vis[1000];
int k;

void dfs(int leave, int cur) {
    
    
	if(leave == 0) {
    
    
		cout<<"yes"<<endl;
		for(int i = 0; i < n; i++) {
    
    
			if(vis[i]) {
    
    
				cout<<a[i]<<" ";
			}
		}
		cout<<endl;
		exit(0);
	}
	if(leave < 0 || cur >= n) {
    
    
		return ;
	}
	
	//	横向(平行)的遍历此时是 要 和 不要 改数 
	
	vis[cur] = 1;
	dfs(leave - a[cur], cur + 1);
	vis[cur] = 0;
	
	dfs(leave, cur + 1);
}


int main()
{
    
    
	cin>>n;
	for(int i = 0; i < n; i++) {
    
    
		cin>>a[i];
	}
	cin>>k;
	dfs(k, 0);
	return 0;
}

3- 水洼数

有一个大小为 N*M 的园子,雨后积起了水。八连通的积水被认为是连接在一起的。
请求出园子里总共有多少水洼?(八连通指的是下图中相对 W 的*的部分)

***
*W*
***

限制条件:N, M ≤ 100
样例输入:N=10, M=12
园子如下图('W’表示积水, '.'表示没有积水)
在这里插入图片描述
输出:3

    问题是要我们求解出连着的一片的水洼的数量,对于这类经典的问题,使用其它迭代等的方法
是难以求解的,因为我们不知道连着的积水的区域有多少,对于这类问题的求解,我们是采用
常用的无死角搜索的深度优先搜索dfs算法来解决,因为dfs能够帮助我们搜索出所有的可能,尝
试去走每一条路线,直到所有的路线都被走完了,那么dfs就终止了。

  其中涉及到搜索以自己为中心的八个方向的搜索,所以存在着八个平行状态的搜索,这里
使用到了一个技巧就是使用两层的for循环来进行处理。
  
  这里还有一个技巧就是当发现这个位置有积水的时候就把这个位置变为干燥,这样在往下
搜索的过程中就能避免往上搜索而造成递归无法出去的问题。这样当一个dfs搜索完之后那
么它周围的积水都被清除掉了,那么继续寻找下一个有积水的地方然后进行dfs,当所有积水区域
都被干燥之后那么水洼的数量就计算出来了。
	不是所有dfs都需要回溯,当对后面有用时需要回溯(需要重新开始选择时)
	此题虽然改变了w,但本质就是为了消去水洼,如果回溯了就无穷无尽了

本题是个经典题目:
1、八个方向的走法
2、八个平行状态(八个DFS)
3、边走边改且不用回溯
4、外层for循环调用dfs,寻找起点

#include <iostream>
using namespace std;

int n, m;
char a[100][100];
int dir[8][2] = {
    
    {
    
    1,0}, {
    
    -1,0}, {
    
    0,1}, {
    
    0,-1}, {
    
    1,1}, {
    
    1,-1}, {
    
    -1,1}, {
    
    -1,-1}};

void dfs(int x, int y) {
    
    
	
	// 变成.且不会在回溯阶段回复,防止搜索死循环, for能找到八个方向 
	//	 
	a[x][y] = '.';
	for(int i = 0; i < 8; i++) {
    
    
		// 这个变为.的W八个方向上都可以在for循环中探到 
		int dx = x + dir[i][0];
		int dy = y + dir[i][1];
		if(dx >= 0 && dx < n && dy >= 0 && dy < m) {
    
    
			if(a[dx][dy] == 'W') {
    
    
				dfs(dx, dy);
			}
		}
	}
}

int main()
{
    
    
	cin>>n>>m;
	for(int i = 0; i < n; i++) {
    
    
		for(int j = 0; j < m; j++) {
    
    
			cin>>a[i][j];
		}
	}
	
	int cnt = 0;
	/*
		这个循环是找dfs的起点,每次相当去消去一个水洼 
	*/
	for(int i = 0; i < n; i++) {
    
    
		for(int j = 0; j < m; j++) {
    
    
			if(a[i][j] == 'W'){
    
    
				dfs(i, j);  // 消去一个水洼  
			  	cnt++;
			}
		}
	}
	cout<<cnt<<endl;
	return 0;
}

/*

10 12

W........WW.
.WWW.....WWW
....WW...WW.
.........WW.
.........W..
..W......W..
.W.W.....WW.
W.W.W.....W.
.W.W......W.
..W.......W.

*/ 

小结

在这里插入图片描述

显式剪枝

1- n皇后

在这里插入图片描述
一个技巧:副对角线上的元素和相同,主对角线元素差相同
在这里插入图片描述

#include<iostream>
#include<cstring>
using namespace std;

int cnt = 0;
int n;  // n*n n
int a[1000];  // a表示每一行,其中的元素存放该行中皇后所在的列数  

bool check(int x, int y) {
    
    
//	是按照行进行逐步推进的,所以不用检查行
// !注意这里如果写i <= n 就错了,只需要检查前x行 不然下面 x+y == a[i]+i 如 1 + 1 == 0 + 2
	for(int i = 1; i <= x; i++) {
    
    
		if(a[i] == y) {
    
    
			return false;
		}
		if(x+y == a[i]+i || x-y == i-a[i]){
    
    
			return false;
		}
	}
	return true;
}

void dfs(int step) {
    
    
	if(step > n) {
    
    
		cnt++;
		return ;
	}
	
	for(int i = 1; i <= n; i++) {
    
    
		if(check(step, i)) {
    
    
			a[step] = i;
			dfs(step + 1);
			a[step] = 0;
		}
	}
}

int main(){
    
    
	memset(a, 0, sizeof(a));
	cin>>n;
	dfs(1);
	cout<<cnt<<endl; 
	return 0;
}

2- 素数环

在这里插入图片描述

#include<iostream>
#include<cstring>
using namespace std;

int n;
int a[1000];
int vis[1000];

bool isPrime(int m) {
    
    
	for(int i = 2; i < m; i++) {
    
    
		if(m%i == 0) {
    
    
			return false;
		}
	}
	return true;
}

bool check(int step, int i) {
    
    
	if(step == n && isPrime(i+1) && isPrime(i+a[step-1])) {
    
    
		return true;
	} 
	//	如果下面用else 一个很隐蔽的bug:在最后一个比对时,
	//	如果isprime(i+1) 不满足,会自动进入下一个,变为比较最后一个数和前一个 
	if(step < n){
    
    
		if(isPrime(i+a[step-1])){
    
    
			return true;
		}
	}
	return false;
}

void dfs(int step) {
    
    
	if(step > n) {
    
    
		for(int i = 1; i <= n; i++) {
    
    
			cout<<a[i]<<" ";
		}
		cout<<endl;
		return ;
	}
	
	for(int i = 2; i <= n; i++) {
    
    
		if(vis[i] == 0 && check(step, i)) {
    
    
			a[step] = i;
			vis[i] = 1;
			dfs(step+1);
			vis[i] = 0;
			a[step] = 0;
		}
	}
}

int main(){
    
    
	memset(vis, 0, sizeof(vis));
	a[1] = 1;
	vis[1] = 1;
	n=8;
	
	dfs(2);
	return 0; 
}

3- 困难的串

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44227389/article/details/107880761