红黑树是如何实现的?

文章目录

  • 一、红黑树的概念
  • 二、红黑树的性质
  • 三、红黑树和AVL树对比
  • 四、红黑树的插入
    • 1. 红黑树的结点定义
    • 2. 父亲的颜色
    • 3. 叔叔的颜色为红色
    • 4. 叔叔不存在
    • 5. 叔叔存在且为黑
    • 6. 插入的抽象图
  • 五、红黑树的验证
    • 1. 检查平衡
    • 2. 计算高度与旋转次数
    • 3. 验证
  • 六、 红黑树与AVL树的比较

一、红黑树的概念

红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍(即最长路径不超过最短路径的两倍,近似平衡),因而是接近平衡的。

image-20230921131357000

二、红黑树的性质

  1. 每个结点不是红色就是黑色
  2. 根节点是黑色的
  3. 如果一个节点是红色的,则它的两个孩子结点必须是黑色的(即任何路径没有连续的红色结点)
  4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点(每条路径上的黑色结点个数相同,这里的路径包括空结点)
  5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点,已经不在是传统的叶子结点了,图中的NIL结点就是空结点)

这里对第四点做一补充说明

对于下面这棵树有11条路径,而不是5条路径,因为空结点也包括在内

image-20230921132948276

如果不清楚上面的这一点,如果遇到下面这棵树,可能会误以为他是红黑树,实际上它不是红黑树。

如果我们不看每个空结点的话,它看上去有四条路径,且每条路径都只有两个黑色结点,看上去好像是红黑树。但是实际上要包括空结点,且空结点是黑色的。所以一共有九条路径,最左边的一条路径只有两个黑色结点,其他路径都有三个黑色结点。

image-20230921133042188

第四点中说的虽然是每个结点的每条路径上的黑色结点个数相同,但是由于每个结点的祖先的路径是唯一确定的,所以我们只需要判断从根节点到每个空结点上的路径中黑色结点个数是否相同即可

那么为什么上面的性质可以保证最长路径不超过最长路径的两倍呢?

如下图所示, 最长路径就是黑红相间的情况,最短路径就是全黑的情况。其他路径都是在这两者之间的,此时我们也不难看出最长路径不超过最短路径的两倍。如果最短路径为N,那么最长路径就是2N-1,因为根节点一定是黑色的,最终的叶子结点也一定是黑色的。

image-20230921140144567

三、红黑树和AVL树对比

既然有了AVL树,那么为什么要有红黑树呢?其实是因为AVL树太严格了。它要控制平衡就需要付出代价。而红黑树要求并不严格,综合来看,红黑树的综合性能其实更优

AVL树红黑树
高度对比高度差不超过一最长路径不超过最短路径的两倍
插入1000个值logN---->102*logN---->20
插入10亿个值logN---->302*logN---->60

我们可以看到,虽然他们的高度有点差异,但是他们的效率都是同一个量级的,而且cpu的速度是非常快的,这点效率对于cpu几乎没有任何区别

性能是同一量级的,但是AVL树控制严格平衡是需要付出代价的,插入和删除需要大量的旋转。

四、红黑树的插入

1. 红黑树的结点定义

如下所示, 由于红黑树有红色结点和黑色结点两种颜色。所以不妨我们使用一个枚举类型来进行表示。红黑树里面我们需要有一个pair类型来进行存储key和value类型的数据。然后我们定义三个指针,分别指向父亲,左孩子,右孩子。最后定义其的颜色。在构造函数中,我们将其的颜色默认设置为红色。

设置为红色是比较有讲究的。那是因为我们大多数场景下都是去创建一个新节点去插入的。如果我们插入的这个新结点是黑色的话,那么造成的后果就是违反了规则4,即每条路径上的黑色结点个数相同,显然这种情况要进行补救的话是十分麻烦的。还不如去插入红色结点,如果插入的是红色结点的话,仅仅有可能会违反规则3,也就是红色结点的孩子必须是黑色结点。这一点的话,我们还有的补救,因为仅仅影响本条路径。

enum Color
{RED,BLACK
};template<class K , class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Color _color;RBTreeNode(const pair<K, V>& kv):_kv(kv),_left(nullptr),_right(nullptr),_parent(nullptr),_color(RED) // 插入红色结点,违反规则3,只影响一条路径,甚至可能不影响。如果插入黑色结点,违反规则4,影响所有路径{}
};

2. 父亲的颜色

因为我们要插入的结点是红色的,那么在我们插入的位置,它的父亲要么是红色的要么是黑色的。如果是黑色的,那么就是如下的情况

image-20230924165738466

我们可以看到,似乎并未影响整棵树的结构,不违反任何一条规则。最短路径仍然不超过最长路径的两倍。每条路径上的黑色结点个数仍然相等。所以如果插入一个新节点以后,如果此处它的父亲是黑色的,完美,不需要做出任何修改即可。如果父亲甚至都不存在,那么这个结点就是这颗树了。我们只需要将这个结点变为黑色即可。如果父亲是红色的话,那么就比较麻烦了。

如下图所示,就是插入的时候父亲为红色,显然已经违反了规则3了,此时我们需要对这颗树进行处理了。

image-20230924174004600

3. 叔叔的颜色为红色

如果我们插入了一个结点以后,此时,父亲结点为红色,且有父亲的兄弟,即叔叔,且叔叔的颜色是红色。

即如下的情况,uncle存在且为红

这样的话,我们可以将parent和uncle都变黑,然后让grandfather变红,即如下的情形

image-20230924181504937

这样的话,在黑色结点数量不变的条件下,使得连续红色结点的问题暂时解决了。现在原本cur为红色的问题转换为了grandfather为红色的问题。

此时的话,如果这个grandfather如果没有父亲了,那么根据规则1,我们只需要让他变为黑色即可。此时仅仅只是所有的路径多了一个黑色结点。如果grandfather有父亲,那么我们只需要让grandfather变为cur,继续向上处理即可

在这里继续向上处理的时候又分为以下几种情况

  1. grandfather没有父亲,那么直接让grandfather变黑即可
  2. grandfather有父亲,且父亲为黑色,那么由于前面的树本身就是满足红黑树规则,这里改变了之后仍然满足红黑树规则,那么不处理即可
  3. grandfather有父亲,且父亲为红色,这样的情况下,父亲为红色,就隐含了必然存在grandfather的grandfather,因为原来的树也要满足红黑树的规则,这样一来就是类似的问题继续往处理即可。

image-20230925163352597

然后由于此时恰好uncle存在且为红,那么我们只需要按照前面的逻辑进行处理即可,即uncle和parent均变黑,然后grandfather变为红

image-20230925163643420

然后又为了满足根节点为红,所以grandfather变为黑即可

image-20230925163816261

4. 叔叔不存在

如下图所示,当叔叔不存在的时候,我们还插入了一个新节点以后,我们发现最长路径已经超过了最短路径的两倍了。这时候单纯的进行变色,已经无法解决问题了。我们需要旋转了。

image-20230925164520373

所以我们就需要进行旋转+变色了。

  • 对于变色:与前面的情况类似,即本来parent和uncle都为红色的话,就把他们两个变为黑色,然后把grandfather变为红色就可以了。不过现在uncle不存在了。那我们就先不管它了,将parent变为黑色,grandfather变为红色就可以了。
  • 对于旋转:这个就需要我们进行具体的分析了,看看究竟是左旋还是右旋还是双旋。具体判断规则与AVL基本是一致的,如果是直线那么就是单旋,我们只需要分析谁高就可以了。如果是折线就是双旋,我们还是分析哪边高就可以了。

如上图所示的情形就是右单旋就可以了

image-20230925170559866

5. 叔叔存在且为黑

我们接着前面的图,我们继续插入一个新的结点

image-20230925171058231

那么此时的uncle存在且为红,我们进行相应的处理后,变为如下的情况

image-20230925171747589

这时候,我们遇到了新的情况,uncle存在且为黑

那么此时的处理情形就和前面的uncle不存在是一样的,直接旋转加变色。这里的如何旋转和变色统一结论后面详细讨论

image-20230925171823196

总结

  1. 红黑树的插入主要看uncle

  2. uncle存在且为红,变色+继续往上更新

  3. uncle不存在或uncle存在且为黑,旋转+变色

6. 插入的抽象图

  • 如下是第一种情况,当cur为红,p为红,g为黑,u存在且为红的条件下。

image-20230926163310656

在这种情况下,我们可以p和u改为黑色,g改为红,然后把g当成cur,继续向上调整。

image-20230926163631710

上面是我们的抽象图,我们如果要具体细化每一种情况的下是非常之麻烦的

比如说当a、b、c、d、e均为空,即cur是新增结点的时候,如下所示

image-20230926172512048

当a,b不为空,且cur是黑色结点。那么cde都是含有一个结点的子树,那么cde的每个样子都有四种情况,如下所示

image-20230926172700176

由于它可以在a,b这两个红色结点任意处进行插入,也就是说,可以在四个位置插入。

image-20230926172742003

那么这种变换的情况就复杂很多,有4*4*4*4共256种情况

如果cde有两个黑节点的话,那么情况将更加复杂,画图已经很难表示出来了

image-20230926174004784

显然我们如果用具象图的话,那么红黑树有无数种情况,所以我们只能使用抽象图来表示这种情况。

所以根据前面的分析,我们就可以写出如下的代码了。下面只是处理颜色的部分。

		//开始处理颜色while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_color == RED){parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = grandfather->_parent;}else if (uncle == nullptr || uncle->_color == BLACK){}}else{Node* uncle = grandfather->_left;if (uncle&& uncle->_color = RED){parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = grandfather->_parent;}else if (uncle == nullptr || uncle->_color == BLACK){}}}
  • 接下来,我们讨论第二种情况,即cur为红,p为红,g为黑,u不存在/u存在且为黑

image-20230929185023277

首先来分析uncle不存在的情况下,即如下的情况。此时我们可以注意到,由于uncle不存在,那么cur必然就是新插入的结点。此时我们就根据当前的cur与p的关系来确定是单旋还是双旋。旋转之后,进行变色。在这里的变色中,如果是单旋,就是g和p变色即可。如果是双旋,那么就是cur和g变色

image-20230929171305920

  1. p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反p为g的右孩子,cur为p的右孩子,则进行左单旋转

    p、g变色—p变黑,g变红

  2. p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,此时转化了为了第一步的情况

再来看一下uncle存在且为黑的情况下。由于uncle存在且为黑,所以cur之前必然为黑色的,因为为了满足每条路径上的黑色结点个数相同,就代表着,cur必须为先前向上处理后的,在向上处理过程中,cur变为了红色。

image-20230929185000121

当uncle存在且为黑的情况下,他的变色以及旋转规则都是与uncle不存在是一模一样的

  1. p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反p为g的右孩子,cur为p的右孩子,则进行左单旋转

    p、g变色—p变黑,g变红

  2. p为g的左孩子,cur为p的右孩子,则针对p做左单旋转;相反,p为g的右孩子,cur为p的左孩子,则针对p做右单旋转,此时转化了为了第一步的情况

所以最终插入的完整代码为

	bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_color = BLACK; //根节点必须是黑色return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(kv); //插入红色结点if (parent->_kv.first < kv.first){parent->_right = cur;}else if (parent->_kv.first > kv.first){parent->_left = cur;}cur->_parent = parent;//开始处理颜色while (parent && parent->_color == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){Node* uncle = grandfather->_right;if (uncle && uncle->_color == RED){parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = grandfather->_parent;}else if (uncle == nullptr || uncle->_color == BLACK){if (parent->_left == cur){RotateR(grandfather);parent->_color = BLACK;grandfather->_color = RED;break;}else{RotateL(parent);RotateR(grandfather);cur->_color = BLACK;grandfather->_color = RED;break;}}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_color == RED){parent->_color = BLACK;uncle->_color = BLACK;grandfather->_color = RED;cur = grandfather;parent = grandfather->_parent;}else if (uncle == nullptr || uncle->_color == BLACK){if (parent->_right == cur){RotateL(grandfather);grandfather->_color = RED;parent->_color = BLACK;break;}else{RotateR(parent);RotateL(grandfather);cur->_color = BLACK;grandfather->_color = RED;break;}}}}_root->_color = BLACK;return true;}void RotateL(Node* parent){Node* cur = parent->_right;Node* curleft = cur->_left;Node* ppnode = parent->_parent;//改变parentparent->_right = curleft;parent->_parent = cur;//改变curleftif (curleft != nullptr){curleft->_parent = parent;}//改变curcur->_left = parent;cur->_parent = ppnode;//改变ppnodeif (ppnode == nullptr){_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}}void RotateR(Node* parent){Node* cur = parent->_left;Node* curright = cur->_right;Node* ppnode = parent->_parent;//改变parentparent->_left = curright;parent->_parent = cur;//改变currightif (curright != nullptr){curright->_parent = parent;}//改变curcur->_right = parent;cur->_parent = ppnode;//改变ppnodeif (ppnode == nullptr){_root = cur;}else{if (ppnode->_left == parent){ppnode->_left = cur;}else{ppnode->_right = cur;}}}

五、红黑树的验证

当我们写完一个比较复杂的程序的时候,我们最好去写一个辅助代码去验证它

1. 检查平衡

如下代码所示,可以去检测我们实现的红黑树当插入数据以后是否会出现不平衡的现象。检查利用的就是每条路径上黑色结点个数相同与不能出现连续的两个红色结点这两条规则。

	bool IsBalance(){return IsBalance(_root);}bool IsBalance(Node* root){if (root == nullptr){return true;}if (root->_color == RED){return false;}int benchmark = 0;Node* cur = root;while (cur){if (cur->_color == BLACK){benchmark++;}cur = cur->_left;}return CheckColor(root, 0, benchmark);}bool CheckColor(Node* root, int blacknum, int benchmark){//检查每条路径的黑色结点数量是否相等if (root == nullptr){if (blacknum != benchmark){return false;}return true;}if (root->_color == BLACK){blacknum++;}//检查颜色if (root->_color == RED && root->_parent && root->_parent->_color == RED){cout << root->_kv.first << ":" << "出现两个连续的红色结点" << endl;return false;}return CheckColor(root->_left, blacknum, benchmark) && CheckColor(root->_right, blacknum, benchmark);}

2. 计算高度与旋转次数

高度的代码很简单,如下所示

	int Height(){return Height(_root);}int Height(Node* root){if (root == nullptr){return 0;}int leftheight = Height(root->_left);int rightheight = Height(root->_right);return leftheight > rightheight ? 1 + leftheight : 1 + rightheight;}

对于旋转次数,我们可以直接设置一个变量专门计数。每旋转一次这个值加一次

	int Getrotatecount(){return _rotatecount;}

3. 验证

int main()
{RBTree<int, int> rb;srand(time(0));for (int i = 0; i < 10000; i++){int tmp = rand();rb.Insert(make_pair(tmp, tmp));//rb.Insert(make_pair(i, i));//cout << rb.IsBalance() << endl;//cout << i << ":" << tmp << endl;}cout << "height:" << rb.Height() << endl;cout << "rotatecount:" << rb.Getrotatecount() << endl;cout << rb.IsBalance() << endl;return 0;
}

我们使用上面的随机数来进行测试

运行结果如下所示

image-20230929223920622

六、 红黑树与AVL树的比较

红黑树和AVL树都是高效的平衡二叉树,增删改查的时间复杂度都是logN,红黑树不追求绝对平衡,其只需保证最长路径不超过最短路径的2倍,相对而言,降低了插入和旋转的次数,所以在经常进行增删的结构中性能比AVL树更优,而且红黑树实现比较简单,所以实际运用中红黑树更多。

从源代码中我们也可以看出来,其实map与set的底层都是红黑树,而且是kv结构的红黑树

image-20230929224532662

image-20230929224609714

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

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

相关文章

基于单片机的煤气泄漏检测报警装置设计

一、项目介绍 煤气泄漏是一种常见的危险情况&#xff0c;可能导致火灾、爆炸和人员伤亡。为了及时发现煤气泄漏并采取相应的安全措施&#xff0c;设计了一种基于单片机的煤气泄漏检测报警装置。 主控芯片采用STM32F103C8T6作为主控芯片&#xff0c;具有强大的计算和控制能力。…

[H5动画制作系列 ]变量,帧频,监听器等的生命周期基础测试

模式:按照上述抓图,actions层&#xff0c;1帧,写初始化代码,10帧写返回代码到2帧代码,2-10帧之间一直循环。1帧及10帧代码如下&#xff1a; 如果程序在1-10之间循环,会反复创建变量i,多个监听器等。所以,第一帧最好执行一次即可&#xff0c;程序在2-10帧之间一直循环。

MySQL Installer is running in Community mode

每天很准时的弹出&#xff1a; 这是由于检查MySql并且更新的一个定时任务&#xff0c;没有更新成功导致 解决办法&#xff1a;禁用定时任务 1.先关闭错误框 2.打开控制面板 &#xff0c;使用小图标查看 3. 打开管理工具&#xff0c;双击打开任务计划程序 4.双击进入&#xf…

epoll与socket缓冲区的恩恩怨怨

文章目录 前言一、什么是socket缓冲区二、阻塞与非阻塞内核缓冲区1、如果发送缓冲区满了会怎么样阻塞非阻塞 2、如果接受缓冲区为空会怎么样阻塞非阻塞 三、epoll与缓冲区的恩恩怨怨水平触发边缘触发非阻塞阻塞 结论 前言 本文深挖网络编程中的缓冲区&#xff0c;从什么是缓冲…

数据结构 | 树

树 树是n&#xff08;n>0&#xff09;个结点的有限集。当n 0时&#xff0c;称为空树。在任意一棵非空树中应满足&#xff1a; 有且仅有一个特定的称为根的结点。当n>1时&#xff0c;其余节点可分为m&#xff08;m>0&#xff09;个互不相交的有限集T1,T2,…,Tm&#…

【Android】线程下载资源保证资源到位采用了 OkHttp的三方网络下载 文件缓存策略

背景 使用 SVGA的三方的url播放方式会比较慢&#xff0c;至少延迟3s以上才会出现svga效果&#xff0c;所以改变策略&#xff1a;将线上的svga全部下载到本地进行播放&#xff0c;那么就得将采用网络缓存的方式实现效果。 实现 那么就得实现以下几点&#xff1a; 初次下载缓…

专栏更新情况:华为流程、产品经理、战略管理、IPD

目录 前言 01 华为流程体系入门课 CSDN学院 02 产品经理进阶课 CSDN学院 03 BLM 战略方法论进阶课 04 IPD 进阶 100 例专栏 作者简介 前言 已上线四大课程专栏更新情况&#xff1a; 01 华为流程体系入门课&#xff08;视频图文&#xff09;&#xff1b; 02 硬件产品经…

UE4/5数字人MetaHuman通过已有动画进行修改

目录 通过已有动画修改动画 开始制作 创建一个关卡序列 将动画序列烘焙到控制绑定 打开我们自己创建的动画序列 之后便是烘焙出来 通过已有动画修改动画 首先架设我们已经有相关的MetaHuman的动画&#xff0c;但是这个动画因为是外部导入进来的&#xff0c;所以可能会出…

处理conda安装工具的动态库问题——解决记录 libssl.1.0.0 系统中所有openssl位置全览 whereis openssl

处理conda安装工具的动态库问题——解决记录 处理conda安装工具的动态库问题——解决记录 - 简书 解决libssl.so.1.0.0: cannot open shared object file: No such file or directory问题 - 简书 openssl 默认版本问题&#xff08;Anaconda相关&#xff09;_anaconda openssl-…

web:[极客大挑战 2019]Upload

题目 页面显示为一个上传&#xff0c;猜测上传一句话木马文件 先查看源代码看一下有没有有用的信息&#xff0c;说明要先上传图片&#xff0c;先尝试上传含有一句话木马的图片 构造payload <?php eval($_POST[123]);?> 上传后页面显示为&#xff0c;不能包含<&…

架构案例2022(四十二)

促销管理系统 某电子商务公司拟升级其会员与促销管理系统&#xff0c;向用户提供个性化服务&#xff0c;提高用户的粘性。在项目立项之初&#xff0c;公司领导层一致认为本次升级的主要目标是提升会员管理方式的灵活性&#xff0c;由于当前用户规模不大&#xff0c;业务也相对…

【笔记】离线Ubuntu20.04+mysql 5.7.36 + xtrabackup定时增量备份脚本

一、环境 ● Ubuntu版本查看 lsb_release -a● mysql 版本查看 mysql --version我的是ubuntu 20.04&#xff0c;mysql是5.7.36&#xff0c;所以要用 install_percona-xtrabackup-24 二、原理 备份 通过ubuntu自带的定时器运行增量备份脚本备份文件可以存储在映射后的其他…

云原生Kubernetes:Pod控制器

目录 一、理论 1.Pod控制器 2.Deployment 控制器 3.SatefulSet 控制器 4.DaemonSet 控制器 5.Job 控制器 6.CronJob 控制器 二、实验 1.Deployment 控制器 2.SatefulSet 控制器 3.DaemonSet 控制器 4.Job 控制器 5.CronJob 控制器 三、问题 1. showmount -e 报错…

华为OD机试 - 判断字符串子序列(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述1、输入2、输出3、说明 四、Java算法源码五、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&am…

京东大型API网关实践之路

概述 1、背景 京东作为电商平台&#xff0c;近几年用户、业务持续增长&#xff0c;访问量持续上升&#xff0c;随着这些业务的发展&#xff0c;API网关应运而生。 API网关&#xff0c;就是为了解放客户端与服务端而存在的。对于客户端&#xff0c;使开放给客户端的接口标准统…

2023-9-28 JZ54 二叉搜索树的第k个结点

题目链接&#xff1a;二叉搜索树的第k个结点 import java.util.*;/** public class TreeNode {* int val 0;* TreeNode left null;* TreeNode right null;* public TreeNode(int val) {* this.val val;* }* }*/public class Solution {/*** 代码中的类名、方…

TVP专家谈腾讯云 Cloud Studio:开启云端开发新篇章

导语 | 近日&#xff0c;由腾讯云 TVP 团队倾力打造的 TVP 吐槽大会第六期「腾讯云 Cloud Studio」专场圆满落幕&#xff0c;6 位资深的 TVP 专家深度体验腾讯云 Cloud Studio 产品&#xff0c;提出了直击痛点的意见与建议&#xff0c;同时也充分肯定了腾讯云 Cloud Studio 的实…

文件审计及文件完整性监控

什么是文件审核 对文件服务器中发生的所有事件的检查称为文件审核。这包括监视文件访问&#xff0c;其中包含谁访问了什么文件、何时以及从何处访问的详细信息;对访问最多和修改的文件的分析;成功和失败的文件访问尝试;等等。文件服务器审核过程的主要目标是跟踪在配置的服务器…

Vue3最佳实践 第六章 Pinia,Vuex与axios,VueUse 1(Pinia)

Pinia状态管理 在 Vue3 中项目中组件之间传递数据时&#xff0c;可以使用 Props 和 Emit&#xff0c;还可以使用 Provide/Inject 来代替 Props 和 Emit。Props 和 Emit 只能在具有父子关系的组件之间传递数据&#xff0c;所以层级越深&#xff0c;过程就越复杂。为了解决此类问…

蓝桥杯 题库 简单 每日十题 day11

01 质数 质数 题目描述 给定一个正整数N&#xff0c;请你输出N以内&#xff08;不包含N&#xff09;的质数以及质数的个数。 输入描述 输入一行&#xff0c;包含一个正整数N。1≤N≤10^3 输出描述 共两行。 第1行包含若干个素数&#xff0c;每两个素数之间用一个空格隔开&…