【算法】回溯法

回溯法(Backtracking)是一种通过系统地搜索问题的解空间来找到所有可能结果或最佳解的算法设计范式。它广泛应用于解决各种组合优化问题,比如图的着色、数独求解、八皇后问题、旅行商问题等。

在程序中,回溯法通常表现为递归函数。

递归代码的基本结构是一种自我调用的方式,用来解决可以被分解为子问题且具有重复结构的问题。

一、 递归代码的基本通用模板

def recursive_function(parameters):# 1. 基线条件 (Base Case): 定义递归何时停止if some_condition:return some_value  # 一旦满足条件,直接返回结果,结束递归# 2. 递归过程 (Recursive Case): 将问题分解为子问题,再递归调用自身# 例如,计算更小规模的问题smaller_problem_result = recursive_function(smaller_parameters)# 3. 合并子问题的结果并返回result = do_some_operations(smaller_problem_result)return result
1. 基线条件(Base Case)
  • 作用:为递归设立清晰的停止点,避免无限递归。
  • 设计方式:要满足递归逻辑的结束条件,例如问题规模缩小到 0、1,或者已经达到了目标条件等。
2. 递归过程(Recursive Case)
  • 作用:将问题规模缩小,递归解决规模更小的子问题。
  • 设计方式
    • 提取当前层的问题需要解决的部分。
    • 为剩余的子问题递归调用自身。
    • 每一级调用后,都会处理一些局部问题,然后把剩下的部分交给接下来的递归。
3. 合并结果
  • 作用:将子问题递归返回的结果整合,形成当前问题的解。
  • 设计方式:合并或加工子问题数据(例如加和、拼接、选取最优解等),最后返回结果。

二、 示例:全排列(Permutations)

1. 代码实现
from typing import Listdef permute(nums: List[int]) -> List[List[int]]:# 1. 基线条件:当列表为空时,返回一个空排列 [[]]if not nums:return [[]]# 2. 递归过程:让每个数字轮流作为第一个元素result = []for i, num in enumerate(nums):# 剩余子问题remains = nums[:i] + nums[i+1:]# 对剩余部分递归求全排列permutations = permute(remains)# 3. 合并结果:将当前数字与子问题的结果拼接for perm in permutations:result.append([num] + perm)return result

为列表 nums = [1, 2, 3],生成所有排列:

nums = [1,2,3]
result = permute(nums)
print(result)

以上述调用代码为例,我们将用 显式栈 来模拟递归过程,并结合输入 nums = [1, 2, 3] 展示每一步的调用堆栈。递归函数调用实际上会通过系统栈记录调用信息,直到递归的返回条件触发为止。

2. 递归的堆栈过程

初始调用是 permute([1, 2, 3])。整个递归的堆栈过程包括两部分:

  1. 递归展开:函数调用不断深入,分解问题。
  2. 递归回溯(收缩):从底层结果开始逐步返回,拼接结果。
2.1 堆栈结构:
permute([1, 2, 3])   
result = []
└── 处理中:num = 1, remains = [2, 3]└── permute([2, 3])result = []└── 处理中:num = 2, remains = [3]└── permute([3])result = []└── 处理中:num = 3, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[3]]  # 将 num = 3 加入返回:[[3]]拼接:result = [[2, 3]]  # 将 num = 2 加到子问题 [[3]]└── 处理中:num = 3, remains = [2]└── permute([2])result = []└── 处理中:num = 2, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[2]]  # 将 num = 2 加入返回:[[2]]拼接:result = [[3, 2]]  # 将 num = 3 加到子问题 [[2]]返回:[[2, 3], [3, 2]]	# result.append拼接:result = [[1, 2, 3], [1, 3, 2]]  # 将 num = 1 加到子问题 [[2, 3], [3, 2]]
└── 处理中:num = 2, remains = [1, 3]└── permute([1, 3])result = []└── 处理中:num = 1, remains = [3]└── permute([3])result = []└── 处理中:num = 3, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[3]]  # 将 num = 3 加入返回:[[3]]拼接:result = [[1, 3]]  # 将 num = 1 加到子问题 [[3]]└── 处理中:num = 3, remains = [1]└── permute([1])result = []└── 处理中:num = 1, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[1]]  # 将 num = 1 加入返回:[[1]]拼接:result = [[3, 1]]  # 将 num = 3 加到子问题 [[1]]返回:[[1, 3], [3, 1]]拼接:result = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1]]  # 将 num = 2 加到子问题 [[1, 3], [3, 1]]
└── 处理中:num = 3, remains = [1, 2]└── permute([1, 2])result = []└── 处理中:num = 1, remains = [2]└── permute([2])result = []└── 处理中:num = 2, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[2]]  # 将 num = 2 加入返回:[[2]]拼接:result = [[1, 2]]  # 将 num = 1 加到子问题 [[2]]└── 处理中:num = 2, remains = [1]└── permute([1])result = []└── 处理中:num = 1, remains = []└── permute([])result = [[]]  # 基线条件,返回空排列拼接:result = [[1]]  # 将 num = 1 加入返回:[[1]]拼接:result = [[2, 1]]  # 将 num = 2 加到子问题 [[1]]返回:[[1, 2], [2, 1]]拼接:result = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]  # 将 num = 3 加到子问题 [[1, 2], [2, 1]]
返回:[[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

注意:递归函数的每次调用都有独立的上下文,每一层的result都是完全不同的变量

2.2 具体过程:

输入:nums = [1, 2, 3]

  1. 第一层递归:调用 permute([1, 2, 3])

    • 迭代 nums
      • 第一个数字为 1remains = [2, 3],递归调用 permute([2, 3])
  2. 第二层递归:调用 permute([2, 3])

    • 迭代 nums
      • 第一个数字为 2remains = [3],递归调用 permute([3])
  3. 第三层递归:调用 permute([3])

    • 迭代 nums
      • 第一个数字为 3remains = [] ,递归调用 permute([])
  4. 第四层递归:调用permute([])

    • 迭代nums:
      • 由于 nums = [],触发基线条件 if not nums
        • 返回结果 permutations = [[]](空排列)
  5. 返回第三层:

    • 遍历permutations = [[]]
      • 将 3 拼接到每个排列:[3] + [] = [3]。
        • 当前结果 result = [[3]]。返回 [[3]]
  6. 返回第二层:

    • 处理 permute([2, 3]) 的第一个分支
      • 遍历 permutations = [[3]],将 2 拼接到每个排列:[2] + [3] = [2, 3]。
        • 当前结果 result = [[2, 3]]
  7. 继续处理 permute([2, 3]) 的第二个分支:

    • 第二次选取 num = 3,剩余数字 remains = [2]
      • 递归调用 permute([2])
  8. 递归处理 permute([2])

    • 遍历 nums = [2],进入循环:
      • 第一次选取 num = 2,剩余数字 remains = [],递归调用 permute([])
  9. 递归处理permute([])

    • 迭代nums:
      • 由于 nums = [],触发基线条件 if not nums
        • 返回结果 permutations = [[]](空排列)
  10. 返回上一层:

    • 遍历 permutations = [[]],将 2 拼接到每个排列:[2] + [] = [2]
      • 当前结果 result = [[2]]
  11. 返回第二层:

    • 处理 permute([2, 3]) 的第二个分支
      • 遍历 permutations = [[2]],将 3 拼接到每个排列:[3] + [2] = [3, 2]
        • 当前结果 result = [[2, 3], [3, 2]]
  12. 返回第一层:

    • 处理 permute([1, 2, 3]) 的第一个分支
      • 遍历 permutations = [[2, 3], [3, 2]],将 1 拼接到每个排列:[1] + [2, 3] = [1, 2, 3][1] + [3, 2] = [1, 3, 2]
        • 当前结果 result = [[1, 2, 3], [1, 3, 2]]
  13. 回到 permute([1, 2, 3])
    当前栈为:

permute([1, 2, 3])
└── 处理中:num = 2, remains = [1, 3]

重复对剩下的分支处理(递归展开 + 收缩)

后续操作类似:

  • 处理 num = 2, remains = [1, 3],递归展开成 [[1, 3], [3, 1]],最终拼接得出 [[2, 1, 3], [2, 3, 1]]
  • 再处理 num = 3, remains = [1, 2],递归展开成 [[1, 2], [2, 1]],最终拼接得出 [[3, 1, 2], [3, 2, 1]]

返回最终结果:

[[1, 2, 3], [1, 3, 2],[2, 1, 3], [2, 3, 1],[3, 1, 2], [3, 2, 1]]

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

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

相关文章

【ASP.NET学习】Web Forms创建Web应用

文章目录 什么是 Web Forms?ASP.NET Web Forms - HTML 页面用 ASP.NET 编写的 Hello RUNOOB.COM它是如何工作的?经典 ASP ASP.NET Web Forms - 服务器控件经典 ASP 的局限性ASP.NET - 服务器控件ASP.NET - HTML 服务器控件ASP.NET - Web 服务器控件ASP.N…

Linux 常见运营维护,从安装软件开始,到mysql,php,redis,tomcat等软件安装,配置,优化,持续更新中。。。

下载centos7 CentOS 7 完整版(DVD): https://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.isoCentOS 7 最小化版(Minimal): https://mirrors.aliyun.com/centos/7/isos/x86_64/C…

用户界面软件05

已知应用 几乎所有的流行的用户界面架构都使用这种模式。我在这里举三个例子: 1. Seeheim 用户界面架构的特点是有一个应用核心的领域层和一个用户界面层。后者 被分为两层,叫做表示层和对话控制层。因为这个架构和面向事务系统有渊源,没有…

从玩具到工业控制--51单片机的跨界传奇【2】

咱们在上一篇博客里面讲解了什么是单片机《单片机入门》,让大家对单片机有了初步的了解。我们今天继续讲解一些有关单片机的知识,顺便也讲解一下我们单片机用到的C语言知识。如果你对C语言还不太了解的话,可以看看博主的C语言专栏哟&#xff…

LVGL移植高通点阵字库GT30L24A3W

字库芯片: GT30L24A3W MCU:STM32F429 LVGL版本:V8.4 一、实现gt_read_data() 和 r_dat_bat() 请参考下面视频 如何在32位MCU上使用高通点阵字库_哔哩哔哩_bilibili 高通字库使用教程(1)硬件链接与注意事项部分_哔哩哔哩_bilibili 高通字库使用教程(2)SPI底层函数使用_哔哩…

C# OpenCV机器视觉:转速测量

在一个看似平常却又暗藏神秘能量的日子里,阿杰正在他那充满科技感的实验室里,对着一堆奇奇怪怪的仪器发呆。突然,手机铃声如一道凌厉的剑气划破寂静,原来是工厂的赵厂长打来的紧急电话:“阿杰啊,咱们工厂新…

【Vue】Vue组件--上

目录 一、组件基础 二、组件的嵌套关系 1. 基础架构 2. 嵌套 三、组件注册方式 1. 局部注册: 2. 全局注册: 四、组件传递数据 1. 基础架构 2. 传递多值 3. 动态传递数据 五、组件传递多种数据类型 1. Number 2. Array 3. Object 六、组…

unity下载newtonsoft-json

Package Manager,输入com.unity.nuget.newtonsoft-json 右键Assets-Reinport All

SpringBoot项目实战(40)--Beetl网页开发在控制层使用通用方法映射前端不同路径的网页

在SpringBoot中使用Beetl做前端页面,后端如何使用Controller映射前端不同的页面,不需要为每个前端页面单独增加控制层方法? 因为前端页面比较多,每个前端页面对应一个独立Controller方法也是不现实的,总不能每增加一个…

【自动化测试】—— Appium安装配置保姆教程(图文详解)

目录 一. 环境准备 二. JDK安装 1. 下载JDK 2. 安装JDK 3. 配置环境 4. 验证安装 三. Android SDK安装 1. 下载Android SDK 2. 安装Android SDK 3. 安装工具 4. 配置环境 5. 验证安装 四. NodeJS安装 1. 下载NodeJS 2. 安装NodeJS 3. 验证安装 4. 安装淘宝镜像…

Oracle 终止正在执行的SQL

目录 一. 背景二. 操作简介三. 投入数据四. 效果展示 一. 背景 项目中要求进行性能测试,需要向指定的表中投入几百万条数据。 在数据投入的过程中发现投入的数据不对,需要紧急停止SQL的执行。 二. 操作简介 👉需要DBA权限👈 ⏹…

【SH】Xiaomi9刷Windows10系统研发记录 、手机刷Windows系统教程、小米9重装win10系统

文章目录 参考资料云盘资料软硬件环境手机解锁刷机驱动绑定账号和设备解锁手机 Mindows工具箱安装工具箱和修复下载下载安卓和woa资源包第三方Recovery 一键安装Windows准备工作创建分区安装系统 效果展示Windows和Android一键互换Win切换安卓安卓切换Win 删除分区 参考资料 解…

MWORKS 2025a 直播回顾 | 第二期:M语言计算环境重磅更新

MWORKS.Syslab首次推出时已实现基于Julia语言的科学计算环境,尽管如此,仍有大量工程师团队坚持使用M语言相关软件。除了使用习惯和学习语言等问题,更深层的原因在于大量历史代码资产复用的问题。为了解决这一关键问题,同元软控在后…

晨辉面试抽签和评分管理系统之八:随机编排考生的面试批次(以教师资格考试面试为例)

晨辉面试抽签和评分管理系统(下载地址:www.chenhuisoft.cn)是公务员招录面试、教师资格考试面试、企业招录面试等各类面试通用的考生编排、考生入场抽签、候考室倒计时管理、面试考官抽签、面试评分记录和成绩核算的面试全流程信息化管理软件。提供了考生…

专用小软件,完全免费,非常丝滑

今天给大家介绍一个专门将PDF数电发票合并打印的软件,这个软件可以批量操作,完全免费没有任何的广告。 电子发票专用批量打印工具 免费批量使用 软件无需安装,解压之后双击这个图标就能直接使用了。 点击右上角的加号,选中需要打…

《leetcode-runner》如何手搓一个debug调试器——架构

本文主要聚焦leetcode-runner对于debug功能的整体设计,并讲述设计原因以及存在的难点 设计引入 让我们来思考一下,一个最简单的调试器需要哪些内容 首先,它能够接受用户的输入 其次,它能够读懂用户想让调试器干嘛,…

【0x005B】HCI_Write_Default_Erroneous_Data_Reporting命令详解

目录 一、命令概述 二、命令格式及参数 2.1. HCI_Write_Default_Erroneous_Data_Reporting命令格式 2.2. Erroneous_Data_Reporting 三、生成事件及参数 3.1. HCI_Command_Complete事件 3.2. 状态码(Status) 四、命令执行流程 4.1. 命令发起阶段(主机端) 4.2. 命…

uniapp 小程序 textarea 层级穿透,聚焦光标位置错误怎么办?

前言 在开发微信小程序时,使用 textarea 组件可能会遇到一些棘手的问题。最近我在使用 uniapp 开发微信小程序时,就遇到了两个非常令人头疼的问题: 层级穿透:由于 textarea 是原生组件,任何元素都无法遮盖住它。当其…

Kotlin 快速上手指南:从安装 IntelliJ IDEA 到编写第一个程序

文章目录 什么是kotlinIntelliJ IDEA安装 IntelliJ IDEA创建 Kotlin 项目运行 Kotlin 程序更改进入后默认打开上一次项目的设置打开 IntelliJ IDEA进入设置:重新启动 IntelliJ IDEA:快速学习Kotlin变量声明类型推断条件表达式定义函数单表达式函数when 表达式when 语句的基本…

Docker 部署 Typecho

1. 官网 https://typecho.org/插件 & 主题 https://github.com/typecho-fans/plugins https://typechx.com/ https://typecho.work/2. 通过 compose 文件安装 github官网: https://github.com/typecho/Dockerfile 新建一个目录,存放 typecho 的相…