C++——AVL树

作者:几冬雪来

时间:2023年11月30日

内容:C++板块AVL树讲解

目录

前言: 

AVL树与搜索二叉树之间的关系: 

AVL树概念:

插入结点:

平衡因子:

旋转:

双旋: 

验证AVL树:

代码:

结尾:


前言: 

在上一篇博客中我们完成了对C++中异常的讲解,但异常在C++中并不是一个重点知识,而在更前面的二叉搜索树中我们有提到过,二叉搜索树是一个非常重要的知识板块,它作为基础会引出之后要学习AVL树与红黑树,而今天我们就来讲解二者其中的一棵树——AVL树。

AVL树与搜索二叉树之间的关系: 

在了解AVL树之前,我们先来回顾一下之前说过的AVL树和红黑树是以二叉搜索树为基础改良出来的树

那么AVL树和红黑树是怎么对二叉搜索树进行改良的

在二叉搜索树博客处我们就有说过二叉搜索树的一种特殊情况,就是上图这种二叉搜索树一边过长,另一边只有一个值的这种极端场景

这种极端场景下这棵二叉搜索树就会退化成类似链表的结果。

AVL树和红黑树就是为了解决这种情况诞生的,只不过二者解决这个问题所用的方法有些类似,但是其性质天差地别。 

因此AVL树与红黑树又被称为二叉平衡搜索树。 

AVL树概念:

了解完了AVL树之后,接下来就来讲解一下AVL树的概念。 

如果二叉搜索树插入一个新结点后,要是能保证每个结点的左右子树高度差的绝对值不超过1。这就是AVL树的做法。

而做到控制这棵树的方法有很多。

在这里控制树的方法就是引入了平衡因子,但是这里的平衡因子并不是必须的

而这个地方每个结点都有平衡因子,它的平衡因子有一套计算公式

平衡因子 = 右子树的高度 - 左子树的高度 

而这里的AVL树也是成功的控制了树的效率它的增删查改时间复杂度为O(logN)

这个地方满二叉树结点的个数为2^h-1转换为AVL树的话,这里的-1就要换为-x。与此同时我们也可以计算出x的范围是多少

插入结点:

在书写完了AVL树的基础框架之后,要想让AVL可以去控制它的平衡,在这个基础上需要我们先进行结点的插入操作

在书写完了它的插入代码之后,接下来就是要对它的平衡因子进行修改了,那要怎么去修改该结点的平衡因子呢? 

平衡因子:

在书写完了二叉搜索树的代码之后,接下来就需要去控制它的平衡

在这个地方地方有一颗AVL树,要在AVL树插入一个结点如果这个结点不平衡应该怎么办?又怎么确定它就是不平衡的呢

而为了解决这些问题,就需要对其进行假设。

这里假设出三种新结点的插入方法,然后依次对它们进行分析。

首先这个地方可以确定这个结点cur与它的父结点parent,这里插入的方法也有两种,一种是插入在parent的左边,另一种是插在parent的右边

那么它的平衡因子会发生怎么样的变化?

类似上图,类似情况一:如果父结点原先就有一个右结点,再插入一个结点到父结点左边的话,这个地方父结点的平衡因子就会从1变为0

情况二:原parent左右结点都为空,在这种情况下插入左结点父结点的平衡因子会变为-1,如果插入右结点的话,它的平衡因子会变为1

但是要注意插入的结点只会影响它的父结点与祖先的平衡因子,它其他方向是不会被影 响的

接下来就是其增加结点对父结点或者祖宗结点平衡因子的影响

在这里如果父结点在增加结点前自己原先已有左结点或者右结点,这里向它另一边增加结点崩坏改变高度,只会影响到父结点

类似左边的图不会影响到祖宗,但是右边的图AVL树的高度发生变化,会影响到祖宗

这里还需要知道一个点当parent更新等于0的时候,这里就说明parent所在的子树高度不变,不会影响到祖先,因此没有必要向上更新

相反如果更新后平衡因子等于1或者-1的话,说明它的子树高度变化,要向上进行更新。要是等于2或者-2的话,该子树的高度变化且不平衡,这里这棵树就出问题了不能继续向上更新

这里平衡因子的作用就是用来判断该AVL树是否平衡没有出现问题

既然知道了这里的平衡因子的使用方法,那么下来就要在代码中去实现它。

首先要记录父结点的平衡因子是0或者1和-1等值,这个地方就需要定义一个整形且将这个整形初始化为0

这里首先要知道什么情况下更新要停止一种情况就是parent的平衡因子为0的时候更新结束另一种是更新到parent的平衡因子为2或-2的情况,该树出问题停止更新,最后一种特殊情况那就是当更新到根节点的时候,这里也是要停止的

然后接下来就要书写更新平衡因子的代码。

因为平衡因子的更新有可能更新到根结点,因此这个地方使用parent来作为循环的条件

接下来就是对_bf的++与--,如果插入的结点为parent的左边,那么_bf就进行--操作,反之在右边进行++操作如果_bf为0则证明不用再更新了,这里就break跳出循环

然后就判断_bf为-1与1和2与-2的情况,如果平衡因子为-1或1的话cur与parent都向上移动,如果平衡因子值为2或者-2那就需要处理这种情况,最后为了防止代码出错导致_bf会变为其他值,else语句中药进行断言操作

旋转:

接下来就是平衡因子的处理了,这里如果某个结点的平衡因子为2,那就说明它的左子树高度比右子树少2

反正为-2的话则是右子树高度比左子树少2

如果发生什么两种情况的其中一种的话,那么这里就要对较高子树的那边做一个旋转的操作

但是在旋转时需要注意几个问题

1.旋转后保持它是搜索树

2.让这棵树变为平衡树且降低这棵子树的高度

再者就是旋转的原理图

这里就是旋转的原理。

如同上图,根结点或者某棵区域树的平衡因子为2,这个时候就需要用到旋转

旋转的核心操作就是把cur的左给parent的右,再让parent给cur的左。这样子做可以让这棵树依旧保持是搜索树,同时保证了平衡因子不为2或者-2,也降低了树的高度

如果根结点为2那么这个地方就需要进行左单旋,相反为-2则是进行右单旋,这样的结果都能保证根结点的平衡因子为0

这棵树旋转后相比插入之前高度发生了变化

接下来就是旋转处代码的书写。

这里就是旋转的具体操作,输入核心代码看似只有两句,但是真正写的时候需要对其进行更改的地方有很多

同时旋转的时候要注意的东西也有很多,如果稍微漏了一点则可能会导致旋转代码的失败

这里就是旋转代码的原理图。

首先给两个结点存cur的位置与cur->left的位置,再给一个结点存parent的父结点

接下来将curleft给给parent的right完成第一步连接,接下来处理cur左子树为空的情况, 然后将原parent的父结点指向cur,然后就是判断原parent结点的parent是空函数某个子树的左右结点后将其连接好修改

最后就是对平衡因子的修改,这里就是AVL树它的左单旋

而既然有左单旋,那么同样的AVL树也右单旋,对比左单旋,右单旋的操作就是将左单旋的代码进行一定的修改

这个地方就是右单旋的代码书写,它和左单旋的区别在于一个是指向cur的left,另外一个是指向cur的right

但是即使是这样,它们的实现原理还是相同的,这里的内容就不再去过多的赘述了。

而且在讲解完了左右单旋之后,接下来就要讲到AVL树的左右双旋

双旋: 

AVL树中不仅有单旋操作,同时还有双旋的操作

这里地方单旋有一个特点,那就是一定是单纯的右边高或者左边高

那么为什么只能单纯的一边高,而不是parent结点是右边高,来到cur结点变成左边高呢?这里就需要一张图来带我们对其进行了解。

这里就是只能单纯的一边高的原因,在上边的那棵树就是单纯的右边高,这个地方cur的左给parent的右后再让cur的左指向parent,这样左并不会破坏这棵树的平衡

但是下边这棵树并不是单纯的一边高在parent那里是右子树高,但是到cur处变成了左子树高在这种情况下让cur的左给parent的右,再让cur的左指向parent,这样子做还是会使得cur的平衡因子为2

而为了解决这个问题就需要使用到双旋的操作。

这里就是双旋操作的概念图。

这里在结点值为2的左子树插入一个新结点,要让这棵树变成平衡树的话,这个地方先要以3结点为旋转点进行一次右单旋再然后以1为旋转点再继续一次左单旋,这里就能成功的解决这个问题了。

而要进行的是单旋还是双旋的基本条件也是很好区分的。  

这里如果满足parent的值是2,cur的值为-1。又或者parent的值是-2,cur的值为1,这两种情况都达到了使用双旋的条件

在知道条件之后,接下来就需要书写代码了。

而且这里的双旋的代码就可以套用上面两个单旋的代码,如果parent为2,cur为-1,那么就要进行双旋操作。

首先就是将parent的右为旋转点进行一次右单旋,再以parent为旋转点进行一次左单旋,这样就能达到想要的效果。

旋转完了之后,下来就是对_bf的修改了,只不过在

这里要注意的点是,如上图在值为60的结点左右插入结点都能用双旋使它变为AVL树,但是插入的是左右哪个结点却会影响到树的造型与_bf的值

如图,如果插入在60结点的左边,最后的结果是值为90的结点平衡因子为1,值为30与60结点的平衡因子都为0

但是插入在60结点的右边,最后是60与90结点的平衡因子为0,30结点的平衡因子为-1,这就是需要注意的点。

如果值为60的结点平衡因子为0,那就说明这个结点是新增结点,在双旋之后3者的平衡因子都为0

同样的在这里值为60结点的子树的下一个结点后插入新结点,最终也使得这棵树的parent与它的左右两个结点的平衡因子不同

在上边有提及值为60的结点平衡因子是0的情况我们可以确定60是这个地方的新增结点

但是这个地方值为60的结点必须保证它自己是新增结点才行如果插入结点使得值为60的结点拥有左右子树并且它的平衡因子为0的话,这个地方结点会在更新到60处停止,并不会继续向上更新

因此我们也可以得出一个结论,如上图平衡因子的更新关键看的是值为60的这个结点。 

再接下来就是对其代码的书写,这里我们先写入的是parent的右边,也就是parent为正数时候的处理方式

首先要确定cur与cur的left的位置,再用_bf记录没旋转前cur的左left的平衡因子

旋转完了之后判断平衡因子为0,1与-1三种情况对应的curleftcur与parent各种的平衡因子是什么,这样就能成功的实现双旋的操作。

剩下的再把parent的左边双旋代码也写出来即可

验证AVL树:

在上边我们完成了结点的平衡因子的修改,并且在不同情况下平衡因子对针对不同情况进行修改,但在并不意味着这里的平衡因子就没有问题了

这里依旧会存在插入的值引发平衡因子发生异常的问题

因此就需要来验证这里的AVL树是否正常。

像这个地方的IsBlance就是来验证AVL树的

首先如果根结点为空的话就直接返回true如果不是的话,接下来就是从根结点判断它的左右子树的高度,在判断根结点左右子树的函数中再走判断根结点的左右结点对应的左右子树,用递归的形式

在然后就是判断如果它的左子树的高度-右子树的高度不等于该结点的_bf的话,这里就对代码进行报错

要是没问题的话就返回true

就像上图一样,在.cpp文件中我们创建了一个数组并且使用map的make_pair将组数组进行了排序和平衡因子的修改

但是在打印insert的值和它们对应的平衡因子的时候我们可以看见,在插入结点11的时候树发生的错误,而且这个错误导致了原先平衡因子正常值为7的结点,它的平衡因子出错了

然后就是通过条件断点和监视窗口解决这个问题。

在这里我们在值为11的结点处设置一个条件断点,接下来就是监视窗口的调试和平衡因子产生的变化

这个地方最终可以确定在右旋处有一点代码没有添加

这就是AVL树的验证方法。

代码:

AVLTree.cpp 

#include"AVLTree.h"int main()
{int a[] = { 16,3,7,11,9,26,18,14,15 };AVLTree<int, int> t;for (auto e : a){if (e == 11){int x = 0;}t.Insert(make_pair(e, e));cout << "Insert:" << e << "->" << t.IsBalance() << endl;}return 0;
}

AVLTree.h 

#pragma once#include <iostream>
#include <assert.h>
using namespace std;template<class K,class V>
struct AVLTreeNode
{pair<K, V> _kv;AVLTreeNode<K, V>* _left;AVLTreeNode<K, V>* _right;AVLTreeNode<K, V>* _parent;int _bf;AVLTreeNode(const pair<K, V>& kv):_kv(kv),_right(nullptr),_left(nullptr),_parent(nullptr),_bf(0){}
};template<class K, class V>
class AVLTree
{typedef AVLTreeNode<K, V> Node;Node* parent;
public:bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);return true;}Node* cur = _root;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);if (parent->_kv.first < kv.first){parent->_right = cur;}else {parent->_left = cur;}cur->_parent = parent;while (parent){if (cur == parent->_left){parent->_bf--;}else{parent->_bf++;}if (parent->_bf == 0){break;}else if (parent->_bf == 1 || parent->_bf == -1){//继续更新cur = parent;parent = parent->_parent;}else if (parent->_bf == 2 || parent->_bf == -2){//需要旋转if (parent->_bf == 2 && cur->_bf == 1){RotateL(parent);}else if (parent->_bf == -2 && cur->_bf == -1){RotateR(parent);}else if(parent->_bf == 2 && cur->_bf == -1){RotateRL(parent);}else if (parent->_bf == -2 && cur->_bf == 1){RotateLR(parent);}break;}else{assert(false);}}return true;}void RotateRL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;int bf = curleft->_bf; RotateR(parent->_right);RotateL(parent);if (bf == 0){cur->_bf = 0;curleft->_bf = 0;parent->_bf = 0;}else if (bf == 1){cur->_bf = 0;curleft->_bf = 0;parent->_bf = -1;}else if (bf == -1){cur->_bf = 1;curleft->_bf = 0;parent->_bf = 0;}else{assert(false);}}void RotateLR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;int bf = curright->_bf;RotateL(parent->_left);RotateR(parent);if (bf == 0){parent->_bf = 0;cur->_bf = 0;curright->_bf = 0;}else if (bf == -1){parent->_bf = 1;cur->_bf = 0;curright->_bf = 0;}else if (bf == 1){parent->_bf = 0;cur->_bf = -1;curright->_bf = 0;}}void RotateL(Node* parent)  { Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;parent->_right = curleft;if (curleft){curleft->_parent = parent;}cur->_left = parent;parent->_parent = cur;if (parent == _root){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->_bf = cur->_bf = 0; }void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;parent->_left = cur->_right;if (curright){curright->_parent = parent;}cur->_right = parent;parent->_parent = cur;if (ppnode == nullptr){_root = cur;cur->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}cur->_parent = ppnode;}parent->_bf = cur->_bf = 0;}int Height(Node* root){if (root == nullptr){return 0;}int leftHight = Height(root->_left);int rightHight = Height(root->_right);return leftHight > rightHight ? leftHight + 1 : rightHight + 1;}bool IsBalance(){return IsBlance(_root);}bool IsBlance(Node* root){if (root == nullptr){return true;}int leftHight = Height(root->_left);int rightHight = Height(root->_right);if (rightHight - leftHight != root->_bf){cout << "平衡因子出错:" << root->_kv.first << "->" << root->_bf << endl;return false;}return abs(rightHight - leftHight) < 2 && IsBlance(root->_left) && IsBlance(root->_right);}private:Node* _root = nullptr;
};

结尾:

到这里我们的AVL树就大概的讲解完毕了,在AVL树里面还会用到删除和查找操作,查找操作十分容易,但是AVL的删除操作相比插入操作有些繁琐。这里不讲解的原因是因为删除操作本质和插入操作没什么区别,只是平衡因子改动变麻烦了,实际上无论是去外应聘或者找工作什么的,都极少要求手撕。而在讲解完了AVL树之后,后面就是要讲另一棵树——红黑树,最后希望这篇博客能给各位带来帮助。

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

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

相关文章

基于SpringBoot母婴商城

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本母婴商城系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

网络基础『发展 ‖ 协议 ‖ 传输 ‖ 地址』

&#x1f52d;个人主页&#xff1a; 北 海 &#x1f6dc;所属专栏&#xff1a; 神奇的网络世界 &#x1f4bb;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 文章目录 &#x1f324;️前言&#x1f326;️正文1.网络发展1.1.背景1.2.类型 2.网络协议2.1.什么是协议2.2.协议…

SpringCloud核心组件

Eureka 注册中心&#xff0c;服务的注册与发现 Feign远程调用 Ribbon负载均衡&#xff0c;默认轮询 Hystrix 熔断 降级 Zuul微服务网关&#xff08;这个组件负责网络路由&#xff0c;可以做统一的降级、限流、认证授权、安全&#xff09; Eureka 微服务的功能主要有以下几…

算法通关村第六关—序列恢复二叉树(青铜)

根据序列恢复二叉树 示例 给定序列恢复二叉树(1)前序&#xff1a;1 2 3 4 5 6 8 7 9 10 11 12 13 15 14(2)中序&#xff1a;3 4 8 6 7 5 2 1 10 9 11 15 13 14 123)后序&#xff1a;8 7 6 5 4 3 2 10 15 14 13 12 11 9 1 一、前中序列恢复二叉树 (1)前序&#xff1a;1 2 3…

电商营销场景的RocketMQ实战01-RocketMQ原理

架构图 Broker主从架构与集群模式 RocketMQ原理深入剖析 Broker主从架构原理 HAConnection与HAClient Broker基于raft协议的主从架构 Consumer运行原理 基础知识 001_RocketMQ架构设计与运行流程分析 RocketMQ这一块&#xff0c;非常关键的一个重要的技术&#xff0c;面试的时候…

高级IO select 多路转接实现思路

文章目录 select 函数fd_set 类型timeval 结构体select 函数的基本使用流程文件描述符就绪条件以select函数为中心实现多路转接的思路select 缺陷 select 函数 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); selec…

PlantUML语法(全)及使用教程-时序图

目录 1. 参与者1.1、参与者说明1.2、背景色1.3、参与者顺序 2. 消息和箭头2.1、 文本对其方式2.2、响应信息显示在箭头下面2.3、箭头设置2.4、修改箭头颜色2.5、对消息排序 3. 页面标题、眉角、页脚4. 分割页面5. 生命线6. 填充区设置7. 注释8. 移除脚注9. 组合信息9.1、alt/el…

MySQL三大日志详细总结(redo log undo log binlog)

MySQL日志 包括事务日志&#xff08;redolog undolog&#xff09;慢查询日志&#xff0c;通用查询日志&#xff0c;二进制日志&#xff08;binlog&#xff09; 最为重要的就是binlog&#xff08;归档日志&#xff09;事务日志redolog&#xff08;重做日志&#xff09;undolog…

MySQL备份与恢复(重点)

MySQL备份与恢复&#xff08;重点&#xff09; 一、用户管理与权限管理 ☆ 用户管理 1、创建MySQL用户 注意&#xff1a;MySQL中不能单纯通过用户名来说明用户&#xff0c;必须要加上主机。如jack10.1.1.1 基本语法&#xff1a; mysql> create user 用户名被允许连接的主…

ssm+vue的仓库在线管理系统的设计与实现(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的仓库在线管理系统的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三…

建文工程项目管理软件 SQL 注入漏洞复现

0x01 产品简介 建文工程管理软件是一个适用于工程投资领域的综合型的多方协作平台。 0x02 漏洞概述 建文工程项目管理软件BusinessManger.ashx、Desktop.ashx等接口处存在SQL注入漏洞&#xff0c;攻击者可通过该漏洞获取数据库中的信息&#xff08;例如&#xff0c;管理员后台…

微信小程序体验版提交审核,提示接口未配置在app.json文件且无权限

在火狐浏览器 打开微信公众平台 发布小程序 弹窗一闪而过 是因为 放开这里就可以了

EI级 | Matlab实现TCN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测

EI级 | Matlab实现TCN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测 目录 EI级 | Matlab实现TCN-BiLSTM-Multihead-Attention多头注意力机制多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.【EI级】Matlab实现TCN-BiLSTM-Multihead-…

特征变换1

编译工具&#xff1a;PyCharm 有些编译工具不用写print可以直接将数据打印出来&#xff0c;pycharm需要写print才会打印出来。 概念 1.特征类型 特征的类型&#xff1a;“离散型”和“连续型” 机器学习算法对特征的类型是有要求的&#xff0c;不是任意类型的特征都可以随意…

docker (简介、dcoker详细安装步骤、容器常用命令)一站打包- day01

一、 为什么出现 Docker是基于Go语言实现的云开源项目。 Docker的主要目标是“Build&#xff0c;Ship and Run Any App,Anywhere”&#xff0c;也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理&#xff0c;使用户的APP&#xff08;可以是一个WEB应用或数据库应…

阿里云Arthas使用——通过watch命令查看类的返回值 捞数据出来

前言 Arthas 是一款线上监控诊断产品&#xff0c;通过全局视角实时查看应用 load、内存、gc、线程的状态信息&#xff0c;并能在不修改应用代码的情况下&#xff0c;对业务问题进行诊断&#xff0c;包括查看方法调用的出入参、异常&#xff0c;监测方法执行耗时&#xff0c;类…

计算机体系结构----指令系统(二)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----计算机系统结构教程(第二版)张晨曦等 计算机体系结构----指令系统&#xff08;二&#xff09; 2.1 指令系统结构的分类2.2 寻址方式2.3 MIPS 指令系统结构2.3.1 MIPS的寄存器2.3.2 MIPS的…

PLC:200smart(13-16章)

PLC&#xff1a;200smart 第十三章2、带参子程序3、将子程序设置成库文件 第十三章 项目ValueValue主程序MAIN一个项目只能有一个&#xff0c;循环扫描子程序SBR_0项目中最多有128个&#xff0c;只有在调用时 才执行&#xff08;子程序可以嵌套其他子程序&#xff0c;最多八层…

SSD-FTL算法学习总结1

SSD核心技术&#xff1a;FTL算法。 1、什么是FTL? FTL是Flash Translation Layer&#xff08;闪存转换层&#xff09;&#xff0c;完成主机&#xff08;HOST&#xff09;逻辑地址空间到闪存Flash物理地址空间的翻译,或者说映射&#xff08;Mapping&#xff09;。 FTL算法分成…

Linux常用命令----mkdir命令

文章目录 1. 基础概念2. 参数含义3. 常见用法4. 实例演示5. 结论 在Linux操作系统中&#xff0c;mkdir 命令是用来创建目录的基础命令。这个命令简单但极其强大&#xff0c;是每个Linux用户都应当熟悉的工具之一。以下是对mkdir命令的详细介绍&#xff0c;包括其参数含义、常见…