深度优先搜索(DFS)与回溯法:从全排列到子集问题的决策树与剪枝优化

在这里插入图片描述

文章目录

  • 前言
    • 🎄一、全排列
      • ✨核心思路
      • ✨实现步骤
      • ✨代码
      • ✨时间和空间复杂度
        • 🎁1. 时间复杂度
        • 🎁2. 空间复杂度
    • 🎄二、子集
      • ✨解法一:逐位置决策法
        • 🎁步骤分析
        • 🎁运行示例
        • 🎁代码
      • ✨解法二:逐元素扩展法
        • 🎁步骤分析
        • 🎁运行示例
        • 🎁代码
      • ✨时间复杂度分析
        • 🎁解法一
        • 🎁解法二
      • ✨空间复杂度分析
        • 🎁解法一
        • 🎁解法二
    • 🎄三、找出所有子集的异或总和再求和
      • ✨解法简介
      • ✨解法步骤
        • 🎁Step 1: 初始化变量
        • 🎁Step 2: 回溯递归函数设计
        • 🎁Step 3: 主函数调用
      • ✨代码
      • ✨时间与空间复杂度
        • 🎁时间复杂度
        • 🎁空间复杂度
    • 🎄四、全排列II
      • ✨解法简介
      • ✨步骤详解
        • 🎁Step 1: 初始化
        • 🎁Step 2: 回溯递归函数设计
        • 🎁Step 3: 主函数调用
      • ✨代码
      • ✨时间与空间复杂度
        • 🎁时间复杂度
        • 🎁空间复杂度
  • 结语


前言

深度优先搜索(DFS)和回溯法是解决复杂问题中不可或缺的算法工具,尤其在组合问题(如全排列、子集等)中,发挥着至关重要的作用。通过递归的方式,DFS 能够遍历问题的解空间,而回溯法则通过撤销不合法的选择,避免重复计算,提高效率。在解题过程中,剪枝是优化回溯法的重要手段,它通过提前排除无效路径,进一步减少了运算的复杂度。本文将深入探讨如何使用 DFS、回溯法及剪枝技术,构建解决全排列和子集问题的决策树,并优化算法的执行效率。


🎄一、全排列

题目链接:https://leetcode.cn/problems/permutations/description/

✨核心思路

这段代码实现了生成一个数组的所有排列(Permutation),并返回所有可能的排列列表。采用了**深度优先搜索(DFS)**的方式,配合一个 check 数组来记录哪些元素已经使用过,从而避免重复使用。

✨实现步骤

  1. 初始化变量
    • vector<vector<int>> ret:存储最终结果,包含所有排列。
    • vector<int> path:存储当前正在构造的排列。
    • bool check[7]:标记数组,记录某个位置是否已经被使用,避免重复选择。默认全为 false
  2. 主函数 permute
    • 调用 dfs(nums) 开始进行递归遍历,寻找所有排列。
    • 返回最终结果 ret
  3. 辅助函数 dfs
    • 递归终止条件:当 path 的长度等于 nums.size(),说明已经构造出一个完整的排列,将其加入结果集 ret
    • 循环遍历:
      • 遍历数组的每个元素,判断其是否已经被使用(check[i] == false)。
      • 如果未使用,将其加入当前路径 path,同时更新 check[i] = true
      • 递归调用 dfs(nums),继续构造排列。
      • 回溯:
        • 移除当前路径中的最后一个元素 path.pop_back()
        • 重置 check[i] = false,允许后续尝试其他路径。

如图分析:

在这里插入图片描述

✨代码

class Solution {
public:vector<vector<int>> ret;vector<int> path;bool check[7];vector<vector<int>> permute(vector<int>& nums) {dfs(nums);return ret;}void dfs(vector<int>& nums) {if (path.size() == nums.size()) {ret.push_back(path);return;}for (int i = 0; i < nums.size(); i++) {if (check[i] == false) {path.push_back(nums[i]);check[i] = true;dfs(nums);//  -> ֳָpath.pop_back();check[i] = false;}}}
};

✨时间和空间复杂度

🎁1. 时间复杂度
  • 每个排列需要遍历 nums 的所有元素,同时需要递归构造排列。
  • 全排列的总数为 n ! n! n! ,其中 n 为 nums 的长度。
  • 每次构造排列需要 O ( n ) O(n) O(n) 的时间复杂度,因此总时间复杂度为: O ( n ⋅ n ! ) O(n⋅n!) O(nn!)
🎁2. 空间复杂度
  • 递归深度为 O ( n ) O(n) O(n),对应递归栈的最大深度。
  • 存储结果的 ret 大小为 O ( n ⋅ n ! ) O(n⋅n!) O(nn!)(每个排列长度为 n,共有 n! 个排列)。
  • 额外空间为 pathcheck,均为 $O(n) $。
  • 总空间复杂度为: O ( n ⋅ n ! ) O(n⋅n!) O(nn!)

🎄二、子集

题目链接:https://leetcode.cn/problems/subsets/description/

✨解法一:逐位置决策法

核心思想:对于每个位置,都需要明确“选”或者“不选”两种可能,然后继续对后续位置进行递归。

🎁步骤分析
  1. 输入数组nums = [1, 2, 3]
  2. 决策树构造:
    • 对于每个元素,存在两个分支:选它不选它
    • 递归过程中,我们先“选”再“回溯”,然后“跳过当前元素”。
  3. 递归终止条件:
    • 当递归位置 pos 达到数组末尾(pos == nums.size())时,将当前路径 path 加入结果集 ret
🎁运行示例
  • 初始调用:dfs(nums, 0)
    • 选1:path = [1],递归进入 dfs(nums, 1)
      • 选2:path = [1, 2],递归进入 dfs(nums, 2)
        • 选3path = [1, 2, 3],递归终止,保存结果 [[1, 2, 3]]
        • 回溯到选2状态path = [1, 2]
        • 不选3path = [1, 2],递归终止,保存结果 [[1, 2, 3], [1, 2]]
      • 回溯到选1状态path = [1]
      • 不选2:path = [1],递归进入 dfs(nums, 2)
        • 选3path = [1, 3],递归终止,保存结果 [[1, 2, 3], [1, 2], [1, 3]]
        • 不选3path = [1],递归终止,保存结果 [[1, 2, 3], [1, 2], [1, 3], [1]]
    • 回溯到初始状态path = []
    • 不选1:path = [],递归进入 dfs(nums, 1)
      • 选2path = [2],递归类似上面。
      • 不选2path = [],递归继续处理后续。

最终得到所有子集。

如图分析:

在这里插入图片描述

🎁代码
class Solution {
public:vector<vector<int>> ret;vector<int> path;vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {if (pos == nums.size()) {ret.push_back(path);return;}// 选path.push_back(nums[pos]);dfs(nums, pos + 1);// 回溯 -> 恢复现场path.pop_back();// 不选dfs(nums, pos + 1);}
};

✨解法二:逐元素扩展法

核心思想:直接将当前路径加入结果集,然后从当前位置逐个向后选取元素递归。

🎁步骤分析
  1. 输入数组nums = [1, 2, 3]
  2. 递归机制:
    • 每次递归时,直接将当前路径加入结果集。
    • 从当前位置 pos 开始,依次向后选择元素,尝试递归。
🎁运行示例
  • 初始调用:dfs(nums, 0)
    • 当前路径为空,加入结果集:ret = [[]]
    • pos = 0 开始:
      • 选1:path = [1],加入结果集:ret = [[], [1]],递归进入 dfs(nums, 1)
        • 选2:path = [1, 2],加入结果集:ret = [[], [1], [1, 2]],递归进入 dfs(nums, 2)
          • 选3path = [1, 2, 3],加入结果集:ret = [[], [1], [1, 2], [1, 2, 3]]
          • 回溯到选2状态path = [1, 2]
        • 回溯到选1状态path = [1]
      • 回溯到初始状态path = []
      • 选2:递归同上。
      • 选3:递归继续。

如图分析:

在这里插入图片描述

🎁代码
class Solution {
public:vector<vector<int>> ret;vector<int> path;vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {ret.push_back(path);for (int i = pos; i < nums.size(); i++) {path.push_back(nums[i]);dfs(nums, i + 1);// 回溯 -> 恢复现场path.pop_back();}}
};

✨时间复杂度分析

🎁解法一
  1. 决策树分析
    • 每个元素有两种选择(选或不选),对于长度为 n 的数组,共有 2^n 种子集。
    • 递归的每一步都对应一个子集的生成,因此遍历整个决策树需要 O(2^n) 的时间。
  2. 额外操作
    • 每次递归终止时,将当前路径 path 加入结果,需要 O(n) 的时间(复制 path 的内容)。

总时间复杂度
O ( 2 n ⋅ n ) O(2^n⋅n) O(2nn)

🎁解法二
  1. 决策树分析
    • 每个元素被递归遍历一次,所有可能的子集共 2^n 个。
    • 递归过程中,每次递归都会将当前路径 path 加入结果集 ret
  2. 额外操作
    • 每次将路径加入结果集也需要 O(n) 的时间(复制 path)。

总时间复杂度
O(2n⋅n)


✨空间复杂度分析

🎁解法一
  1. 递归深度
    • 递归调用的最大深度为数组长度 n ,因此递归栈的空间复杂度为 O ( n ) O(n) O(n)
  2. 路径存储
    • path 需要存储当前路径,大小为 O ( n ) O(n) O(n)
  3. 结果集
    • 最终结果集 ret 包含 2 n 2^n 2n 个子集,每个子集的平均长度为 O ( n / 2 ) O(n/2) O(n/2),因此结果集的存储空间为: O ( 2 n ⋅ n ) O(2n⋅n) O(2nn)

总空间复杂度
O ( 2 n ⋅ n ) O(2^n \cdot n) O(2nn)
(递归栈和路径空间较小,可忽略不计)

🎁解法二
  1. 递归深度
    • 递归调用的最大深度为数组长度 n,因此递归栈的空间复杂度为 O ( n ) O(n) O(n)
  2. 路径存储
    • path 需要存储当前路径,大小为 O ( n ) O(n) O(n)
  3. 结果集
    • 同解法一,结果集需要 $O(2^n \cdot n) $的空间。

总空间复杂度
O ( 2 n ⋅ n ) O(2^n \cdot n) O(2nn)
(递归栈和路径空间较小,可忽略不计)

🎄三、找出所有子集的异或总和再求和

题目链接:https://leetcode.cn/problems/sum-of-all-subset-xor-totals/description/

✨解法简介

题目要求求出数组中所有子集的异或和的总和。解法采用 回溯法 (Backtracking),枚举所有子集,同时计算每个子集的异或和,并将其累加到结果中。


✨解法步骤

🎁Step 1: 初始化变量
  • ret:最终结果,用于存储所有子集的异或和的累加值。
  • path:当前路径变量,表示当前子集的异或值。
  • nums:输入数组。

🎁Step 2: 回溯递归函数设计

递归函数 dfs(nums, pos)

  1. 功能
    • 遍历以 pos 为起点的所有可能子集。
    • 累加每个子集的异或值到 ret
  2. 核心逻辑
    • 累加当前路径的异或值到 ret: 每次递归进入时,将当前路径的异或值(path)加入到结果中。
    • 枚举后续元素: 从当前 pos 开始,逐一尝试将后续元素加入路径 path,并递归处理。
    • 回溯: 在递归返回后,撤销当前选择(通过异或运算恢复 path 的值)。
  3. 递归终止条件
    • 无需显式终止条件,因为递归自然会遍历所有子集,当 pos 超出数组范围时,循环自动结束。

🎁Step 3: 主函数调用

在主函数 subsetXORSum 中:

  1. 初始化结果变量 ret = 0
  2. 调用回溯函数:dfs(nums, 0),从数组的第 0 个位置开始递归。
  3. 返回最终结果 ret

如图分析:

在这里插入图片描述

✨代码

class Solution {
public:int ret = 0;int path = 0;int subsetXORSum(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos){ret += path;for(int i = pos; i < nums.size(); i++){path ^= nums[i];dfs(nums, i + 1);// 回溯path ^= nums[i];}}
};

✨时间与空间复杂度

🎁时间复杂度
  1. 子集数量:对于数组长度为 n n n,共有 2 n 2^n 2n 个子集。
  2. 异或计算:每次递归执行异或操作的时间复杂度为 O ( 1 ) O(1) O(1)
  3. 总复杂度 O ( 2 n ) O(2^n) O(2n)
🎁空间复杂度
  1. 递归深度:递归调用栈的最大深度为 n n n
  2. 路径变量path 是一个整数,占用 O ( 1 ) O(1) O(1) 的空间。
  3. 总复杂度 O ( n ) O(n) O(n)

🎄四、全排列II

题目链接:https://leetcode.cn/problems/permutations-ii/description/

✨解法简介

利用 回溯法 (Backtracking) 生成所有排列,通过排序和剪枝(跳过重复元素)来避免生成重复排列。


✨步骤详解

🎁Step 1: 初始化
  1. 变量说明
    • ret:存储所有结果的二维数组。
    • path:当前路径,用于构造一个排列。
    • check:布尔数组,记录每个元素是否已经被使用过。
  2. 预处理
    • 对输入数组 nums 进行排序:sort(nums.begin(), nums.end())
      • 排序的目的是将相同的元素放在一起,方便后续剪枝操作。

🎁Step 2: 回溯递归函数设计

递归函数 dfs(nums, pos)

  1. 递归终止条件
    • 如果当前路径 path 的大小等于数组 nums 的长度,说明构造了一组完整排列,将其加入结果集 ret
  2. 循环遍历每个元素
    • 遍历 nums 中的每个元素,尝试将未被使用的元素加入当前路径。
  3. 剪枝条件
    • 元素未被使用:通过 check[i] == false 判断当前元素是否可用。
    • 跳过重复元素:
      • nums[i] == nums[i - 1]:当前元素与前一个元素相同。
      • check[i - 1] == false:前一个相同的元素尚未被使用,说明该排列已经处理过,直接跳过当前分支。
  4. 递归和回溯
    • 递归调用:
      • 将当前元素加入路径,标记为已使用。
      • 递归处理下一个位置。
    • 回溯操作:
      • 递归返回后,将当前元素移出路径,标记为未使用,恢复状态。

🎁Step 3: 主函数调用

在主函数 permuteUnique 中:

  1. 对数组排序:sort(nums.begin(), nums.end())
  2. 初始化布尔数组:check 的大小为 nums.size(),初始值为 false
  3. 调用回溯函数:dfs(nums, 0)
  4. 返回结果集 ret

如图分析:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

✨代码

class Solution {
public:vector<vector<int>> ret;vector<int> path;bool check[9];vector<vector<int>> permuteUnique(vector<int>& nums) {// 先排序sort(nums.begin(), nums.end());dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos) {if (path.size() == nums.size()) {ret.push_back(path);return;}for (int i = 0; i < nums.size(); i++) {// 剪枝(只关心合法分支)if (check[i] == false && (i == 0 || nums[i] != nums[i - 1] || check[i - 1] == true)) {path.push_back(nums[i]);check[i] = true;dfs(nums, pos + 1);// 回溯path.pop_back();check[i] = false;}}}

✨时间与空间复杂度

🎁时间复杂度
  1. 全排列生成:
    • 数组长度为 n n n,排列数量为 n ! n! n!
  2. 剪枝优化:
    • 剪枝减少重复分支,但在最坏情况下仍需要生成 n ! n! n! 个排列。

总时间复杂度 O ( n ⋅ n ! ) O(n \cdot n!) O(nn!)

🎁空间复杂度
  1. 递归深度:
    • 递归调用栈的深度为 O ( n ) O(n) O(n)
  2. 路径和标记数组:
    • 路径数组 path 和布尔数组 check 的大小为 O ( n ) O(n) O(n)

总空间复杂度 O ( n ) O(n) O(n)


结语

回溯法与深度优先搜索(DFS)结合,能有效地解决多种组合问题。通过决策树的结构,回溯法逐步探索解空间,而剪枝则通过减少不必要的计算,显著提高算法的效率。针对特定问题的剪枝优化,可以进一步提升回溯法的性能。

希望通过本文的讲解,您能深入理解回溯法与DFS在解决组合问题中的应用,并通过剪枝技术优化算法效率。如果您对回溯算法或其他算法问题有任何疑问,欢迎交流与讨论!

在这里插入图片描述

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

在这里插入图片描述

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

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

相关文章

egg初始搭建

前言 egg.js 是由阿里开源的面向企业级开发的 Node.js 服务端框架&#xff0c;它的底层是由 Koa2 搭建。 Github&#xff1a;https://github.com/eggjs/egg&#xff0c;目前 14.8K Star&#xff0c;egg 很适合做中台。 安装 首先&#xff0c;你要 确保 Node 已经配置环境变量…

Python中的all/any函数和操作符and/or

操作符直观易读适用简单逻辑&#xff0c;函数紧凑方便适宜多条件处理。 (笔记模板由python脚本于2024年12月12日 22:19:10创建&#xff0c;本篇笔记适合有一定编程基础的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff…

论文学习——多种变化环境下基于多种群进化的动态约束多目标优化

论文题目&#xff1a;Multipopulation Evolution-Based Dynamic Constrained Multiobjective Optimization Under Diverse Changing Environments 多种变化环境下基于多种群进化的动态约束多目标优化&#xff08;Qingda Chen , Member, IEEE, Jinliang Ding , Senior Member, …

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省) 任务书

2025年山东省职业院校技能大赛“信息安全管理与评估”(山东省 任务书 模块一网络平台搭建与设备安全防护任务1&#xff1a;网络平台搭建 &#xff08;50分&#xff09;任务2&#xff1a;网络安全设备配置与防护&#xff08;250分&#xff09; 模块二网络安全事件响应、数字取证…

国标GB28181-2022平台EasyGBS如何实现无插件也能让RTSP在网页端播放?

在流媒体技术日新月异的今天&#xff0c;实时流传输协议&#xff08;RTSP&#xff09;作为视频监控、在线直播等领域的重要支撑&#xff0c;正经历着前所未有的变革。曾经&#xff0c;RTSP在网页端播放面临着诸多挑战&#xff0c;如浏览器兼容性问题、安全性考量以及视频流处理…

EXCEL 关于plot 折线图--频度折线图的一些细节

目录 0 折线图有很多 1 频度折线图 1.1 直接用原始数据做的频度折线图 2 将原始数据生成数据透视表 3 这样可以做出了&#xff0c;频度plot 4 做按某字段汇总&#xff0c;成为累计plot分布 5 修改上面显示效果&#xff0c;做成百分比累计plot频度分布 0 折线图有很多 这…

MATLAB四种逻辑运算

MATLAB中的四种逻辑运算包括逻辑与用&或 a n d 表示 ( 全为 1 时才为 1 &#xff0c;否则为 0 ) and表示(全为1时才为1&#xff0c;否则为0) and表示(全为1时才为1&#xff0c;否则为0)&#xff0c;逻辑或用|或 o r 表示 ( 有 1 就为 1 &#xff0c;都为 0 才为 0 ) or表示…

jmeter CLI Mode 传参实现动态设置用户数

一.需求 CLI 运行模式下每次运行想要传入不同的用户数&#xff0c;比如寻找瓶颈值的场景&#xff0c;需要运行多次设置不同的用户数。 二.解决思路 查看官方API Apache JMeter - Users Manual: Getting Started api CLI Mode 一节中提到可以使用如下参数做属性的替换&#…

iPhone苹果相册视频怎么提取音频?

在数字时代&#xff0c;视频已成为我们记录生活、分享故事的重要方式。然而&#xff0c;有时候我们只想保留视频中的音频部分&#xff0c;比如一段动人的背景音乐或是一段珍贵的对话。那么&#xff0c;苹果相册视频怎么提取音频呢&#xff1f;本文将介绍三种简单且实用的方法&a…

阿里云数据库MongoDB版助力极致游戏高效开发

客户简介 成立于2010年的厦门极致互动网络技术股份有限公司&#xff08;以下简称“公司”或“极致游戏”&#xff09;&#xff0c;是一家集网络游戏产品研发与运营为一体的重点软件企业&#xff0c;公司专注于面向全球用户的网络游戏研发与运营。在整个产业链中&#xff0c;公…

前端成长之路:HTML(4)

前文提到&#xff0c;在HTML中&#xff0c;表格是为了展示数据&#xff0c;表单是为了提交数据。表单标签是十分重要的标签&#xff0c;在网页中&#xff0c;需要和用户进行交互&#xff0c;收集用户信息等&#xff0c;此时就需要使用表单。表单可以将前端收集到的用户输入的信…

axios请求之参数拼接

URL 查询参数传递数据 优点&#xff1a; 简洁性: URL 查询参数的方式比较简洁&#xff0c;适合传递少量的数据。缓存友好: 查询参数可以被浏览器缓存&#xff0c;适合 GET 请求&#xff0c;但对于 POST 请求&#xff0c;浏览器通常不会缓存。 缺点&#xff1a; 数据大小限制: U…

软考高级架构 —— 10.6 大型网站系统架构演化实例 + 软件架构维护

10.6 大型网站系统架构演化实例 大型网站的技术挑战主要来自于庞大的用户&#xff0c;高并发的访问和海量的数据&#xff0c;主要解决这类问题。 1. 单体架构 特点: 所有资源&#xff08;应用程序、数据库、文件&#xff09;集中在一台服务器上。适用场景: 小型网站&am…

【Java学习笔记】Collections 工具类

一、基本介绍 Collections 是一个操作 Set、List 和 Map 等集合的工具类Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作 二、排序操作&#xff1a;&#xff08;均为 static 方法) 三、查找、替换

Mac备忘录表格中换行(`Option` + `Return`(回车键))

在Mac的ARM架构设备上&#xff0c;如果你使用的是Apple的原生“备忘录”应用来创建表格&#xff0c;换行操作可以通过以下步骤来实现&#xff1a; 在单元格中换行&#xff1a; 双击你想要编辑的单元格你可以输入文本&#xff0c;按Option&#xff08;⌥&#xff09; Enter来插…

亚信安全DeepSecurity完成与超云超融合软件兼容性互认

近日&#xff0c;亚信安全与超云数字技术集团有限公司&#xff08;以下简称“超云”&#xff09;联合宣布&#xff0c;亚信安全成功完成与超云超融合软件的产品兼容性互认证。经严格测试&#xff0c;亚信安全云主机安全DeepSecurity与超云FS5000增强型融合系统&#xff08;简称…

Strawberry Fields:探索学习量子光学编程的奇妙世界

​​​​​​​ 一、Strawberry Fields 简介 Strawberry Fields 是由加拿大量子计算公司Xanadu开发的全栈 Python 库&#xff0c;在量子计算领域中占据着重要的地位。它为设计、模拟和优化连续变量&#xff08;CV&#xff09;量子光学电路提供强大工具&#xff0c;Strawberry…

System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本

问题1&#xff1a;“/”应用程序中的服务器错误。 System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本。 说明: 执行当前 Web 请求期间&#xff0c;出现未经处理的异常。请检查堆栈跟踪信息&#xff0c;以了解有关该错误以及代码中导致错误的出处的详细…

Linux学习——7_SElinux

SElinux SElinux简介 SELinux是Security-Enhanced Linux的缩写&#xff0c;意思是安全强化的linux SELinux 主要由美国国家安全局&#xff08;NSA&#xff09;开发&#xff0c;当初开发的目的是为了避免资源的误用 传统的访问控制在我们开启权限后&#xff0c;系统进程可以直…

es(elasticsearch)

elasticsearch启动顺序 文章目录 elasticsearch启动顺序 1️⃣、elasticsearch-7.6.12️⃣、elasticsearch-head-master3️⃣、elasticsearch-7.6.1-kibana-7.6.1 1️⃣、elasticsearch-7.6.1 双击&#xff1a;D:\javaworkspace\java\elasticsearch-7.6.1-windows-x86_64\elas…