回溯算法基本思想及其实现

news/2024/11/20 0:29:58/

文章目录

  • 基本思想
  • 回溯算法的递归框架
    • 组合问题
    • 组合总和
    • 组合去重
    • 子集
    • 全排列

基本思想

回溯算法是一种递归算法,它试图通过尝试不同的选择,解决一个问题。它的基本思想是从可能的决策开始搜索,如果发现这条路往下走不能得到有效的解答,就返回上一层重新选择另外一种可能的决策,并且重复以上的步骤。

具体来说,回溯算法通过深度优先搜索的方式,遍历问题的解空间。在深度优先搜索的过程中,当搜索到某一层时,根据问题的限制条件判断该层的节点状态是否满足条件。如果条件不满足,则这个节点的状态被排除,并回溯到当前节点的父节点,重新进行遍历。如果条件满足,则继续搜索下一层的节点,并重复以上操作,直到搜索到一个符合条件的解或者遍历完整个解空间。

回溯算法一般适用于求解组合问题、排列问题、搜索问题等。由于在搜索过程中,需要不断地重新选择和放弃某个状态,所以回溯算法具有空间复杂度为 O(N) 的特点。

因为回溯算法的搜索路径呈现树形结构,所以有时候也被称作"回溯搜索"或"深度优先搜索+状态撤销"算法。

回溯算法的递归框架

回溯算法是一种典型的递归算法,它通常需要使用递归函数来实现。回溯算法的递归框架一般为以下形式:

def backtrack(candidate, state):if state == target:                        # 满足条件,输出结果output(candidate)return# 选择for choice in choices:make_choice(choice)                    # 做选择backtrack(candidate, state + 1)        # 递归进入下一层状态undo_choice(choice)                    # 撤销选择

这个递归框架通常包括三个部分:选择、递归和撤销选择。具体来说,每次迭代中,算法选择一个可用的状态或者变量,进行尝试。然后进入下一层状态,继续进行递归。如果递归过程中发现当前状态不符合要求,则需要撤销前面的选择,回到上一层状态,重新开始搜索。

在回溯算法中,重点是如何定义选择和撤销操作。这些操作通常和具体问题相关,需要根据问题的特点进行定义。回溯算法的实现应该包括以下步骤:

  1. 判断是否到达了目标状态。当搜索到符合要求的状态时,将其输出,并结束搜索。
  2. 选择当前状态或变量。在当前状态的所有可选序列、可选子节点等中,选择一个未被搜索过的状态或变量。
  3. 尝试选择新状态。在进入下一层状态之前,需要先尝试选择新状态,完成相应的操作,比如加入选择列表等。
  4. 递归进入下一层状态。进入下一层状态并继续搜索。
  5. 撤销选择。如果当前状态不符合要求,需要撤销前面所做的所有选择、完成的操作等,返回上一层状态,继续搜索其他可选序列或子节点。

组合问题

LeetCode第77题:https://leetcode.cn/problems/combinations/
在这里插入图片描述

首先定义两个全局变量用于存放结果集和当前候选集合:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. 递归的终止条件:找到了一个子集大小为k的组合,即
if (temp.size() == k) {ans.add(new ArrayList<Integer>(temp));return;
}
  1. 选择
for(int i = cur; i<=n;i++){temp.add(i);	// 当前元素加入候选集合dfs(i+1,n,k);	// 递归进入下一层状态temp.remove(temp.size() - 1);	// 当前元素移除候选集合
}

完整代码

class Solution {List<List<Integer>> ans = new ArrayList<List<Integer>>();List<Integer> temp = new ArrayList<Integer>();public List<List<Integer>> combine(int n, int k) {dfs(1, n, k);return ans;}public void dfs(int cur, int n, int k) {// 剪枝:可选的元素不足K个if (temp.size() + (n - cur + 1) < k) {return;}if (temp.size() == k) {ans.add(new ArrayList<Integer>(temp));return;}for(int i = cur; i<=n;i++){temp.add(i);dfs(i+1,n,k);temp.remove(temp.size() - 1);}}
}

组合总和

LeetCode 第39题:组合总和:https://leetcode.cn/problems/combination-sum/
在这里插入图片描述

首先定义两个全局变量用于存放结果集和当前候选集合:

// 存放所有满足条件的结果
List<List<Integer>> ans = new ArrayList<List<Integer>>();
// 当前得到的候选集
List<Integer> temp = new ArrayList<Integer>();
  1. 递归的终止条件:当前候选集合总和大于target或找到总和为target的组合,即
if(sum > target){return;
}
if(sum == target){res.add(new ArrayList<Integer>(temp));return;
}
  1. 选择
for(int i=index;i<candidates.length;i++){temp.add(candidates[i]);	// 当前元素加入候选集合sum+=candidates[i];			// 当前候选集总和dfs(candidates, target, i);	// 递归进入下一层状态// 当前元素移除候选集合sum -= temp.get(temp.size()-1);temp.remove(temp.size()-1);
}

完整代码

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> temp = new ArrayList<Integer>();int sum = 0;public List<List<Integer>> combinationSum(int[] candidates, int target) {dfs(candidates, target, 0);return res;}public void dfs(int[] candidates, int target, int index){if(sum > target){return;}if(sum == target){res.add(new ArrayList<Integer>(temp));return;}for(int i=index;i<candidates.length;i++){temp.add(candidates[i]);sum+=candidates[i];dfs(candidates, target, i);sum -= temp.get(temp.size()-1);temp.remove(temp.size()-1);}}
}

组合去重

LeetCode第40题: 组合总和 II:https://leetcode.cn/problems/combination-sum-ii/
在这里插入图片描述

在上一题组合求和基础上,定义一个used数组记录元素是否是否过:

  1. 递归的终止条件:当前候选集合总和大于target或找到总和为target的组合,即
if(sum > target){return;
}
if(sum == target){res.add(new ArrayList<Integer>(temp));return;
}
  1. 选择
for(int i=cur;i<candidates.length;i++){//当前元素是否有效if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){continue;}temp.add(candidates[i]);	// 当前元素加入候选集合used[i] = true;		// 标记当前元素已使用sum += candidates[i];		// 当前候选集总和dfs(candidates,target,i+1,sum,used);		// 递归进入下一层状态// 当前元素移除候选集合sum -= temp.get(temp.size() - 1);temp.remove(temp.size() - 1);used[i] = false;
}
for(int i=index;i<candidates.length;i++){temp.add(candidates[i]);	// 当前元素加入候选集合sum+=candidates[i];			// 当前候选集总和dfs(candidates, target, i);	// 递归进入下一层状态// 当前元素移除候选集合sum -= temp.get(temp.size()-1);temp.remove(temp.size()-1);
}

完整代码

class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> temp = new ArrayList<Integer>();public List<List<Integer>> combinationSum2(int[] candidates, int target) {Arrays.sort(candidates);boolean[] used = new boolean[candidates.length];dfs(candidates, target, 0, 0, used);return res;}public void dfs(int[] candidates, int target, int cur, int sum, boolean[] used){if(sum > target){return;}if(sum == target){res.add(new ArrayList<Integer>(temp));return;}for(int i=cur;i<candidates.length;i++){if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){continue;}temp.add(candidates[i]);used[i] = true;sum += candidates[i];dfs(candidates,target,i+1,sum,used);sum -= temp.get(temp.size() - 1);temp.remove(temp.size() - 1);used[i] = false;}}
}

子集

  1. 子集: https://leetcode.cn/problems/subsets/
    在这里插入图片描述
class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();Deque<Integer> temp = new ArrayDeque<Integer>();public List<List<Integer>> subsets(int[] nums) {dfs(nums, 0);return res;}public void dfs(int[] nums, int index){res.add(new ArrayList<Integer>(temp));for(int i=index;i<nums.length;i++){temp.addLast(nums[i]);dfs(nums, i+1);temp.removeLast();}}
}

全排列

  1. 全排列: https://leetcode.cn/problems/permutations/
    在这里插入图片描述
class Solution {List<List<Integer>> res = new ArrayList<List<Integer>>();List<Integer> temp = new ArrayList<Integer>();public List<List<Integer>> permute(int[] nums) {boolean[] used = new boolean[nums.length];dfs(nums, used, 0);return res;}public void dfs(int[] nums, boolean[] used, int index){if(temp.size() == nums.length){res.add(new ArrayList<Integer>(temp));return;}for(int i=0;i<nums.length;i++){if(used[i]){continue;}used[i] = true;temp.add(nums[i]);dfs(nums, used, i+1);used[i] = false;temp.remove(temp.size()-1);}}
}

http://www.ppmy.cn/news/404476.html

相关文章

C++入门教程||C++ STL 教程

C STL 教程 C STL 教程 在前面的章节中&#xff0c;我们已经学习了 C 模板的概念。C STL&#xff08;标准模板库&#xff09;是一套功能强大的 C 模板类&#xff0c;提供了通用的模板类和函数&#xff0c;这些模板类和函数可以实现多种流行和常用的算法和数据结构&#xff0c…

Java中List、Set、Map的区别和实现方式

Java中List、Set、Map的区别和实现方式 List List 是一个有序的集合&#xff0c;即元素按照插入的顺序进行排序&#xff0c;可以有重复的元素。因为是有序的&#xff0c;所以可以根据下标来获取元素或者遍历整个集合内的元素。常用的实现类包括 ArrayList 和 LinkedList。 A…

PAT 1080. Graduate Admission (30)

算法思想&#xff1a;首先对所有学生按成绩排序&#xff0c;然后依次放入各个学校&#xff08;过程中注意是否排名和该学校最后一名一样&#xff09; 算法的主要步骤如下&#xff1a; 首先&#xff0c;根据申请者的总分和 GE 分数对申请者列表进行排序&#xff0c;将总分高的…

高通Kryo 架构

高通Kryo架构 Kryo Kryo是Qualcomm Technologies推出的首款定制设计的64位CPU。Kryo采用最新14纳米FinFET工艺制程&#xff0c;拥有四个核心&#xff0c;单核支持最高达2.2GHz的处理速度&#xff0c;与骁龙810处理器相比&#xff0c;Kryo CPU在性能方面将带来最高达两倍的提升…

Qualcomm 处理器 Krait架构

Krait是美国高通公司基于ARMv7-A指令集、自主设计的采用28纳米工艺的全新处理器微架构。能够实现每个内核最高运行速度可达2.5GHz&#xff0c;较高通第一代的Scorpion CPU微架构在性能上提高60%以上&#xff0c;并将功耗降低65%。 骁龙移动智能处理器的S4系列多数使用了Krait …

什么是SoC(System-on-a-Chip)

SoC SoC的全称叫做&#xff1a;System-on-a-Chip&#xff0c;中文的的意思就是“把系统都做在一个芯片上”&#xff0c;如果在PC时代我们说一个电脑的核心是CPU&#xff0c;那么在智能终端时代&#xff0c;手机的核心就是这个SoC。 这么说是因为SoC上集成了很多手机上最关键的…

【Unity3D】unity-mono编译libmono.so成功

目录 文章最终成功编译出libmono.so如下图所示&#xff0c;历时9天 一、下载文件配置环境 二、下载Unity-Mono库 三、正式开始编译libmono.so 1、libmono.so编译文件基础说明 2、修改相关文件&#xff08;及其重要&#xff09; ① 修改/home/用户名/mono/external/build…

ARM Cortex-X1架构 自研的终结者

随着麒麟9000和三星Exynos 1080的发布&#xff0c;Android手机芯脏领域正式进入了5nm时代。可惜&#xff0c;麒麟9000的CPU架构仍然停留在ARM去年发布的Cortex-A77阶段&#xff0c;而Exynos 1080虽然用上了ARM最新发布的Cortex-A78&#xff0c;但出于定位的原因它并没能引入AMD…