初涉回溯算法

回溯算法:全排列问题


前言

在学习C++时,我们一定会学到回溯算法。但回溯算法的细节方面,总是不那么容易掌握。这篇文章,就是为各位扫盲的。相信大家一定会得到些许帮助。


一、回溯是什么?

回溯,顾名思义,就是“一条路走到黑”,直到“撞破南墙”才回头一种类似于枚举的算法。不断试探,直到发现此方案不可行就尝试其它的方案。

二、典题:组合的输出

1.题目

排列与组合是常用的数学方法,其中组合就是从n个元素中抽出r个元素(不分顺序且r≤n),我们可以简单地将n个元素理解为自然数1,2,…,n,从中任取r个数。

现要求你用递归的方法输出所有组合。例如n=5,r=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

2.分析

可以把这颗搜索树理解成一棵r叉树。如下图(图有省略):
一张五叉树的图片
而递归程序所做的,就是将本身与上面的每层的每个节点对应起来。如下图:
在这里插入图片描述
如何用程序来表达呢?如下(会做解释):

#include <bits/stdc++.h>
using namespace std;
int r,n,a[20];
bool init(int n){//判断元素n是否在数组a[]里
	for(int i = 0;i < r;i++) if(a[i] == n) return 1;
	return 0;
}
void print(){//
	for(int i = 0;i < r;i++) cout<<a[i]<<" ";
	cout<<endl;
}
void fill(int t){//已经填了t个元素
	if(t == r){//填完了 
		print();
		return;	
	}
	int b[20];
	for(int i = 0;i < 20;i++) b[i] = a[i];//备份 
	for(int i = 1;i <= n;i++){
		if(!init(i)){//每一个元素需且只出现一次
			a[t] = i;fill(t + 1);//将i填至a[t],然后尝试已经填t+1个元素的情况
		}
	}
	for(int i = 0;i < 20;i++) a[i] = b[i];//恢复现场 
} 
int main(){
	cin>>n>>r;
	fill(0);//最开始一个元素都没填,所以是0
	return 0;
}

6.19更:由此我们可以得出一个回溯程序框架(在上面也同样适用):

void dfs(int t){
	if(满足成功的条件){
		打印方案;
		return;
	}else{
		for(遍历尝试的范围){
			temp = 以前的方案;
			if(当前的方案可以){
				将它填进去;
				dfs(t + 1);
			}
			方案 = temp;
		}
		
}

3.注意点

相信大家看到这里,都知道回溯的原理。但有一个细节,大家可能没注意。我拿吃饭举例:
小明一天吃早餐,午餐,晚餐三餐。
早餐可吃:皮蛋瘦肉粥、油条配烧饼、肉包子。
午餐可吃:杂酱面,抄手,可乐鸡饭。
晚餐可吃:牛排,牛百叶汤、牛肉面。
现在对小明一天吃的东西进行暴力搜索,那么我们应:A.把早餐吃的东西换来换去。B.先固定早餐,然后依次选。

答案为B。我们显然需要“给早餐一个机会”。切忌只变动第一项,却不见后面的动静。希望大家注意。

总结

以上就是今天要讲的内容,本文仅仅简单介绍了回溯的使用,而回溯的题目大部分都是由《组合的输出》、《全排列》这几道题变通出来的。希望大家能够将回溯算法融会贯通,往后对回溯算法得心应手!
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值