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

news2024/11/23 14:56:33

文章目录

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

基本思想

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

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

回溯算法一般适用于求解组合问题、排列问题、搜索问题等。由于在搜索过程中,需要不断地重新选择和放弃某个状态,所以回溯算法具有空间复杂度为 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.coloradmin.cn/o/650769.html

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

相关文章

12-事件模型(也就是一个先后触发顺序)

一、事件与事件流 HTML文档、浏览器中发生的一种交互。使得具备互动性&#xff0c;加载、鼠标、自定义事件。 由于DOM是一个树结构&#xff0c;意味着标签存在嵌套关系&#xff0c;当绑定事件的时候&#xff0c;当触发子节点的时候&#xff0c;一个顺序问题&#xff0c;概念-事…

人机交互学习-4 交互设计过程

交互设计过程 交互设计过程基本活动关键特征 设计过程中的问题如何选取用户&#xff1f;如何明确需求&#xff1f;如何提出候选方案&#xff1f;如何在候选方案中选择&#xff1f; 交互设计生命周期模型星型生命周期模型可用性工程生命周期模型 交互设计过程管理界面设计的4个支…

这三个方法可以视频音频转换你知道吗?

小明&#xff1a;你听说过音频转换吗&#xff1f;最近我在学习音乐制作&#xff0c;发现这个功能特别有用&#xff01; 小红&#xff1a;啊&#xff0c;好像没有听说过。它是用来干嘛的&#xff1f; 小明&#xff1a;简单来说&#xff0c;就是可以将不同格式的音频文件进行转…

嵌入式Linux应用开发笔记:串口

文章目录 目的基础说明开发准备设备树应用程序 应用程序与演示代码演示 总结设备树文件 目的 串口&#xff08;UART&#xff09;是嵌入式设备中比较常用的功能。这篇文章将记录下应用程序中串口操作相关内容。 这篇文章中内容均在下面的开发板上进行测试&#xff1a; 《新唐N…

阿里 P8 架构师总结的 Java 面试笔记,上线仅七天,Github 标星 55K

作为一名优秀的程序员&#xff0c;技术面试是不可避免的一个环节&#xff0c;一般技术面试官都会通过自己的方式去考察程序员的技术功底与基础理论知识。 如果你参加过一些大厂面试&#xff0c;肯定会遇到一些这样的问题&#xff1a; 1、看你项目都用的框架&#xff0c;熟悉 …

AI翻唱整合

感谢阅读 不完全原创声明环境部署下载工具包安装人声背景音分离工具分离消除脏数据&#xff08;比如杂音&#xff09;准备自己的声音预处理完工效果参考 不完全原创声明 本人使用了多个第三方软件&#xff0c;并修改了一部分代码使得其可以在PC上训练&#xff0c;如有侵权请联…

看板是什么?使用看板进行任务管理有哪些好处?

看板是一个易于使用的工具&#xff0c;用于可视化和管理工作流程。 它的特点是有一列代表工作流程的各个阶段。看板卡被用来跟踪各个任务和活动在各个阶段的进展情况。 看板的两种主要类型是实体看板和数字看板。实体看板最适合办公室内、同地办公的团队。数字看板更适合远程和…

数据库MySQL学习-数据查询(持续更新中...)

MySQL数据库 MySQL是DBMS软件系统&#xff0c;通过这些系统来维护管理数据库。 DBMS类似于用于和数据库之间的桥梁。 一、安装配置 下载免费的MySQL 社区版&#xff0c;安装后需要下载MySQL workbench vscode phpmyadmin等工具来接入MySQL。 MySQL可以管理多个数据库的&…

广告数仓:数仓搭建

系列文章目录 广告数仓&#xff1a;采集通道创建 广告数仓&#xff1a;数仓搭建 文章目录 系列文章目录前言一、环境搭建1.hive安装2.编写配置文件3.拷贝jar包4.初始化源数据库5.修改字符集6.更换Spark引擎1.上传并解压spark2.修改配置文件3.在hadoop上创建需要的文件夹4.上传…

离散数学题目收集整理练习(期末过关进度10%)

✨博主&#xff1a;命运之光 &#x1f984;专栏&#xff1a;离散数学考前复习&#xff08;知识点题&#xff09; &#x1f353;专栏&#xff1a;概率论期末速成&#xff08;一套卷&#xff09; &#x1f433;专栏&#xff1a;数字电路考前复习 ✨博主的其他文章&#xff1a;点击…

观察者模式(二十)

相信自己&#xff0c;请一定要相信自己 上一章简单介绍了迭代器模式(十九), 如果没有看过, 请观看上一章 一. 观察者模式 引用 菜鸟教程里面 观察者模式介绍: https://www.runoob.com/design-pattern/observer-pattern.html 当对象间存在一对多关系时&#xff0c;则使用观察…

cef支持.net6.0

开发环境&#xff1a;vs2022,.net6.0 cef CefSharp.Common.NETCore 114.2.100 安装 Cefsharp简介 CEF &#xff0c;全称Chromium Embedded Framework &#xff0c;基于谷歌 Chromium项目的开源Web Browser控件&#xff0c;它的主要用途是嵌入了第三方应用以实现浏览器相关的功…

【C#】简单认识TransactionScope,以及常见的事务类型

在实际项目开发时&#xff0c;后端编码少不了事务处理。 为什么要用事务&#xff0c;其中一个最直接的原因就是保持数据完整性和一致性 目录 1、C#事务概念1.1、逻辑单元1.2、Transaction对象1.3、事务简单实例 2、事务场景描述3、事务流程图3.1、没有事务流程3.2、有事务流程 …

黑客是怎样练成的?

网学黑客技术的人越来越多了&#xff0c;不少人都不知道该怎么学&#xff0c;今天就来详细的说一说黑客是如何炼成的。 首先&#xff0c;什么是黑客&#xff1f; 黑客 &#xff1a;泛指擅长IT技术的电脑高手 黑客一词&#xff0c;源自英文Hacker&#xff0c;早期其实就是一群…

【MySQL】一文带你了解检索数据

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

21 RBM(Restricted Boltzmann Machine)——受限玻尔兹曼机

文章目录 21 RBM(Restricted Boltzmann Machine)——受限玻尔兹曼机21.1 背景介绍22.2 RBM模型表示22.3 Inference问题22.4 Marginal问题 21 RBM(Restricted Boltzmann Machine)——受限玻尔兹曼机 21.1 背景介绍 什么是玻尔兹曼机&#xff1a; 简单来说就是具有条件的Marko…

无线蓝牙耳机排行榜,十大口碑最好蓝牙耳机

近年来&#xff0c;随着生活水平的提高&#xff0c;越来越多的人对高品质蓝牙耳机的需求日益增加。无论我们在选购什么产品&#xff0c;我们都会考虑一个价值范围&#xff0c;买蓝牙耳机也是同样&#xff0c;都会给自己一个预算&#xff0c;然后根据预算去网上搜寻这个价格范围…

【python学习】-读入xlsx文件,将datetime.time转为minute的格式,并将新数据存入csv文件

读入xlsx文件&#xff0c;将datetime.time转为minute的格式&#xff0c;并将新数据存入csv文件 任务概要思路设计代码实现导入相关库时间转换函数算法内核csv文件结果 接到一个需求&#xff0c;师兄在做稳定性测试时&#xff0c;时间显示格式为<class ‘datetime.time’>…

深入剖析 Python 函数参数传递机制及高级应用

前言 在本篇文章中&#xff0c;笔者将带你深入探讨 Python 函数传参的进阶主题。 通过阅读本篇文章&#xff0c;你可以深入了解 Python 函数传参的进阶主题&#xff0c;掌握更多高级的函数技巧&#xff0c;提升你的 Python 编程能力。 前面分享了Python 函数传参基础篇&#xf…

图像生成--对抗生成模型

生成模型概述 对抗生成模型 机器学习中的两大主要问题&#xff1a; 判别生成 判别模型的典型代表即为图像分类任务&#xff0c;即给定一个数据&#xff0c;判定他是哪一类。 判别模型学习到的是一个概率&#xff08;贝叶斯过程&#xff09; 而生成模型的区别在于&#xf…