[leetcode·回溯算法]回溯算法解题套路框架

本文参考labuladong算法笔记[回溯算法解题套路框架 | labuladong 的算法笔记]

本文解决几个问题:

回溯算法是什么?解决回溯算法相关的问题有什么技巧?如何学习回溯算法?回溯算法代码是否有规律可循?

其实回溯算法和我们常说的 DFS 算法基本可以认为是同一种算法,它们的细微差异我在 关于 DFS 和回溯算法的若干问题 中有详细解释,本文聚焦回溯算法,不展开。

抽象地说,解决一个回溯问题,实际上就是遍历一棵决策树的过程,树的每个叶子节点存放着一个合法答案。你把整棵树遍历一遍,把叶子节点上的答案都收集起来,就能得到所有的合法答案

站在回溯树的一个节点上,你只需要思考 3 个问题:

1、路径:也就是已经做出的选择。

2、选择列表:也就是你当前可以做的选择。

3、结束条件:也就是到达决策树底层,无法再做选择的条件。

如果你不理解这三个词语的解释,没关系,我们后面会用「全排列」这个经典的回溯算法问题来帮你理解这些词语是什么意思,现在你先留着印象。

回溯算法框架:

result = []
def backtrack(路径, 选择列表):if 满足结束条件:result.add(路径)returnfor 选择 in 选择列表:做选择backtrack(路径, 选择列表)撤销选择

其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」,特别简单

全排列问题解析

力扣第 46 题「全排列」就是给你输入一个数组 nums,让你返回这些数字的全排列。

46. 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

我们这次讨论的全排列问题不包含重复的数字,包含重复数字的扩展场景我在后文 回溯算法秒杀排列组合子集的九种题型 中讲解

另外,有些读者之前看过的全排列算法代码可能是那种 swap 交换元素的写法,和我在本文介绍的代码不同。这是回溯算法两种穷举思路,我会在后文 球盒模型:回溯算法穷举的两种视角 讲明白。现在还不适合直接跟你讲那个解法,你照着我的思路学习即可。

我们在高中的时候就做过排列组合的数学题,我们也知道 n 个不重复的数,全排列共有 n! 个。那么我们当时是怎么穷举全排列的呢?

比方说给三个数 [1,2,3],你肯定不会无规律地乱穷举,一般是这样:

先固定第一位为 1,然后第二位可以是 2,那么第三位只能是 3;然后可以把第二位变成 3,第三位就只能是 2 了;然后就只能变化第一位,变成 2,然后再穷举后两位……

其实这就是回溯算法,我们高中无师自通就会用,或者有的同学直接画出如下这棵回溯树:

只要从根遍历这棵树,记录路径上的数字,其实就是所有的全排列。我们不妨把这棵树称为回溯算法的「决策树」

为啥说这是决策树呢,因为你在每个节点上其实都在做决策。比如说你站在下图的红色节点上:

你现在就在做决策,可以选择 1 那条树枝,也可以选择 3 那条树枝。为啥只能在 1 和 3 之中选择呢?因为 2 这个树枝在你身后,这个选择你之前做过了,而全排列是不允许重复使用数字的。

现在可以解答开头的几个名词:[2] 就是「路径」,记录你已经做过的选择;[1,3] 就是「选择列表」,表示你当前可以做出的选择;「结束条件」就是遍历到树的底层叶子节点,这里也就是选择列表为空的时候

如果明白了这几个名词,可以把「路径」和「选择」列表作为决策树上每个节点的属性,比如下图列出了几个蓝色节点的属性:

我们定义的 backtrack 函数其实就像一个指针,在这棵树上游走,同时要正确维护每个节点的属性,每当走到树的底层叶子节点,其「路径」就是一个全排列

再进一步,如何遍历一棵树?这个应该不难吧。回忆一下之前 学习数据结构的框架思维 写过,各种搜索问题其实都是树的遍历问题,而多叉树的遍历框架就是这样:

def traverse(root: TreeNode):for child in root.children:# 前序位置需要的操作traverse(child)# 后序位置需要的操作

而所谓的前序遍历和后序遍历,他们只是两个很有用的时间点,我给你画张图你就明白了:

前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行

回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确处理节点的属性,那么就要在这两个特殊时间点搞点动作:

现在,你是否理解了回溯算法的这段核心框架?

for 选择 in 选择列表:# 做选择将该选择从选择列表移除路径.add(选择)backtrack(路径, 选择列表)# 撤销选择路径.remove(选择)将该选择再加入选择列表

我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。

Python解法代码:

from typing import Listclass Solution:def __init__(self):self.res = []# 主函数,输入一组不重复的数字,返回它们的全排列def permute(self, nums: List[int]) -> List[List[int]]:# 记录「路径」track = []# 「路径」中的元素会被标记为 true,避免重复使用used = [False] * len(nums)self.backtrack(nums, track, used)return self.res# 路径:记录在 track 中# 选择列表:nums 中不存在于 track 的那些元素(used[i] 为 false)# 结束条件:nums 中的元素全都在 track 中出现def backtrack(self, nums: List[int], track: List[int], used: List[bool]):# 触发结束条件if len(track) == len(nums):self.res.append(track.copy())returnfor i in range(len(nums)):# 排除不合法的选择if used[i]: # nums[i] 已经在 track 中,跳过continue# 做选择track.append(nums[i])used[i] = True# 进入下一层决策树self.backtrack(nums, track, used)# 取消选择track.pop()used[i] = False

我们这里稍微做了些变通,没有显式记录「选择列表」,而是通过 used 数组排除已经存在 track 中的元素,从而推导出当前的选择列表:

至此,我们就通过全排列问题详解了回溯算法的底层原理。当然,这个算法解决全排列不是最高效的,你可能看到有的解法连 used 数组都不使用,通过交换元素达到目的。但是那种解法稍微难理解一些,我会在 球盒模型:回溯算法两种穷举视角 中介绍。

但是必须说明的是,不管怎么优化,都符合回溯框架,而且时间复杂度都不可能低于 O(N!),因为穷举整棵决策树是无法避免的,你最后肯定要穷举出 N! 种全排列结果。

这也是回溯算法的一个特点,不像动态规划存在重叠子问题可以优化,回溯算法就是纯暴力穷举,复杂度一般都很高

最后总结

回溯算法就是个多叉树的遍历问题,关键就是在前序遍历后序遍历的位置做一些操作。

写 backtrack 函数时,需要维护走过的「路径」和当前可以做的「选择列表」,当触发「结束条件」时,将「路径」记入结果集

其实想想看,回溯算法和动态规划是不是有点像呢?我们在动态规划系列文章中多次强调,动态规划的三个需要明确的点就是「状态」「选择」和「base case」,是不是就对应着走过的「路径」,当前的「选择列表」和「结束条件」?

动态规划和回溯算法底层都把问题抽象成了树的结构,但这两种算法在思路上是完全不同的。

leetcode练习

数独游戏

大家应该都玩过数独游戏,就是给你一个 9x9 的棋盘,其中有一些格子预先填入了数字,让你在其余空的格子中填入数字 1~9,要求每行、每列和每个 3x3 的九宫格内数字都不能重复。

下面是一个数独游戏的例子(来源 Wikipedia):

我小的时候也尝试过玩数独游戏,但只要稍微有些难度,就搞不定了。后来我才知道做数独是有技巧的,有一些比较专业的数独游戏软件会教你玩数独的技巧,不过在我看来这些技巧都太复杂,根本就没有兴趣看下去。

现在学习了回溯算法,多困难的数独问题都拦不住我了。只要有规则,就一定可以暴力穷举出符合条件的答案来,不是吗?

N 皇后问题

在国际象棋中,皇后可以攻击同一行、同一列和同一条对角线上的任意单位。N 皇后问题是指在一个 N×N 的棋盘上摆放 N 个皇后,要求任何两个皇后之间都不能互相攻击。

换句话说,就是让你在一个 N×N 的棋盘上放置 N 个皇后,使得每行、每列和每个对角线都只有一个皇后。

比如这是 8 皇后问题的一个解(来源 Wikipedia):

可以看到,对于任意一个皇后,它所在的行、列和对角线(左上、右上、左下、右下)都没有其他皇后,所以这就是一个符合规则的解。

在讲上述题目之前,我需要先讲一道比较简单的回溯算法问题,把这个问题作为铺垫,就能更容易理解数独游戏和 N 皇后问题的解法了。

n 位二进制数的所有可能

我来给你编一道简单的题目,请你实现这样一个函数:

def generateBinaryNumber(n: int) -> List[str]:

函数的输入是一个正整数 n,请你返回所有长度为 n 的二进制数(0、1 组成),你可以按任意顺序返回答案。

比如说 n = 3,那么你需要以字符串形式返回如下 2^3=8种不同的二进制数:

000
001
010
011
100
101
110
111

这道题可以认为是数独游戏和 N 皇后问题的简化版:

这道题相当于让你对一个长度为 n 的一维数组中的每一个位置进行穷举,其中每个位置可以填 0 或 1本质还是遍历一颗二叉树。

数独游戏相当于让你对一个 9x9 的二维数组中的每个位置进行穷举,其中每个位置可以是数字 1~9,且同一行、同一列和同一个 3x3 的九宫格内数字不能重复。本质是遍历一颗九叉树。

N 皇后问题相当于让你对一个 N x N 的二维数组中的每个位置进行穷举,其中每个位置可以不放皇后或者放置皇后(相当于 0 或 1),且不能存在多个皇后在同一行、同一列或同一对角线上。本质是遍历一颗N叉树。

所以,只要你把这道简化版的题目的穷举过程搞明白,其他问题都迎刃而解了,无非是规则多了一些而已。

思路分析

题目是对一个长度为 n 的数组进行穷举,每个位置可以填入 0 或 1,让你返回所有可能的结果。

Important

看到这种题目,脑海中应该立刻出现一棵递归树;有了这棵树,你就有办法使用 回溯算法框架 无遗漏地穷举所有可能的解;能无遗漏地穷举所有解,就一定能找到符合题目要求的答案

具体来说,这道题的递归树应该是一棵 n + 1 层的二叉树。

递归树的节点就是穷举过程中产生的状态,以不同的视角来看这些节点,会有不同的作用。

具体来说就是以下问题:

应该如何理解这棵树每一层的节点;应该如何理解这棵树每一列(每条树枝)的节点;应该如何理解每个节点本身

你可以把鼠标移动到可视化面板生成的这棵递归树的每个节点上,会显示节点和树枝的信息。请你结合可视化面板的这些信息,听我来分析:

1、从层的角度来看,第 i 层的节点表示的就是长度为 i 的所有二进制数(i 从 0 开始算),比如第 2 层的节点分别是 00011011,这些就是长度为 2 的所有二进制数。

2、从列(树枝)的角度来看,每条树枝上的节点记录了状态转移的过程。比如你把鼠标移动到 001 这个节点上,树枝上会显示这个 001 是如何被穷举出来的,即首先选择 0,然后选择 0,最后选择 1

3、从节点的角度来看,每个节点都是一个状态,从该节点向下延伸的树枝,就是当前状态的所有可能的选择。比如第二层的节点 01,它的前两位已经确定,现在要对第三位进行穷举。第三位可以是 0 或 1,所以从 01 这个节点向下延伸的两个子节点就是 010 和 011

回溯算法代码框架

class Solution:# 生成所有长度为 n 的二进制数def generateBinaryNumber(self, n: int) -> List[str]:res = []# 回溯路径track = []# 回溯算法核心框架def backtrack(i):if i == n:# 当递归深度等于 n 时,说明找到一个长度为 n 的二进制数,结束递归res.append(''.join(track))return# 选择有两种,0 或 1# 所以递归树是一棵二叉树for val in [0, 1]:# 做选择track.append(str(val))backtrack(i + 1)# 撤销选择track.pop()backtrack(0)return res

37. 解数独 | 力扣 | LeetCode |  🔴

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:

提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

先不管数独游戏的规则,反正现在就是有 9x9=81 个格子,每个格子都可以填入 1~9 对吧,那总共就有 981981 种填法,把这些解法都穷举一遍,一定可以找到符合规则的答案。

对于这个问题,你心里应该抽象出一棵九叉递归树,共有 81 层,只要心里有了这棵树,就可以参照前面实现的 generateBinaryNumber 函数写出求解数独游戏的代码了。

不过你可能会有以下疑惑:

1、前面 generateBinaryNumber 是针对一维数组的穷举,如何才能适配到数独游戏的二维数组呢?

2、981981 是一个非常大的数字,穷举这么多递归树节点,即便写出回溯算法代码,也会超时吧?

3、generateBinaryNumber 函数返回的是所有解,而数独题目只要求找到一个可行解,所以当我们找到一个可行解时就应该结束算法,这样可以减少时间复杂度。

我来一一解答。

对于第一个问题,不熟悉递归的读者可能会有这个疑问,是不是在二维数组上递归就把你绕晕了?那么我们干脆玩个花样,直接绕过二维数组的问题,因为不管是几维的数组,都可以转化成一维数组

说白了,就是设计一个编解码算法,能够把一个多维坐标(多元组)编码到一个一维坐标(数字),且可以反向解码回去。

对于一个 MxN 的二维数组,可以把二维坐标 (i, j) 映射到一维坐标 index = i * N + j,反之可以通过 i = index / N 和 j = index % N 得到原来的二维坐标。

有了这个编解码算法,数独游戏的 9x9 二维数组就可以在逻辑上看做一个长度为 81 的一维数组,然后按照一维数组的递归树来穷举即可。

对于第二个问题,981981 只是一个非常粗略的估算,实际写代码时,要考虑数独的规则。

首先,初始棋盘会给你预先填充一些数字,这些数字是不能改变的,所以这些格子不用穷举,应该从 81 个格子去除。

另外,数独规则要求每行、每列和每个 3x3 的九宫格内数字都不能重复,所以实际上每个空格子可以填入的数字并不是真的有 9 种选择,而是要根据当前棋盘的状态,去除不合法的数字(剪枝)。

而且,981981 是穷举所有可行解的复杂度估算,实际上题目只要求我们寻找一个可行解,所以只要找到一个可行解就可以结束算法了,没必要穷举所有解。

那么这样算下来,穷举的实际复杂度取决于空格子的个数,远低于 981981,所以不用担心超时问题。

讲到这里,我们应该可以发现一个有趣的现象:

按照人类的一般理解,规则越多,问题越难;但是对于计算机来说,规则越多,问题反而越简单。

因为它要穷举嘛,规则越多,越容易剪枝,搜索空间就越小,就越容易找到答案。反之,如果没有任何规则,那就是纯暴力穷举,效率会非常低。

我们对回溯算法的剪枝优化,本质上就是寻找规则,提前排除不合法的选择,从而提高穷举的效率。

对于第三个问题,让算法在找到一个可行解时立即结束递归。这个问题有多种解决方案,我会建议使用一个全局变量来标记是否找到可行解(原因见 解答回溯/DFS的若干问题),辅助递归算法提前终止。

回溯算法代码实现:

class Solution:# 标记是否已经找到可行解found = Falsedef solveSudoku(self, board):self.backtrack(board, 0)# 路径:board 中小于 index 的位置所填的数字# 选择列表:数字 1~9# 结束条件:整个 board 都填满数字def backtrack(self, board, index):if self.found:# 已经找到一个可行解,立即结束returnm, n = 9, 9i, j = index // n, index % nif index == m * n:# 找到一个可行解,触发 base caseself.found = Truereturnif board[i][j] != '.':# 如果有预设数字,不用我们穷举self.backtrack(board, index + 1)returnfor ch in '123456789':# 剪枝:如果遇到不合法的数字,就跳过if not self.isValid(board, i, j, ch):continue# 做选择board[i][j] = chself.backtrack(board, index + 1)if self.found:# 如果找到一个可行解,立即结束# 不要撤销选择,否则 board[i][j] 会被重置为 '.'return# 撤销选择board[i][j] = '.'# 判断是否可以在 (r, c) 位置放置数字 numdef isValid(self, board, r, c, num):for i in range(9):# 判断行是否存在重复if board[r][i] == num:return False# 判断列是否存在重复if board[i][c] == num:return False# 判断 3 x 3 方框是否存在重复if board[(r // 3) * 3 + i // 3][(c // 3) * 3 + i % 3] == num:return Falsereturn True

51. N 皇后 | 力扣 | LeetCode |  🔴

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4
输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

其实 N 皇后问题和数独游戏的解法思路是一样的,具体有以下差别:

1、数独的规则和 N 皇后问题的规则不同,我们需要修改 isValid 函数,判断一个位置是否可以放置皇后。

2、题目要求找到 N 皇后问题所有解,而不是仅仅找到一个解。我们需要去除算法提前终止的逻辑,让算法穷举并记录所有的合法解。

这两个问题都比较容易解决,按理说可以直接参照数独游戏的代码实现 N 皇后问题。

但 N 皇后问题相对数独游戏还有一个优化:我们可以以为单位进行穷举,而不是像数独游戏那样以格子为单位穷举

举个直观的例子,在数独游戏中,如果我们设置 board[i][j] = 1,接下来呢,要去穷举 board[i][j+1] 了对吧?而对于 N 皇后问题,我们知道每行必然有且只有一个皇后,所以如果我们决定在 board[i] 这一行的某一列放置皇后,那么接下来就不用管 board[i] 这一行了,应该考虑 board[i+1] 这一行的皇后要放在哪里。

所以 N 皇后问题的穷举对象是棋盘中的行,每一行都持有一个皇后,可以选择把皇后放在该行的任意一列。

回溯算法代码实现:

class Solution:def __init__(self):self.res = []def solveNQueens(self, n: int) -> list[list[str]]:# 每个字符串代表一行,字符串列表代表一个棋盘# '.' 表示空,'Q' 表示皇后,初始化空棋盘board = ["." * n for _ in range(n)]self.backtrack(board, 0)return self.res# 路径:board 中小于 row 的那些行都已经成功放置了皇后# 选择列表:第 row 行的所有列都是放置皇后的选择# 结束条件:row 超过 board 的最后一行def backtrack(self, board: list[str], row: int):if row == len(board):# 棋盘已经填满,找到一个解self.res.append(board[:])returnn = len(board[row])for col in range(n):# 剪枝,排除不合法选择if not self.isValid(board, row, col):continue# 做选择board[row] = board[row][:col] + 'Q' + board[row][col+1:]# 进入下一行决策self.backtrack(board, row + 1)# 撤销选择board[row] = board[row][:col] + '.' + board[row][col+1:]# 是否可以在 board[row][col] 放置皇后?def isValid(self, board: list[str], row: int, col: int) -> bool:n = len(board)# 因为我们是从上往下放置皇后的,# 所以只需要检查上方是否有皇后互相冲突,# 不需要检查下方# 检查列是否有皇后互相冲突for i in range(row):if board[i][col] == 'Q':return False# 检查右上方是否有皇后互相冲突i, j = row - 1, col + 1while i >= 0 and j < n:if board[i][j] == 'Q':return Falsei, j = i - 1, j + 1# 检查左上方是否有皇后互相冲突i, j = row - 1, col - 1while i >= 0 and j >= 0:if board[i][j] == 'Q':return Falsei, j = i - 1, j - 1return True

52. N 皇后 II | 力扣 | LeetCode |  🔴

n 皇后问题 研究的是如何将 n 个皇后放置在 n × n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给你一个整数 n ,返回 n 皇后问题 不同的解决方案的数量。

示例 1:

输入:n = 4
输出:2
解释:如上图所示,4 皇后问题存在两个不同的解法。

示例 2:

输入:n = 1
输出:1

提示:

  • 1 <= n <= 9

直接把 res 变量变成 int 类型,每次在 base case 找到一个合法答案的时候递增 res 变量即可:

class Solution:def __init__(self):# 仅仅记录合法结果的数量self.res = 0def backtrack(self, board: list[str], row: int):if row == len(board):# 找到一个合法结果self.res += 1return# 其他都一样

总结

回溯算法本质上就是遍历一颗多叉树:

1、确定递归终止条件

2、执行单层操作逻辑

3、确定下一层递归所需的操作列表

4、注意剪枝和代码优化

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

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

相关文章

总结11..

#include <stdio.h> #include <string.h> #define MAXN 1001 #define MAXM 1000001 int n, m; char maze[MAXN][MAXN]; int block[MAXN][MAXN]; // 标记每个格子所属的连通块编号 int blockSize[MAXN * MAXN]; // 记录每个连通块的大小 int dx[] {0, 0, 1, -1};…

SQL Server中DENSE_RANK()函数:简洁处理连续排名

什么是DENSE_RANK&#xff1f; DENSE_RANK()是SQL Server中的窗口函数&#xff0c;用于为结果集中的行生成无间隔的连续排名。与RANK()不同&#xff0c;当遇到相同值时&#xff0c;后续排名不会跳过数字。前一篇已经介绍了rank的用法&#xff0c;这次介绍一下dense_rank。 DEN…

【Go语言圣经】第七节:接口

第七章&#xff1a;接口 Golang 当中接口类型的独特之处在于它是满足隐式实现的。即&#xff1a;没必要对于给定的具体类型定义所有满足的接口类型&#xff0c;简单地拥有一些必要的方法即可。这种设计使得我们可以创建一个新的接口类型来满足已经存在的具体类型&#xff0c;却…

【网络】3.HTTP(讲解HTTP协议和写HTTP服务)

目录 1 认识URL1.1 URI的格式 2 HTTP协议2.1 请求报文2.2 响应报文 3 模拟HTTP3.1 Socket.hpp3.2 HttpServer.hpp3.2.1 start()3.2.2 ThreadRun()3.2.3 HandlerHttp&#xff08;&#xff09; 总结 1 认识URL 什么是URI&#xff1f; URI 是 Uniform Resource Identifier的缩写&…

数据分析师使用Kutools for Excel 插件

数据分析师使用Kutools for Excel 插件 Kutools for Excel 是一款功能强大的 Excel 插件&#xff0c;旨在提高 Excel 用户的工作效率&#xff0c;简化复杂的操作。它提供了超过 300 个增强功能&#xff0c;帮助用户快速完成数据管理、格式化、排序、分析等任务&#xff0c;特别…

ElasticStack简介及应用

文章目录 1.Elastic Stack 技术栈2.ES 安装2.1 准备2.2 yum单机部署2.3 集群部署 3.Kibana3.1 安装配置3.2 web访问 4.Filebeat4.1 安装4.2 配置 inputs4.3 配置 output4.4 索引4.5 分片和副本 5.收集nginx日志5.1 原生日志5.2 nginx日志格式5.3 filebeat 配置 6.logstash6.1 安…

解决Mac安装软件的“已损坏,无法打开。 您应该将它移到废纸篓”问题

mac安装软件时&#xff0c;如果出现这个问题&#xff0c;其实很简单 首先打开终端&#xff0c;输入下面的命令 sudo xattr -r -d com.apple.quarantine 输入完成后&#xff0c;先不要回车&#xff0c;点击访达--应用程序--找到你无法打开的app图标&#xff0c;拖到终端窗口中…

pytorch实现长短期记忆网络 (LSTM)

人工智能例子汇总&#xff1a;AI常见的算法和例子-CSDN博客 LSTM 通过 记忆单元&#xff08;cell&#xff09; 和 三个门控机制&#xff08;遗忘门、输入门、输出门&#xff09;来控制信息流&#xff1a; 记忆单元&#xff08;Cell State&#xff09; 负责存储长期信息&…

后盾人JS--继承

继承是原型的继承 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </hea…

实际操作 检测缺陷刀片

号he 找到目标图像的缺陷位置&#xff0c;首先思路为对图像进行预处理&#xff0c;灰度-二值化-针对图像进行轮廓分析 //定义结构元素 Mat se getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1)); morphologyEx(thre, tc, MORPH_OPEN, se, Point(-1, -1), 1); …

从实数与复数在交流电路正弦量表示中的对比分析

引言 在交流电路领域&#xff0c;深入理解电压和电流等正弦量的表示方式对电路分析至关重要。其中&#xff0c;只用实数表示正弦量存在诸多局限性&#xff0c;而复数的引入则为正弦量的描述与分析带来了极大的便利。下面将从瞬时值角度&#xff0c;详细剖析只用实数的局限性&a…

Python3 OS模块中的文件/目录方法说明十四

一. 简介 前面文章简单学习了 Python3 中 OS模块中的文件/目录的部分函数。 本文继续来学习 OS 模块中文件、目录的操作方法&#xff1a;os.statvfs() 方法&#xff0c;os.symlink() 方法。 二. Python3 OS模块中的文件/目录方法 1. os.statvfs() 方法 os.statvfs() 方法用…

知识蒸馏教程 Knowledge Distillation Tutorial

来自于&#xff1a;Knowledge Distillation Tutorial 将大模型蒸馏为小模型&#xff0c;可以节省计算资源&#xff0c;加快推理过程&#xff0c;更高效的运行。 使用CIFAR-10数据集 import torch import torch.nn as nn import torch.optim as optim import torchvision.tran…

Turing Complete-1位开关

要求如下&#xff1a; 我的思考&#xff1a; 把输入1当作控制信号&#xff0c;把输入2当作输出信号。 通过非门和开关使输入2形成双通道输出&#xff0c; 通道一为输出输入2取反。 通道二为输出输入2本身。 通过输入1来控制两个通道的开闭。

从Transformer到世界模型:AGI核心架构演进

文章目录 引言:架构革命推动AGI进化一、Transformer:重新定义序列建模1.1 注意力机制的革命性突破1.2 从NLP到跨模态演进1.3 规模扩展的黄金定律二、通向世界模型的关键跃迁2.1 从语言模型到认知架构2.2 世界模型的核心特征2.3 混合架构的突破三、构建世界模型的技术路径3.1 …

深度求索DeepSeek横空出世

真正的强者从来不是无所不能&#xff0c;而是尽我所能。多少有关输赢胜负的缠斗&#xff0c;都是直面本心的搏击。所有令人骄傲振奋的突破和成就&#xff0c;看似云淡风轻寥寥数语&#xff0c;背后都是数不尽的焚膏继晷、汗流浃背。每一次何去何从的困惑&#xff0c;都可能通向…

性能优化中的数据过滤优化

目录 以下是一些关于数据过滤优化的策略和方法 索引使用 避免全表扫描 使用分区 数据预处理 合理设计查询 利用缓存机制 数据库层面优化 系统中通常会有一些统计和分析的功能&#xff0c;以前我们主要针对结构化数据&#xff08;关系型数据库存储&#xff09;进行分析&a…

与本地Deepseek R1:14b的第一次交流

本地部署DS的方法&#xff0c;见&#xff1a;本地快速部署DeepSeek-R1模型——2025新年贺岁-CSDN博客 只有16GB内存且没有强大GPU的个人电脑&#xff0c;部署和运行14b参数的DS大模型已是天花板了。 运行模型 ollama run deepseek-r1:14b C:\Users\Administrator>ollama r…

Python 梯度下降法(六):Nadam Optimize

文章目录 Python 梯度下降法&#xff08;六&#xff09;&#xff1a;Nadam Optimize一、数学原理1.1 介绍1.2 符号定义1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码 三、优缺点3.1 优点3.2 缺点 四、相关链接 Python 梯度下降法&#xff08;六&#xff09;&#xff1a;Nad…

【狂热算法篇】探秘图论之Dijkstra 算法:穿越图的迷宫的最短路径力量(通俗易懂版)

羑悻的小杀马特.-CSDN博客羑悻的小杀马特.擅长C/C题海汇总,AI学习,c的不归之路,等方面的知识,羑悻的小杀马特.关注算法,c,c语言,青少年编程领域.https://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_82648291?typebbshttps://blog.csdn.net/2401_8264829…