【算法分析与设计】第八章-回溯法

news/2024/7/20 22:14:13 标签: 算法, 深度优先

一、知识铺垫

  • 约束条件
    分为显式约束隐式约束
    显式:规定了问题的解的分量的取值范围。如求n的全排列每个位置只能取1~n
    隐式:用于判定候选解是否为可行解。如全排列的每个数字不允许重复。
  • 问题状态和状态空间树
            状态空间树是描述问题解空间的树形结构,每个结点称为一个问题状态。树的每条分支代表一次决策,从根结点到叶结点的路径就代表了一个候选解,称该叶结点所代表的状态为解状态。如果候选解可行解则称之为答案状态
  • 剪枝函数
        剪枝函数分为约束函数限界函数
        约束函数:避免无所谓的搜索那些已知不含答案状态的子树。
        限界函数:用于最优化问题,剪去那些不可能含有最优答案结点的子树。
        二者的区别在于:约束函数是对约束条件的实现,剪去不带答案结点的子树。而限界函数常用于求解最优化问题,它剪去的子树可能带答案结点,但不会是最优答案结点。

二、什么是回溯法

回溯法是一种更为一般的解题方法。回溯法是通过搜索状态空间树来求问题的可行解或最优解的方法。本质就是dfs + 剪枝

三、回溯法的使用场景

如果问题的解可表示成一个n元组 ( x 1 , x 2 , . . . , x n ) (x_1,x_2,...,x_n) (x1,x2,...,xn),我们就可以通过枚举所有的可能排列来搜索答案。比如求全排列、迷宫问题、n皇后、子集和数、图的着色问题等。

四、回溯法的解题步骤

void BackTrack(int k){
	if k == n { 输出答案 }
	else{
			for 所有可能的x[k]取值
				if x[k] 满足约束条件
					BackTrack(k+1)
	}
}

五、典例

  • n皇后问题
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 15;
int g[N], ans[N];
int n;
LL cnt;
bool vis[N];
bool check(int k, int j){
	for(int i = 1; i < k; i ++){
		if(ans[i] == j || abs(i - k) == abs(ans[i] - j))
			return false; 
	}
	return true;
}
void nQueens(int d){
	if(d == n + 1){
		for(int i = 1; i <= n; i ++) 
			cout << ans[i] << ' ';
		cout << endl; 
		cnt ++;
		return; 
	}
	for(int i = 1; i <= n; i ++){	// 显式约束
		if(!vis[i] && check(d,i)){	// 隐式约束
			ans[d] = i;
			vis[i] = 1;
			nQueens(d + 1);
			vis[i] = 0;
		} 
	}
}
int main(){
	cin >> n;
	nQueens(1);
	cout << cnt;
	return 0;
}
  • 子集和数问题
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int w[N], x[N], tmp[N];
int idx[100*N];
int n, m;
bool f;
void dfs(int s, int k, int r){
	x[k] = 1;
	if(s + w[k] == m){
		for(int i = k + 1; i < n; i ++)
			x[i] = 0;
		for(int i = 0; i < n; i ++)
			cout << x[idx[tmp[i]]] << ' ';
		cout << endl;
		f = 1;
	}
	else if(s + w[k] + w[k + 1] <= m){	//选 k可以 
			dfs(s + w[k], k + 1, r - w[k]);
	}
	if(s + r - w[k] >= m && s + w[k+1] <= m){	//不选 k 
		x[k] = 0;
		dfs(s, k + 1, r - w[k]);
	}
}
int main(){
	cin >> n >> m;
	for(int i = 0; i < n; i ++)
		cin >> w[i], tmp[i] = w[i];	// tmp copy w for print 
	int r = 0, s = 0;
	for(int i = 0; i < n; i ++)
		r += w[i];
	sort(w, w + n);
	for(int i = 0; i < n; i ++)
		idx[w[i]] = i;
	if(r >= m && w[0] <= m)
		dfs(s,0,r);
	if(!f) cout << "no solution!" << endl;
	return 0;
}

http://www.niftyadmin.cn/n/421410.html

相关文章

C# 获取Http请求服务器响应的cookie

一、C#服务器端响应存储cookie public IActionResult Test2(){Response.Cookies.Append("user","张三丰");Response.Cookies.Append("pwd", "123");return Content("输出cookie成功&#xff1a;张三丰");} 二、C#发送Http请…

spring boot + xxl-job 分布式任务调度

一、介绍 1、任务调度 1.1、什么是任务调度 我们可以先思考一下下面业务场景的解决方案&#xff1a; 某电商系统需要在每天上午10点&#xff0c;下午3点&#xff0c;晚上8点发放一批优惠券。某财务系统需要在每天上午10点前结算前一天的账单数据&#xff0c;统计汇总。某电…

element-plus布局排版问题总结(更新ing)

文章目录 el-container空隙修改app组件 el-header容器空隙检查前端修改el-header el-container空隙 源码-更改了容器的显示&#xff0c;占满屏幕 <template><div class"common-layout"><el-container><el-header><el-row class"el-…

MySQL 索引的10 个核心要点

文章目录 &#x1f349;1. 索引底层采用什么数据结构&#xff1f;为什么不用hash&#x1f349;2. B树与B树区别&#xff1f;为何用B树&#xff1f;&#x1f349;3. 自增主键理解&#xff1f;&#x1f349;4. 为什么自增主键不连续&#x1f349;5. Innodb为什么推荐用自增ID&…

(数组) 1991. 找到数组的中间位置 ——【Leetcode每日一题】

❓1991. 找到数组的中间位置 难度&#xff1a;简单 给你一个下标从 0 开始的整数数组 nums &#xff0c;请你找到 最左边 的中间位置 middleIndex &#xff08;也就是所有可能中间位置下标最小的一个&#xff09;。 中间位置 middleIndex 是满足 nums[0] nums[1] ... num…

【工具】SecureCR-8.5下载、安装激活和使用教程(包含常用设置)

目录 一、安装包下载 二、安装教程 三、激活操作 四、使用教程 五、常用设置 一、安装包下载 SecureCRT8.5安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1yy677I99ln_3evoHc5dMXg 提取码&#xff1a;9tyj 二、安装教程 1. 解压、双击进行安装 2. 安装进…

pytorch笔记:conv2d

来自B站视频&#xff0c;API查阅&#xff0c;TORCH.NN nn.conv2d 中一般 kernel_size 是小奇数&#xff0c;padding 设置为 k − 1 2 \frac{k-1}{2} 2k−1​&#xff08;实际上padding的是 k − 1 k-1 k−1&#xff0c;因为参数的意义是左右各padding&#xff09;&#xff0c;

【漏洞复现】Apache RocketMQ 命令注入漏洞(CVE-2023-33246)

文章目录 前言声明一、漏洞描述二、漏洞危害三、影响版本四、环境搭建五、漏洞复现六、修复建议 前言 RocketMQ 是阿里巴巴在2012年开发的分布式消息中间件&#xff0c;专为万亿级超大规模的消息处理而设计&#xff0c;具有高吞吐量、低延迟、海量堆积、顺序收发等特点。同时它…