算法基础之回溯法

本文将详细介绍回溯法的基本原理和适用条件,并通过经典例题辅助读者理解回溯法的思想、掌握回溯法的使用。本文给出的例题包括:N皇后问题、子集和问题。

算法原理

在问题的解空间树中,回溯法按照深度优先的搜索策略,从根结点出发搜索解空间树,回溯法实际上一个类似穷举的搜索尝试过程。由于采用回溯法求解时存在退回到祖先结点的过程,所以需要保存搜索过的结点,可以自定义栈来保存祖先结点,也可以采用递归。

在搜索解空间时,通常采用两种策略避免无效搜索,以提高搜索效率。一是使用约束函数在拓展结点处剪除不满足约束条件的路径,二是使用限界函数剪去得不到问题解或最优解的路径,这两类函数统称为剪枝函数。

回溯法的一般非递归设计如下:

int x[n];	// 解向量
void backtrack(int n){int i=1;								// 根结点层次while(i>=1){if(ExistSubNode(t)){				// 当前结点存在子结点x[i]取一个可能的值;if(constraint(i)&&bound(i)){	// 满足约束条件和限界函数if(x为可行解)  输出x;			// 输出解else i++;					// 下一层次}}else i--;							// 回溯}
}

回溯法的一般递归设计如下:

int x[n];	// 解向量
void backtrack(int i){if(i>n) 输出结果;							// 到达叶子结点else {for(j=下界;j<=上界;j++){				// 枚举x[i]的所有可能x[i]=j;...if(constraint(i)&&bound(i)) 		// 满足约束条件和限界函数backtrack(i+1);					// 下一层次}}
}

N皇后问题

题目描述

N N N 皇后问题要求在一个 N × N N×N N×N 的棋盘上放置 N N N 个皇后,使得它们彼此不受“攻击”。观察表明 N N N 皇后问题的解存在对称性,求其中不对称的那些解的数量。

输入输出

输入:皇后个数N。

输出:不对称的那些解。

解题思路

仔细观察 N N N 皇后的解,发现一种方案可以通过“对称”得到另一种方案。以“左右对称”为例,当 N = 5 N=5 N=5,限定第一行皇后在左边一半区域时,方案数为 6 6 6,如下图所示。

在这里插入图片描述

通过“左右对称”可以获得另一方案,同时发现,后面有两种方案重复,去除重复方案后,剩下的刚好是 N = 5 N=5 N=5 时的全部方案,如下图所示。

在这里插入图片描述

N N N 为偶数时关于中间那条线对称,当 N N N 为奇数时关于中间那一列对称。利用对称性可以使得工作量减少一半,为此,在放置皇后时,增加两条限制

  • 第一行的皇后只放在左边一半区域,也即位置小于等于 ( n + 1 ) / 2 (n+1)/2 (n+1)/2
  • N N N 为奇数且第一行皇后刚好放在 ( n + 1 ) / 2 (n+1)/2 (n+1)/2 位置(即中间)时,为避免重复,第二行皇后必须放在左边一半区域。
代码实现
int *Q, N, ANS; // 棋盘 棋盘大小 解的数量
int main() {printf("请输入棋盘大小N: ");while (cin >> N) {ANS = 0, Q = new int[N + 1], Q[1] = 0;// 求解queen();// N=1时无第二行,无法施加两条限制,特殊处理printf("解的总数为: %d\n", N!=1?ANS * 2:ANS); printf("\n请输入棋盘大小N: ");}
}
/*** N皇后非递归回溯求解-基础实现* 所有下标均从1开始*/
void queen() {// 第一个皇后int k = 1;// N是否为奇数、中间位置、当前行最多能放到第几列int odd = N & 1, M = (N + 1) >> 1, L;// 开始放置皇后while (k > 0) {// 第k个皇后尝试下一个位置Q[k]++;// 第一行放置的皇后不能超过中间if (k == 1)L = M;// N为奇数且第一行放在中间时,第二行不能超过中间else if (k == 2 && odd && Q[1] == M)L = M - 1;// 其它情况可以放到中间的右边else L = N; // 寻找第k行的下一个可以放置的位置while (Q[k] <= L && !place(k))Q[k]++;// 已超过当前行的上限L,回溯,返回上一行if (Q[k] > L)--k;// 如果放置所有皇后,则打印结果,否则放置下一行else k == N ? (showRes(),ANS++) : Q[++k] = 0;}
}
/*** 判断第k个皇后当前位置是否合适 Q[k]是第k个皇后放置的位置* @param k 第k个皇后* @return 是否可以放置*/
bool place(int k) {for (int i = 1; i < k; ++i)// 同列、同斜线已存在皇后if (Q[i] == Q[k] || abs(Q[i] - Q[k]) == abs(i - k))return 0;return 1;
}
/*** 打印可行解*/
void show() {printf("(");for (int i = 1; i <= N; ++i)printf("%d,", Q[i]);printf("\b)\n");
}

时间复杂度: O ( n n ) O(n^n) O(nn)

空间复杂度: O ( n ) O(n) O(n)

子集和问题

题目描述

已知包含 n n n 个不同正整数 w i w_i wi 的集合, ( 0 ≤ i ≤ n − 1 ) (0≤i≤n-1) (0in1),求该集合的所有满足条件的子集,使得每个子集中的正整数之和等于另一个给定的正整数 W W W

输入输出

输入:一行输入 n n n W W W 的值,第二行输入 n n n 个不同的正整数 w i w_i wi

输出:如果有答案,则输出所有满足条件的子集(用固定长度 n n n 元组 x x x 表示, x i x_i xi 0 0 0 1 1 1)。如果没有答案,则输出 n o s o l u t i o n ! no\ solution! no solution!

解题思路

N = 4 N=4 N=4 时,解空间树如下图所示。其中,结点中的数字为结点的编号,规定往结点左边“走”,对应 x i x_i xi 记为 1 1 1,即表示选取第 i i i 个正整数,往结点右边“走”,对应 x i x_i xi 记为 0 0 0,即表示不选取第 i i i 个正整数。

在这里插入图片描述

集合中的正整数按输入顺序从 0 0 0 开始编号,设数组 x x x x [ i ] = 1 x[i]=1 x[i]=1 表示选择第 i i i 个正整数, x [ i ] = 0 x[i]=0 x[i]=0 表示不选择第 i i i 个正整数。 s w sw sw 记录尝试选取第 i i i 个正整数时,下标为 0 0 0 i − 1 i-1 i1 的正整数中已选取的正整数的和, u w uw uw 记录尝试选取第 i i i 个正整数时,下标为 i + 1 i+1 i+1 n − 1 n-1 n1 的正整数的和。当面对第 i i i 个正整数时,需要依次尝试选取第 i i i 个和不选取第 i i i 个。

尝试选取第 i i i 个时,先判断第 i i i 个是否可选。若 s w + w [ i ] ≤ W sw+w[i]≤W sw+w[i]W,即,在假定选取第 i i i 个的情况下,已选正整数的和没有超过给定正整数 W W W,则第 i i i 个可选,否则,不可选,剪掉左枝。

例如,当前处于上图中的结点 4 4 4,考虑选取第 3 3 3 个正整数,到达结点 8 8 8,如果已选取的正整数的和超过给定正整数 W W W,则剪去以结点 8 8 8 为根结点的二叉树,因为,结点 8 8 8 往下,无论做何选择,已选正整数的和都不可能为 W W W(在结点 8 8 8 时就已经超过 W W W)。

尝试不选取第 i i i 个时,先判断此后是否存在解。若 s w + u w ≥ W sw+uw≥W sw+uwW,即,在假定不选取第 i i i 个的情况下,此后已选正整数的和仍有可能达到 W W W,则此后存在解,否则,此后不存在解,剪掉右枝。

例如,当前处于上图中的第 4 4 4 个节点,考虑不选取第 3 3 3 个正整数,到达结点 9 9 9,如果从结点 9 9 9 开始,一直往左“走”也无法达到给定正整数 W W W,则剪去以结点 9 9 9 为根节点的子树,因为,此后最大和都已不可能达到 W W W

代码实现
int N, W;      // 正整数个数 指定和
int *w, *x;    // 正整数 解
int main() {int rw = 0;// 输入正整数个数Ncin >> N >> W;                   w = new int[N], x = new int[N];// 输入N个正整数for (int i = 0; i < N;rw += w[i++])cin >> w[i]; // 求解solve(0, 0, rw);
}
/*** 尝试选取第i个正整数(i从0开始)* 面对第i个正整数,需要依次尝试选取第i个和不选取第i个*  1.选第i个: 选第i个前判断第i个是否可选,sw+W[i]<=W即为可选*  2.不选第i个: 不选第i个前,判断此后是否存在解,sw+uw>=W即为存在解* @param i 第i个正整数* @param sw [0,i-1]已选正整数的和* @param uw [i+1,n-1]的和*/
void solve(int i, int sw, int uw) {// 已达叶子结点if (i >= N) {if (sw == W)ANS++,show(); //找到一个解return;}// 选取第i个,未超过W(超过则剪掉左枝)if (sw + w[i] <= W) { x[i] = 1;								// 选取第i个solve(i + 1, sw + w[i], uw - w[i]);		// 尝试选取第i+1个}// 不选第i个,此后存在解(不存在则剪掉右枝)if (sw + uw >= W) {   x[i] = 0;							// 不选取第i个solve(i + 1, sw, uw - w[i]);		// 尝试选取第i+1个}
}
/*** 输出解*/
void show() {printf("(%d", x[0]);for (int i = 1; i < N;) printf(",%d", x[i++]);printf(")\n");
}

时间复杂度: O ( 2 n ) O(2^n) O(2n)

空间复杂度: O ( n ) O(n) O(n)

经验总结

回溯法与深度优先遍历非常相似,剪枝是回溯法的一个明显特征,但并不是任何回溯法都包含剪枝,因而很难区分回溯法与深度优先遍历,广义来讲,带回退的算法都是回溯算法。

如果仅仅采用深度优先,那么需要遍历整个解空间,与穷举并无太大区别,此时的回溯法可看做按深度优先+穷举,穷举的时间复杂度无疑是较高的。为了提高搜索的效率,在搜索解空间时,需要在拓展结点处剪除不满足约束条件的路径和得不到问题解或最优解的路径。不满足约束条件是指,当前路径已经不满足题目对解的要求,说明此后含有该路径的路径也必然不符合要求(算法开始前可能需要对元素进行排序才能满足这一点),因此,没必要在此基础上继续尝试。得不到问题解或最优解是指,虽然当前路径目前来说是合法的,但此后即使“使尽全力“也无法达到要求,此时也没必要在此基础上继续尝试。不满足约束条件和得不到问题解或最优解,前者侧重考虑当前,后者侧重考虑将来。

回溯法常采用递归来实现,在递归调用返回时会自动回退和恢复,但也可采用非递归实现,如本实验的N皇后问题的实现,此时需要编码实现回退和恢复。采用递归实现,代码简洁,采用非递归实现,需要处理的问题较多,逻辑稍微更加复杂,代码量相对而言可能更多。

END

文章文档:私信回复关键字可获取本文文档。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/47956.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP溯源工具--IPTraceabilityTool

工具地址&#xff1a;xingyunsec/IPTraceabilityTool: 蓝队值守利器-IP溯源工具 (github.com) 工具介绍&#xff1a; 在攻防演练期间&#xff0c;对于值守人员&#xff0c;某些客户要求对攻击IP都进行分析溯源&#xff0c;发现攻击IP的时候&#xff0c;需要针对攻击IP进行分析…

Hive理论讲解

Hive介绍 1、Hive本质 Hive本质是【数仓设计方案】&#xff0c;hive本身并不存储数据【数据包含&#xff1a;元数据 (表)数据】。 2、hql和sql对比 sql 结构化查询语言【structured query language】hql hive/hadoop类sql查询语言【hive/hadoop query language like sql…

Python学习笔记40:游戏篇之外星人入侵(一)

前言 入门知识已经学完&#xff0c;常用标准库也了解了,pygame入门知识也学了&#xff0c;那么开始尝试小游戏的开发。 当然这个小游戏属于比较简单的小游戏&#xff0c;复杂的游戏需要长时间的编写累计开发经验&#xff0c;同时也需要一定的时间才能编写出来。现在的话还是嫩…

2024年7月16日(使用光盘创建本地仓库,引入网络镜像仓库,创建自建仓库)

了解yum源安装包的特点 了解常用的网络yum源 掌握本地和网络yum源的配置 能够使用yum工具安装软件包 rpm -ivh xxx 手动添加依赖 yum不止执行安装&#xff0c;会自动处理依赖安装 1、yum优点 rpm安装 &#xff08;下载软件 单独安装 需要解决依赖关系&#xff09; 源码安装 &am…

海外媒体发稿-瑞典SEO破茧成蝶:从0到10的实战精要-大舍传媒

海外媒体发稿-瑞典SEO破茧成蝶:从0到10的实战精要 一、迷茫与意义的探寻 有一天我找了王老师聊天&#xff0c;谈到生活迷茫和人生的意义。老师说了一段话&#xff1a;当全情投入于一件事情时&#xff0c;是没有时间去迷茫或思索人生意义的。我感触很深&#xff0c;当总感到迷…

linux服务器配置conda环境安装教程

1 软件准备 1.1 软件下载 https://repo.anaconda.com/archive/index.html 根据官网选择自己需要的版本。 这里下载的是 Anaconda3-2023.03-1-Linux-x86_64.sh 或者直接在linux中输入 wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh 1.…

操作系统内核源码杂谈篇:临界区

临界资源&#xff0c;是指同一时刻只能由一个线程&#xff08;linux下为进程&#xff09;访问的资源&#xff0c;而临界区就是为了确保临界资源访问是单一数据流。 临界区的代码执行&#xff0c;也就是进行原子操作&#xff0c;不会被打断。 先分析RTOS的运行架构&#xff0c…

Paypal个人支付申请及沙箱测试配置

目录 一. 申请paypal账号二. Sanbox 测试配置申请买家Account申请卖家AccountSandbox的Client ID及密钥申请Live的Client ID及密钥申请IPN回调设置 一. 申请paypal账号 浏览器输入https://www.paypal.com, 单击注册按钮 2. 我这里申请个人账户&#xff0c;如果你需要企业账户&…

AI绘画入门实践|Midjourney 的模型版本

模型分类 Midjourney 的模型主要分为2大类&#xff1a; 默认模型&#xff1a;目前包括&#xff1a;V1, V2, V3, V4, V5.0, V5.1, V5.2, V6 NIJI模型&#xff1a;目前包括&#xff1a;NIJI V4, NIJI V5, NIJI V6 模型切换 你在服务器输入框中输入 /settings&#xff1a; 回车后…

【深度学习驱动智能超材料设计与应用】

在深度学习与超材料融合的背景下&#xff0c;不仅提高了设计的效率和质量&#xff0c;还为实现定制化和精准化的治疗提供了可能&#xff0c;展现了在材料科学领域的巨大潜力。深度学习可以帮助实现超材料结构参数的优化、电磁响应的预测、拓扑结构的自动设计、相位的预测及结构…

抖音矩阵系统源码开发部署流程分享

#短视频矩阵源码 #短视频矩阵 #源码交付 抖音矩阵系统源码开发部署流程如下&#xff1a; 环境配置&#xff1a;首先&#xff0c;需要安装并配置开发环境&#xff0c;包括安装Java JDK、MySQL数据库、Eclipse开发工具等。 数据库设计&#xff1a;根据抖音矩阵系统的需求&…

【数字IC/FPGA】书籍推荐(2)----《那些年,我们拿下了FPGA》

在下这几年关于数字电路、Verilog、FPGA和IC方面的书前前后后都读了不少&#xff0c;发现了不少好书&#xff0c;也在一些废话书上浪费过时间。接下来会写一系列文章&#xff0c;把一部分读过的书做个测评&#xff0c;根据个人标准按十分制满分来打分分享给大家。 概述 &#x…

【C语言】全面解析冒泡排序

文章目录 什么是冒泡排序&#xff1f;冒泡排序的基本实现代码解释冒泡排序的优化冒泡排序的性能分析冒泡排序的实际应用结论 在C语言编程中&#xff0c;排序算法是一个非常基础且重要的概念。冒泡排序作为最简单、最易理解的排序算法之一&#xff0c;广泛应用于各种编程教学和实…

在VS Code上搭建Vue项目教程(Vue-cli 脚手架)

1.前期环境准备 搭建Vue项目使用的是Vue-cli 脚手架。前期环境需要准备Node.js环境&#xff0c;就像Java开发要依赖JDK环境一样。 1.1 Node.js环境配置 1&#xff09;具体安装步骤操作即可&#xff1a; npm 安装教程_如何安装npm-CSDN博客文章浏览阅读836次。本文主要在Win…

使用NIFI连接瀚高数据库_并从RestFul的HTTP接口中获取数据局_同步到瀚高数据库中---大数据之Nifi工作笔记0067

首先来看一下如何,使用NIFI 去连接瀚高数据库. 其实,只要配置好了链接的,连接字符串,和驱动,任何支持JDBC的数据库都可以连接的. 首先我们用一个ListDatabaseTables处理器,来连接瀚高DB 主要是看这里,连接地址,以及驱动,还有驱动的位置 这个是数据连接的配置 jdbc:highgo://…

什么是上网行为审计系统?有哪些功能?

上网行为审计系统是一种网络安全与管理工具&#xff0c;用于监控、记录和分析组织内部网络用户的上网行为。 这种系统旨在帮助企业或组织了解员工如何使用网络资源&#xff0c;确保合规性&#xff0c;预防安全风险&#xff0c;并优化网络性能。以下是上网行为审计系统的主要功…

基于SpringBoot的招聘信息管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 个人中心 用户…

JavaScript中==和===的区别

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 前言 JavaScript 中的相等运算符无疑是新手开发者最容易混淆的知识点之一。 和这两个运算符的细微差别往往会在代码中造成一些令人困惑的行为 在本文中,我们将深入探讨这两个…

数据结构-冒泡排序

1 概念 冒泡排序属于一种常见的交换排序&#xff0c;根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。具体操作是按顺序&#xff08;从前往后或从后往前&#xff09;两两对比元素直至本次排序结束&#xff0c;每次排序确认一个固定值&#xff08;末位或首…

Linux——远程连接服务器

sshd服务端 ssh客户端 ssh 服务配置 #ssh 服务安装包 openssh-server [rootserver1 ~] # vim /etc/ssh/sshd_config 17 . #Port 22 # 监听端口&#xff0c;默认监听 22 端口 【默认可修改】 18 . #AddressFamily any #IPV4 和 IPV6 协议家族用哪个&#xff0c; any 表示二者…