算法设计与分析实验:回溯

news2024/11/14 19:46:06

目录

一、组合总和

1.1 具体思路

1.2 思路展示

1.3 代码实现

1.4 复杂度分析

1.5 运行结果

二、全排列

2.1 具体思路

2.2 思路展示

2.3 代码实现

2.4 复杂度分析

2.5 运行结果

三、N皇后问题

3.1 具体思路

3.2 思路展示

3.3 代码实现

3.4 复杂度分析

3.5 运行结果

四、子集II

4.1 具体思路

4.2 思路展示

4.3 代码实现

4.4 复杂度分析

4.5 运行结果

结尾语


一、组合总和

力扣第39题

本题采用回溯的思想解决

1.1 具体思路

首先定义一个递归函数 backtrack,该函数接受当前的组合列表 combination 和当前位置索引 start。

在回溯函数中,首先判断当前目标值是否等于 0。如果等于 0,则将当前组合添加到结果列表中,并返回。

如果目标值小于 0,或者已经遍历到数组的末尾,直接返回。

对于每个候选数,从当前位置索引开始,依次进行如下操作:

·将当前候选数添加到组合列表中。

·调用回溯函数 backtrack,传入新的目标值(target - 候选数)和更新后的位置索引(start)。

·回溯完成后,将最后添加的候选数从组合列表中移除。

在主函数中,初始化结果列表和组合列表,调用回溯函数 backtrack。

1.2 思路展示

假设我们有候选数组 [2, 3, 6, 7] 和目标数 7。我们可以通过画出一个树状图来展示回溯的过程。

首先是整体结构:

          []

         / | \

        2  3  6  7

接下来我们开始从根节点 [] 开始遍历:

选择 2:

         [2]

        / | \

       2  3  6  7

选择 2:

      [2, 2]

      / | \

     2  3  6  7

选择 2:

   [2, 2, 2]

   / | \

  2  3  6  7

此时,目标值为 1,小于 0,因此返回。

回退到上一步,选择 3:

   [2, 3]

   / | \

  2  3  6  7

此时,目标值为 1,小于 0,因此返回。

回退到上一步,选择 6:

   [2, 6]

   / | \

  2  3  6  7

此时,目标值为 1,小于 0,因此返回。

回退到上一步,选择 7:

   [2, 7]

   / | \

  2  3  6  7

此时,目标值为 0,将当前组合 [2, 7] 加入结果列表。

回退到 [],选择 3:

   [3]

   / | \

  2  3  6  7

选择 2:

   [3, 2]

   / | \

  2  3  6  7

选择 2:

   [3, 2, 2]

   / | \

  2  3  6  7

此时,目标值为 2,继续向下选择。...

以上就是回溯的过程,通过逐步选择候选数并进行回溯,不断尝试各种组合,直到得到所有满足条件的组合。

1.3 代码实现

def combinationSum(candidates, target):
    res = []  # 存储结果的列表

    def backtrack(combination, start, target):
        if target == 0:  # 目标值为0,将当前组合添加到结果列表
            res.append(combination)
            return
        if target < 0 or start == len(candidates):  # 目标值小于0或已遍历到末尾,直接返回
            return

        for i in range(start, len(candidates)):
            backtrack(combination + [candidates[i]], i, target - candidates[i])

    backtrack([], 0, target)  # 调用回溯函数,初始组合为空列表,起始位置为0

    return res


# 示例输入


# 示例1
candidates1 = [2, 3, 6, 7]
target1 = 7
print(combinationSum(candidates1, target1))  # [[2, 2, 3], [7]]

# 示例2
candidates2 = [2, 3, 5]
target2 = 8
print(combinationSum(candidates2, target2))  # [[2, 2, 2, 2], [2, 3, 3], [5, 3]]

# 示例3
candidates3 = [2]
target3 = 1
print(combinationSum(candidates3, target3))  # []

1.4 复杂度分析

(1)时间复杂度:

回溯函数的时间复杂度取决于结果的数量。假设结果数量为R,每个结果平均长度为L,那么回溯函数的时间复杂度为O(R * L)。

在最坏情况下,假设候选列表长度为N,目标值为T,结果数量为R,结果平均长度为L,则回溯函数的时间复杂度为O(N^T * R * L)。

(2)空间复杂度:

回溯函数使用了递归调用,同时维护了一个存储结果的列表。在最坏情况下,结果数量为R,结果平均长度为L,空间复杂度为O(R * L)。

同时,递归调用的深度为目标值T,所以空间复杂度为O(T)。

但需要注意的是,以上复杂度分析都是基于没有剪枝等优化措施的实现。

1.5 运行结果

二、全排列

力扣第47题

本题采用回溯的思路解决

2.1 具体思路

首先对输入的nums数组进行排序,这样相同的数字会相邻排列。

创建一个布尔数组used,用于标记nums中的元素是否被使用过,初始化为False。

创建一个空列表res,用于存储最终的全排列结果。

定义一个回溯函数backtrack,该函数采用一个当前排列combination作为参数。

在backtrack函数中,如果combination的长度等于nums的长度,将其加入到结果res中,并返回。

否则,遍历nums数组,对于每个元素,如果它已经被使用(used[i]为True)或者与前一个元素相同且前一个元素未被使用,则跳过。

如果当前元素未被使用,将其标记为已使用,将其加入到combination中,然后递归调用backtrack函数。

递归调用完成后,将当前元素的使用状态还原,以便尝试其他分支。

2.2 思路展示

以输入数组nums = [1, 1, 2]为例。

初始状态是一个空列表[],表示当前排列为空。

在第一层的递归中,我们遍历到了元素1,因为它是第一个不重复的数,所以我们选择使用它。将1加入到当前排列中,并将其标记为已使用。进入下一层递归。

在第二层的递归中,我们继续遍历到了元素1,但由于前一个元素1已经被使用,所以我们跳过这个分支。

在第三层的递归中,我们遍历到了元素2,它是第一个不重复的数,所以我们选择使用它。将2加入到当前排列中,并将其标记为已使用。此时,当前排列为[1, 2]。

因为当前排列的长度等于输入数组的长度,所以将当前排列[1, 2]加入到最终结果中。

回溯到上一层递归,将元素2的使用状态还原,并从当前排列中移除元素2。

在第三层的递归中,我们遍历到了元素1,但由于前一个元素2已经被使用,所以我们跳过这个分支。

回溯到上一层递归,将元素1的使用状态还原,并从当前排列中移除元素1。

在第二层的递归中,我们遍历到了元素2,因为它是第一个不重复的数,所以我们选择使用它。将2加入到当前排列中,并将其标记为已使用。此时,当前排列为[2, 1]。

因为当前排列的长度等于输入数组的长度,所以将当前排列[2, 1]加入到最终结果中。

回溯到上一层递归,将元素2的使用状态还原,并从当前排列中移除元素2。

在第二层的递归中,我们遍历到了元素1,因为它是第一个不重复的数,所以我们选择使用它。将1加入到当前排列中,并将其标记为已使用。此时,当前排列为[1, 1]。

因为当前排列的长度等于输入数组的长度,所以将当前排列[1, 1]加入到最终结果中。

回溯到上一层递归,将元素1的使用状态还原,并从当前排列中移除元素1。

最终得到的全排列结果为[[1, 2], [2, 1], [1, 1]]。

2.3 代码实现

def permuteUnique(nums):
    nums.sort()  # 排序输入数组
    res = []  # 存储结果的列表
    used = [False] * len(nums)  # 标记元素是否被使用的列表
    
    def backtrack(combination):
        if len(combination) == len(nums):  # 如果组合长度等于数组长度,说明找到了一个全排列
            res.append(combination[:])  # 将当前组合加入结果
            return
        
        for i in range(len(nums)):
            if used[i] or (i > 0 and nums[i] == nums[i-1] and not used[i-1]):
                continue  # 如果元素已被使用或者与前一个元素相同且前一个元素未被使用,则跳过该元素
            
            used[i] = True  # 标记当前元素为已使用
            combination.append(nums[i])  # 将当前元素加入组合
            backtrack(combination)  # 递归调用
            used[i] = False  # 回溯时,将当前元素标记为未使用
            combination.pop()  # 回溯时,将当前元素从组合中移除
    
    backtrack([])
    return res

# 示例输入
nums1 = [1, 1, 2]
nums2 = [1, 2, 3]

# 调用函数,输出结果
print(permuteUnique(nums1))
print(permuteUnique(nums2))

2.4 复杂度分析

(1)时间复杂度:该算法的时间复杂度取决于生成的全排列数量,即 O(n*n!),其中 n 是输入数组的长度。

(2)空间复杂度:该算法的空间复杂度主要取决于存储结果的列表 res 和标记数组 used,因此空间复杂度为 O(n*n!)。

2.5 运行结果

与预期结果均保持一致

三、N皇后问题

力扣第52题

本题依旧采用回溯的思想解题

3.1 具体思路

对于 n 皇后问题,我们可以使用一个数组 queens 来表示每个皇后所在的列位置。数组的索引表示行位置,该索引处的值表示该行皇后所在的列位置。

详细过程如下

首先定义一个计数器 count,用于记录符合条件的解决方案数量。

定义一个辅助函数 backtrack(row, queens) 来递归地生成解决方案。

·如果 row 等于 n,说明所有行的皇后都已放置完毕,此时将 count 加一并返回。

·否则,遍历当前行的每一列位置,依次尝试放置皇后。

如果当前位置不与之前已放置的皇后冲突(不在同一列、同一对角线上)

则将当前位置加入 queens 数组,并递归调用 backtrack(row+1, queens) 进行下一行的放置。

放置完毕后,需要将 queens 数组回溯到之前的状态,以便尝试其他的位置。

在主函数中调用 backtrack(0, []) 开始生成解决方案。

返回计数器 count,即不同的解决方案数量。

3.2 思路展示

初始状态 queens = [-1, -1, -1, -1]

               0   1   2   3

       ---------------------

   0  |  Q

   1  | 

   2  | 

   3  | 

第一步:放置第一个皇后(在第 0 行)

尝试在第 0 行的每一列位置放置皇后,并递归进入下一行:

放置皇后在 (0, 0) 位置后,状态 queens = [0, -1, -1, -1]

               0   1   2   3

       ---------------------

   0  |  Q

   1  | 

   2  | 

   3  | 

第二步:放置第二个皇后(在第 1 行)

尝试在第 1 行的每一列位置放置皇后,并递归进入下一行:

放置皇后在 (1, 2) 位置后,状态 queens = [0, 2, -1, -1]

               0   1   2   3

       ---------------------

   0  |  Q

   1  |     Q

   2  | 

   3  | 

第三步:放置第三个皇后(在第 2 行)

尝试在第 2 行的每一列位置放置皇后,并递归进入下一行:

放置皇后在 (2, 1) 位置后,状态 queens = [0, 2, 1, -1]

               0   1   2   3

       ---------------------

   0  |  Q

   1  |     Q

   2  |  Q

   3  | 

第四步:放置第四个皇后(在第 3 行)

尝试在第 3 行的每一列位置放置皇后,并递归进入下一行:

放置皇后在 (3, 3) 位置后,状态 queens = [0, 2, 1, 3]

               0   1   2   3

       ---------------------

   0  |  Q

   1  |         Q

   2  |  Q

   3  |               Q

这个时候找到了一个解

然后程序回溯到第三步,将queens[2]=1 去掉,再寻找第四部的位置,依次类推可求出结果。

3.3 代码实现

def totalNQueens(n):
    count = 0

    def backtrack(row, queens):
        nonlocal count
        if row == n:
            count += 1
            return

        for col in range(n):
            if is_valid(row, col, queens):
                queens.append(col)
                backtrack(row + 1, queens)
                queens.pop()

    def is_valid(row, col, queens):
        for i in range(row):
            if col == queens[i] or row - i == abs(col - queens[i]):
                return False
        return True

    backtrack(0, [])
    return count


# 示例输入
n = 4
# 调用函数,输出结果
print(totalNQueens(n))
m = 1
# 调用函数,输出结果
print(totalNQueens(m))

3.4 复杂度分析

(1)时间复杂度: 在最坏的情况下,需要遍历整个棋盘的所有位置,因此时间复杂度为 O(n^2)。回溯算法的时间复杂度通常是指数级别的,但由于该问题的特殊性,回溯过程中每行只能放置一个皇后,因此可以将时间复杂度简化为 O(n^2)。

(2)空间复杂度: 空间复杂度取决于递归调用的层数,最多不会超过 n。每次递归调用都会创建一个 queens 数组,空间复杂度为 O(n)。因此,总的空间复杂度为 O(n)。

3.5 运行结果

与n=4和n=2的结果保持一致

四、子集II

力扣第90题

本题采用回溯的思想

4.1 具体思路

首先对数组进行排序,这样重复元素会相邻排列。

创建一个辅助函数 backtrack,该函数接收当前位置索引 start、当前正在构建的子集 subset 和最终结果集 result 作为参数。

在 backtrack 函数中,首先将当前子集 subset 加入到结果集 result 中。

然后从 start 开始遍历数组 nums:

如果当前索引 i 大于 start,并且 nums[i] 等于 nums[i-1],则跳过该元素,避免重复。

否则,将 nums[i] 加入到子集 subset 中,并以 i+1 为起始位置递归调用 backtrack。

在递归完成后,将最后一个加入的元素从子集 subset 中移除,以便尝试其他可能性。

最后返回结果集 result。

4.2 思路展示

针对示例数组 [1,2,2] 进行回溯的过程示意图:

初始状态:subset = [], result = []

回溯到第一层:

subset = [],result = [[]]

    - 加入空子集 [] 到结果集

回溯到第二层:

subset = [1],result = [[], [1]]

    - 加入子集 [1] 到结果集

回溯到第三层:

subset = [1, 2],result = [[], [1], [1, 2]]

    - 加入子集 [1, 2] 到结果集

回溯到第四层:

subset = [1, 2, 2],result = [[], [1], [1, 2], [1, 2, 2]]

    - 加入子集 [1, 2, 2] 到结果集

回溯到第五层:

subset = [1, 2],result = [[], [1], [1, 2], [1, 2, 2]]

    - 由于 nums[i] == nums[i-1],跳过该元素,避免重复

回溯到第六层:

subset = [1],result = [[], [1], [1, 2], [1, 2, 2]]

    - 由于 nums[i] == nums[i-1],跳过该元素,避免重复

回溯到第七层:

subset = [],result = [[], [1], [1, 2], [1, 2, 2], [2]]

    - 加入子集 [2] 到结果集

回溯到第八层:

subset = [2, 2],result = [[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]

    - 加入子集 [2, 2] 到结果集

回溯到第九层:

subset = [2],result = [[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]

    - 由于 nums[i] == nums[i-1],跳过该元素,避免重复

回溯结束

最终结果:[[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]

4.3 代码实现

def subsetsWithDup(nums):
    nums.sort()  # 对数组排序
    result = []
    
    def backtrack(start, subset):
        result.append(subset[:])  # 将当前子集加入结果集
        
        for i in range(start, len(nums)):
            if i > start and nums[i] == nums[i-1]:  # 避免重复
                continue
            subset.append(nums[i])  # 将当前元素加入子集
            backtrack(i + 1, subset)  # 递归调用
            subset.pop()  # 回溯,将最后一个元素移除
    
    backtrack(0, [])
    return result

# 示例输入
nums1 = [1, 2, 2]
nums2 = [0]
# 调用函数,输出结果
print(subsetsWithDup(nums1))  # 输出示例1的结果
print(subsetsWithDup(nums2))  # 输出示例2的结果

4.4 复杂度分析

(1)时间复杂度:在最坏情况下,由于要生成所有可能的子集,时间复杂度为O(2^n),其中n是输入数组的长度。另外由于排序操作的时间复杂度为O(nlogn),因此总体时间复杂度为O(nlogn + 2^n)。

(2)空间复杂度:递归调用会占用一定的栈空间,因此空间复杂度为O(n),其中n是输入数组的长度。此外,存储结果集的空间复杂度也为O(2^n),因为子集的个数是指数级别的。

4.5 运行结果

结尾语

好好学习,天天向上 

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

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

相关文章

两个重要极限【高数笔记】

【第一个&#xff1a;lim &#xff08;sinx / x&#xff09; 1, x -- > 0】 1.本质&#xff1a; lim &#xff08;sin‘&#xff1f;’ / ‘&#xff1f;’&#xff09; 1, ‘&#xff1f;’ -- > 0&#xff1b;保证‘&#xff1f;’ -- > 0,与趋向无关 2.例题&#x…

Harbor介绍、整体架构和安装

1.Harbor介绍 Harbor 是由 VMware 开源的一款云原生制品仓库&#xff0c;Harbor 的核心功能是存储和管理 Artifact。Harbor 允许用户用命令行工具对容器镜像及其他 Artifact 进行推送和拉取&#xff0c;并提供了图形管理界面帮助用户查看和管理这些 Artifact。在 Harbor 2.0 版…

LangChain 81 LangGraph 从入门到精通三

LangChain系列文章 LangChain 60 深入理解LangChain 表达式语言23 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 61 深入理解LangChain 表达式语言24 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 62 深入理解Lang…

Logback学习

logback 1、logback介绍 Logback是由log4j创始人设计的另一个开源日志组件&#xff0c;性能比log4j要好。 lockback优点&#xff1a; 内核重写、测试充分、初始化内存加载更小&#xff0c;这一切让logback性能和log4j相比有诸多倍的提升。logback非常自然地直接实现了slf4j…

Open3d计算点云法向量,可视化(代码)

Open3d使用estimate_normals函数来计算法向量。其参数设置Open3d提供了3中参数搜索的方法&#xff08;所有计算的法向量模长为1&#xff09;&#xff1a; open3d.geometry.KDTreeSearchParamKNN(knn20) # 计算近邻的20个点 open3d.geometry.KDTreeSearc…

SVDiff: Compact Parameter Space for Diffusion Fine-Tuning——【论文笔记】

本文发表于ICCV 2023 论文地址&#xff1a;ICCV 2023 Open Access Repository (thecvf.com) 官方代码&#xff1a;mkshing/svdiff-pytorch: Implementation of "SVDiff: Compact Parameter Space for Diffusion Fine-Tuning" (github.com) 一、Introduction 最近几…

Apache POl Excel

目录 介绍 Apache POl的应用场景&#xff1a; 入门使用 通过POI创建Excel文件并且写入文件内容 通过POI读取Excel文件中的内容 介绍 Apache POl是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用POI在Java程序中对Miscrosoft O…

AI应用开发-Visual Studio Code及Remote Development插件远程开发

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…

由vscode自动升级导致的“终端可以ssh服务器,但是vscode无法连接服务器”

问题描述 简单来说就是&#xff0c;ssh配置没动&#xff0c;前两天还可以用vscode连接服务器&#xff0c;今天突然就连不上了&#xff0c;但是用本地终端ssh可以顺利连接。 连接情况 我的ssh配置如下&#xff1a; Host gpu3HostName aaaUser zwx现在直接在终端中进行ssh&am…

【数位dp】【动态规划】【KMP】1397. 找到所有好字符串

作者推荐 【动态规划】【字符串】【表达式】2019. 解出数学表达式的学生分数 本文涉及知识点 动态规划汇总 LeetCode1397. 找到所有好字符串 给你两个长度为 n 的字符串 s1 和 s2 &#xff0c;以及一个字符串 evil 。请你返回 好字符串 的数目。 好字符串 的定义为&#x…

从零开始:构建高效的 JMeter 集群压测环境

当面对大量用户模拟和性能测量需求时&#xff0c;单台计算机运行 JMeter 往往显得力不从心。因此&#xff0c;构建一个多节点的JMeter集群成为了一种提升测试性能的有效途径。接下来&#xff0c;本文将详细介绍如何组建和配置一个JMeter测试集群。 一、准备工作&#xff1a;服…

深入理解直接内存和零拷贝

目录 直接内存深入辨析 堆外内存的优点和缺点 零拷贝 什么是零拷贝? Linux的I/O机制与DMA 传统数据传送机制 Linux支持的零拷贝 mmap内存映射 sendfile splice Java生态圈中的零拷贝 NIO提供的内存映射MappedByteBuffer NIO提供的sendfile Kafka中的零拷贝 直接…

npm ERR! code CERT_HAS_EXPIRED

执行npm i报错&#xff1a; npm ERR! code ETIMEDOUT npm ERR! syscall connect npm ERR! errno ETIMEDOUT npm ERR! network request to https://registry.npmjs.org/react-redux failed, reason: connect ETIMEDOUT 104.16.2.35:443 npm ERR! network This is a problem rel…

LangChain 79 LangGraph 从入门到精通一

LangChain系列文章 LangChain 60 深入理解LangChain 表达式语言23 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 61 深入理解LangChain 表达式语言24 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 62 深入理解Lang…

Python爬虫Scrapyd项目部署详细教程--最完整版本

文章目录 scrapy项目部署1.scrapyd部署工具介绍&#xff08;1&#xff09;环境安装 2.scrapy项目部署&#xff08;1&#xff09;配置需要部署的项目&#xff08;2&#xff09;管理scrapy项目&#xff08;3&#xff09;启动项目&#xff08;4&#xff09;关闭项目&#xff08;5&…

【Transformer 】 Hugging Face手册-推理管道 (04/10)

一、说明 这里是Hugging Face手册第四部分&#xff0c;如何使用推理管道&#xff1b;即使您没有特定模式的经验或不熟悉模型背后的底层代码&#xff0c;您仍然可以使用它们通过 pipeline ()进行推理&#xff01; 二、推理管道 pipeline ()可以轻松使用Hub中的任何模型来推理任…

Go语言的100个错误使用场景(11-20)|项目组织和数据类型

前言 大家好&#xff0c;这里是白泽。 《Go语言的100个错误以及如何避免》 是最近朋友推荐我阅读的书籍&#xff0c;我初步浏览之后&#xff0c;大为惊喜。就像这书中第一章的标题说到的&#xff1a;“Go: Simple to learn but hard to master”&#xff0c;整本书通过分析100…

Channel事件管理类实现(模块三)

目录 类功能 类定义 类实现 类功能 因为涉及到后续一些实现&#xff0c;因此后续可能会进行修改 类定义 class Channel { private:uint32_t _events; // 当前需要监控的事件uint32_t _revents; // 当前连接触发的事件using EventCallback std::function<void()>;E…

自学Java的第58,59天

网络通信 网络通信三要素&#xff1a;ip地址&#xff0c;端口号&#xff0c;协议 ip地址 常用方法 写法 端口号 协议 UDP通信 快速入门 写法&#xff08;客户端&#xff09; &#xff08;服务端&#xff09; UDP通信 多发多收 TCP通信 写法&#xff08;客户端&#xff09; …

Node.js版本管理工具之_Volta

Node.js包管理工具之_Volta 文章目录 Node.js包管理工具之_Volta1. 官网1. 官网介绍2. 特点1. 快( Fast)2. 可靠(Reliable)3. 普遍( Universal) 2. 下载与安装1. 下载2. 安装3. 查看 3. 使用1. 查看已安装的工具包2. 安装指定的node版本3.切换项目中使用的版本 1. 官网 1. 官网…