回溯算法练习day.1

news2025/4/17 3:32:24

回溯算法

回溯法的作用

递归函数与回溯算法是相辅相成的,回溯法往往在递归函数的“下面”

使用原因:有些问题无法暴力进行搜索,只能通过回溯法来解决,例如

1.组合问题

2.切割问题

3.子集问题

4.排列问题

5.棋盘问题

回溯算法是一种效率很低的算法

理解回溯法

所有的回溯法都可以抽象为一个树形结构,因为回溯其实就是一个递归的过程,递归一定是有终止的,回溯法都可以抽象为一颗n叉树,树的宽度就是我们回溯法处理的集合的大小,通常使用for循环来遍历,树的深度就是递归的深度,因为递归一定是有终止的,终止之后会一层一层的向上返回,如下图所示

回溯法的模版

一般来说递归函数没有返回值,因此都是void,一般名为backtracking,回溯法的参数一般较多,在实现过程中需要用到数据再进行参数的添加,即为

void backtracking(参数列表)

{

        //一般到达终止条件时,都是搜集结果的时候

        if(终止条件)

        {

                收集结果

                return ;

        }

        //单层搜索逻辑

        for(集合的元素集)

        {

                处理节点;

                递归函数;

                回溯操作;//撤销处理节点的情况

        }

        return;

}

77.组合

链接:. - 力扣(LeetCode)

题目描述:

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

示例 2:

输入:n = 1, k = 1
输出:[[1]]

提示:

  • 1 <= n <= 20
  • 1 <= k <= n

思路:

只能使用回溯算法来解决,回溯算法通过递归来控制for循环的层数,我们将其抽象为树形结构,我们以n为4,k为2为例,即如下图所示

在第一个子树里,我们将1取出来,再将2,3,4分别取出,就得到了我们需要的集合(叶子节点),从组合来看,12与21是一个集合,因此前面取出的元素不需要再放回去

回溯实现:

1.先确定递归函数的参数和返回值,返回值一般为空,我们需要n和k的值,使用一个变量,将我们每次递归开始要搜索的位置传递进来

2.确定递归终止条件,到了叶子节点就是我们需要的结果,就进行结果的收集

3.确定单层递归逻辑,每一个节点都是一个for循环,起始位置都是开始要搜索的位置传递进来,遍历剩余的元素,存放路径,再去递归遍历,再进行回溯的过程

代码实现:

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
//用来存储单个集合的结果
int *path;
//用来记录单个集合里的元素个数
int pathtop;

//存储全部的结果
int **ans;
//记录最后得到的组合数
int anstop;

void backtracking(int n, int k, int startindex)
{
    //当单个结果集的元素个数满足我们k值时
    if(pathtop == k)
    {
        //由于 backtracking 函数中 path 数组是在递归过程中不断修改的,如果直接将 path 数组的地址存储到 ans 数组中,那么最终 ans 中的所有结果集都将指向同一个地址,导致结果错误
        int *tem = (int *)malloc(sizeof(int) * k);
        for(int i = 0; i < k; i++)
        {
            tem[i] = path[i];
        }
        //将单个结果集存入全部的数组中
        ans[anstop++] = tem;
        return ;
    }

    int j;
    //每次开始遍历的位置
    for(j = startindex; j <= n; j++)
    {
        //记录遍历过的值
        path[pathtop++] = j;
        //递归遍历剩余的元素,从当前的下一个开始
        backtracking(n,k,j+1);
        //回溯到上一步
        --pathtop;
    }
}

int** combine(int n, int k, int* returnSize, int** returnColumnSizes) {
    // 分配存储结果的指针数组空间
    ans = (int **)malloc(sizeof(int *) * 1000);
    // 分配存储当前组合的数组空间
    path = (int *)malloc(sizeof(int) * k);
    // 初始化结果数组和当前组合数组的索引
    anstop = pathtop = 0;

    backtracking(n,k,1);
    // 将结果集中的组合的个数存储
    *returnSize = anstop;
    // 分配存储每个组合长度的数组空间
    *returnColumnSizes = (int*)malloc(sizeof(int) * (*returnSize));
    for(int i = 0; i < *returnSize; i++) {
        (*returnColumnSizes)[i] = k;
    }

    return ans;
}

根据我们抽象出来的树形结构,我们其实可以看出有些部分的遍历其实是无用的,因为元素个数的组合已经不可能到达k个,因我们可以进行剪枝的操作,这样我们就可以减少遍历的次数

在回溯函数中的for循环里,我们每次都会从开始位置遍历到n的位置,其中有很多的步骤是不必要的,因为我们需要得到的每个子集的元素个数为k,而我们已经拿到了pathtop个元素,因此我们还需要的元素个数就应该为k-pathtop

因此我们在集合n中最多从n-(k-pathtop)+1的位置开始遍历,再往后遍历即使加上我们前面的已经取到的元素,也达不到k个,因此就不需要再往后进行遍历

在这里

n 是集合中的总元素数量。

k - pathtop 是还需要选择的元素数量。

因此,n - (k - pathtop) 表示从集合中剩余的元素中开始选择。

加上 1 是因为在程序中,数组的索引是从 0 开始的,而不是从 1 开始的。因此,我们需要考虑起始位置的下一个位置作为实际的起始索引,以确保包含了剩余的元素。

综上所述,加上 1 是为了正确计算起始位置的索引,确保我们在遍历时能够包括剩余的元素

根据分析,就可以知道剪枝后的代码只需要修改for循环中的条件部分


void backtracking(int n, int k, int startindex)
{
    //当单个结果集的元素个数满足我们k值时
    if(pathtop == k)
    {
        //由于 backtracking 函数中 path 数组是在递归过程中不断修改的,如果直接将 path 数组的地址存储到 ans 数组中,那么最终 ans 中的所有结果集都将指向同一个地址,导致结果错误
        int *tem = (int *)malloc(sizeof(int) * k);
        for(int i = 0; i < k; i++)
        {
            tem[i] = path[i];
        }
        //将单个结果集存入全部的数组中
        ans[anstop++] = tem;
        return ;
    }

    int j;
    //每次开始遍历的位置
    for(j = startindex; j <= n - (k-pathtop) + 1; j++)
    {
        //记录遍历过的值
        path[pathtop++] = j;
        //递归遍历剩余的元素,从当前的下一个开始
        backtracking(n,k,j+1);
        //回溯到上一步
        --pathtop;
    }
}

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

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

相关文章

ubuntu 更改 ssh 默认端口 22 以加固安全

出于加固安全考虑&#xff0c;一般公司会禁用 ssh 的 22 端口号&#xff0c;因此我们需要改为其他端口。 1、ssh 命令行登录 进入台式机&#xff0c;修改 /etc/ssh/sshd_config 文件中的 Port 配置行&#xff0c;将 22 改为 8022&#xff0c;保存修改后&#xff0c;重启 ssh 服…

一起学习python——基础篇(19)

今天来说一下python的如何修改文件名称、获取文件大小、读取文中指定的某一行内容。 1、修改文件名称&#xff1a; import os testPath"D:/pythonFile/test.txt" testPath2"D:/pythonFile/test2.txt" #修改文件名称使用rename方法&#xff0c; #第一个参…

Java文件操作-IO流

一、文件 1.基本概念 文件&#xff1a;文件是保存数据的地方&#xff0c;比如经常使用的word文档&#xff0c;txt文件&#xff0c;excel文件…都是文件。它既可以保存一张图片&#xff0c;也可以保持视频&#xff0c;声音… 文件流&#xff1a;文件在程序中是以流的形式来操…

Java项目-源码!大学生兼职信息系统

大学生兼职信息系统 1、功能介绍1.1、演示视频 2、系统部分功能展示2.1、管理员登录2.2、管理员功能模块2.2.1、轮播图管理2.2.2、招聘信息管理2.2.3、企业信息管理 3、系统概述4、开发环境 1、功能介绍 本文以Java为开发技术&#xff0c;实现了一个大学生兼职信息系统。 功能…

SF506DS-ASEMI开关电源二极管SF506DS

编辑&#xff1a;ll SF506DS-ASEMI开关电源二极管SF506DS 型号&#xff1a;SF506DS 品牌&#xff1a;ASEMI 封装&#xff1a;TO-252 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;5A 最大循环峰值反向电压&#xff08;VRRM&#xff09;&#xff1a;600V 最大…

.net框架和c#程序设计第三次测试

目录 一、测试要求 二、实现效果 三、实现代码 一、测试要求 二、实现效果 数据库中的内容&#xff1a; 使用数据库中的账号登录&#xff1a; 若不是数据库中的内容&#xff1a; 三、实现代码 login.aspx文件&#xff1a; <% Page Language"C#" AutoEventW…

MySQL Innodb中 可重复读隔离级别是否能完全规避幻读

一、MySQL 可重复读隔离级别下的幻读 在 MySQL Innodb引擎可重复读隔离级别下&#xff0c;已经尽可能最大程度的规避幻读的问题了&#xff0c;使得大多数情况下&#xff0c;重复读都是可以得到一致的结果。 针对于读数据&#xff0c;可以大致分为两种模式&#xff0c;快照读&…

【最新华为ensp模拟器安装(内含文件下载)】

详细安装步骤 ensp介绍ensp安装需要的软件具体安装步骤查看virtualbox 配置信息是否正确ensp配置相关测试ensp安装是否成功教程亲测没问题&#xff0c;需要的同学点个关注呗。。。。 ensp介绍 ensp是华为路由交换模拟器&#xff0c;目前e.huawei.com上已经下载不到&#xff0c…

二、Flask会话技术和模板语言

Cookie Session # views.py: 路由 + 视图函数 import datetimefrom flask import Blueprint, render_template, request, redirect, session from .models import *# 蓝图 blue = Blueprint(user, __name__)# 首页 可以写两个路由,都是访问同一个函数 @blue.route(/) @blue.ro…

关于机器学习/深度学习的一些事-答知乎问(二)

进化算法与深度强化学习算法结合如何进行改进&#xff1f; &#xff08;1&#xff09;进化算法普遍存在着样本效率低下的问题&#xff0c;虽然其探索度较高&#xff0c;但其本质为全局随机性搜索&#xff0c;需要在整个回合结束后才能更新其种群&#xff0c;而深度强化学习在每…

普乐蛙VR航天体验馆设备VR太空飞船VR元宇宙展厅

三天小长假就要来啦&#xff01;五一假期也即将到来。老板们想捉住人流量这个财富密码吗&#xff1f;那快快行动起来&#xff01;开启VR体验项目&#xff0c;假期赚翻天&#xff01;小编亲测&#xff01;&#xff01;这款设备刺激好玩&#xff0c;想必会吸引各位家长小孩、学生…

v-show和v-if的区别和使用场景(超级详细)

文章目录 一、v-show与v-if的共同点二、v-show与v-if的区别三、v-show与v-if原理分析# v-show原理v-if原理 四、v-show与v-if的使用场景参考文献 一、v-show与v-if的共同点 我们都知道在 vue 中 v-show 与 v-if 的作用效果是相同的(不含v-else)&#xff0c;都能控制元素在页面…

LangChain-25 ReAct 让大模型自己思考和决策下一步 AutoGPT实现途径、AGI重要里程碑

背景介绍 大模型ReAct&#xff08;Reasoning and Acting&#xff09;是一种新兴的技术框架&#xff0c;旨在通过逻辑推理和行动序列的构建&#xff0c;使大型语言模型&#xff08;LLM&#xff09;能够达成特定的目标。这一框架的核心思想是赋予机器模型类似人类的推理和行动能…

js基础知识+练习

一&#xff0c;JavaScript简单了解 1.什么是JavaScript JavaScript简称JS&#xff0c;是较为流行的一种前端编程语言&#xff0c;是一种脚本语言&#xff0c;通过解释器运行&#xff0c;主要在客户端&#xff08;浏览器&#xff09;上运行&#xff0c;现在也可以基于node.js在服…

程序员的故事:麦哲伦死于JAVA之争

程序员的故事&#xff1a;麦哲伦死于JAVA之争 1400年&#xff0c;永乐年间&#xff0c;永乐皇帝七点钟准时上班了&#xff0c;清了清嗓子&#xff0c;问道&#xff1a;大家都到了没有&#xff1f;今天我们开个会&#xff0c;主要是讲一下项目用什么语言&#xff1f; 元朝的时候…

airtest-ios真机搭建实践

首先阅读4 ios connection - Airtest Project Docs 在Windows环境下搭建Airtest对iOS真机进行自动化测试的过程相对复杂&#xff0c;因为iOS的自动化测试通常需要依赖Mac OS系统&#xff0c;但理论上借助一些工具和服务&#xff0c;Windows用户也可以间接完成部分工作。下面是…

Python中的回调函数和C中函数指针什么关系?

你好&#xff0c;我是安然无虞。 Python 回调 在Python中&#xff0c;‘回调函数’ (callback) 是指一个作为参数传递给其它代码的函数。 目的是在后者完成某些操作后调用这个传递进来的函数。 回调允许在执行异步操作或处理事件时通知调用者代码。 回调函数通常用于&#…

家庭网络防御系统搭建-虚拟机安装siem/securityonion网络连接问题汇总

由于我是在虚拟机中安装的security onion&#xff0c;在此过程中&#xff0c;遇到很多的网络访问不通的问题&#xff0c;通过该文章把网络连接问题做一下梳理。如果直接把securityonion 安装在物理机上&#xff0c;网络问题则会少很多。 NAT无法访问虚拟机 security onion虚拟…

团结引擎+OpenHarmony 1配置篇

团结引擎OpenHarmony 1 配置篇 app团结鸿蒙化第一课一 DevEco Studio 下载安装二 团结引擎三 出包 app团结鸿蒙化第一课 1 团结引擎配置2 DevEco Studio 配置 一 DevEco Studio 下载安装 申请开发者套件 1 注册华为账号 签署协议 官网 2 认真填写 DevEco Studio 开发套件申请…

某网站sign签名参数与数据响应加密逆向分析

文章目录 1. 写在前面2. 接口分析3. 断点分析4. 扣代码 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力…