【C++航海王:追寻罗杰的编程之路】关联式容器的底层结构——红黑树

目录

1 -> 红黑树

1.1 -> 红黑树的概念

1.2 -> 红黑树的性质

1.3 -> 红黑树节点的定义

1.4 -> 红黑树的结构

1.5 -> 红黑树的插入操作

1.6 -> 红黑树的验证

1.8 -> 红黑树与AVL树的比较

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

2.2 -> 改造红黑树

2.3 -> map的模拟实现

2.4 -> set的模拟实现


1 -> 红黑树

1.1 -> 红黑树的概念

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

1.2 -> 红黑树的性质

  1. 每个节点不是红色就是黑色。
  2. 根节点是黑色的。
  3. 如果一个节点是红色的,则它的两个孩子节点是黑色的。
  4. 对于每个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。
  5. 每个叶子节点都是黑色的(此处的叶子节点指空节点)。

1.3 -> 红黑树节点的定义

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
using namespace std;// 节点的颜色
enum Color 
{ RED, BLACK 
};// 红黑树节点的定义
template<class ValueType>
struct RBTreeNode
{RBTreeNode(const ValueType& data = ValueType(),Color color = RED): _pLeft(nullptr), _pRight(nullptr), _pParent(nullptr), _data(data), _color(color){}RBTreeNode<ValueType>* _pLeft;   // 节点的左孩子RBTreeNode<ValueType>* _pRight;  // 节点的右孩子RBTreeNode<ValueType>* _pParent; // 节点的双亲(红黑树需要旋转,为了实现简单给出该字段)ValueType _data; // 节点的值域Color _color;    // 节点的颜色
};

1.4 -> 红黑树的结构

为了后续实现关联式容器更加简单,红黑树的实现中增加一个头节点,因为根节点必须是黑色的,为了与根节点区分开,将头节点给成黑色,并且让头节点的pParent域指向红黑树的根节点,pLeft域指向红黑树中最小的节点,_pRight域指向红黑树中最大的节点。

1.5 -> 红黑树的插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可以分为两步:

1. 按照二叉搜索树的树规则插入新节点。

template<class ValueType>
struct RBTree
{bool Insert(const ValueType& data){PNode& pRoot = GetRoot();if (nullptr == pRoot){pRoot = new Node(data, BLACK);// 根的双亲为头节点pRoot->_pParent = _pHead;_pHead->_pParent = pRoot;}else{// 1. 按照二叉搜索的树方式插入新节点// 2. 检测新节点插入后,红黑树的性质是否造到破坏,//    若满足直接退出,否则对红黑树进行旋转着色处理}// 根节点的颜色可能被修改,将其改回黑色pRoot->_color = BLACK;_pHead->_pLeft = LeftMost();_pHead->_pRight = RightMost();return true;}
private:PNode& GetRoot(){return _pHead->_pParent;}// 获取红黑树中最小节点,即最左侧节点PNode LeftMost();// 获取红黑树中最大节点,即最右侧节点PNode RightMost();private:PNode _pHead;
}

2. 检测新节点插入后,红黑树的性质是否遭到破坏。

因为新节点的默认颜色为红色,因此:如果其双亲节点的颜色是黑色,没有违反红黑树的任何性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了性质三,即不能有连在一起的红色节点,此时需要对红黑树分情况来讨论: 

  • 情况一:cur为红,p为红,g为黑,u存在且为红。

注意:此处看到的树可能是一棵完整的树,也可能是一棵子树。

如果g是根节点,调整完成后,需要将g改为黑色。

如果g是子树,g一定有双亲,且g的双亲如果是红色,就需要继续向上调整。 

cur和p均为红,违反了性质三。

解决方法:将p、u改为黑,g改为红,然后把g当成cur,继续向上调整。 

  • 情况二:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 说明:

  1. 如果u节点不存在,则cur一定是新插入节点,因为如果cur不是新插入节点,则cur和p一定有一个节点的颜色是黑色,就不满足性质4:每条路径黑色节点个数相同。
  2. 如果u节点存在,则其一定是黑色的,那么cur节点原来的颜色一定是黑色的,现在看到其是红色的原因是因为cur的子树在调整的过程中将cur节点的颜色由黑色改成了红色。

p为g的左孩子,cur为p的左孩子,则进行右单旋转。

p为g的右孩子,cur为p的右孩子,则进行左单旋转。

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

  • 情况三:cur为红,p为红,g为黑,u不存在/u存在且为黑。

 

p为g的左孩子,cur为p的右孩子,则针对p进行左单旋转。

p为g的右孩子,cur为p的左孩子,则针对p进行右单旋转。

则转换成情况二。

针对每种情况进行相应的处理即可。

bool Insert(const ValueType& data)
{// ...// 新节点插入后,如果其双亲节点的颜色为空色,则违反性质3:不能有连在一起的红色结点while (pParent && RED == pParent->_color){// 注意:grandFather一定存在// 因为pParent存在,且不是黑色节点,则pParent一定不是根,则其一定有双亲PNode grandFather = pParent->_pParent;// 先讨论左侧情况if (pParent == grandFather->_pLeft){PNode unclue = grandFather->_pRight;// 情况三:叔叔节点存在,且为红if (unclue && RED == unclue->_color){pParent->_color = BLACK;unclue->_color = BLACK;grandFather->_color = RED;pCur = grandFather;pParent = pCur->_pParent;}else{// 情况五:叔叔节点不存在,或者叔叔节点存在且为黑if (pCur == pParent->_pRight){_RotateLeft(pParent);swap(pParent, pCur);}// 情况五最后转化成情况四grandFather->_color = RED;pParent->_color = BLACK;_RotateRight(grandFather);}}else{// …}}// ...
}

1.6 -> 红黑树的验证

红黑树的检测分为两步:

  1. 检测其是否满足二叉搜索树(中序遍历是否为有序序列)。
  2. 检测其是否满足红黑树的性质。
bool IsValidRBTree(){PNode pRoot = GetRoot();// 空树也是红黑树if (nullptr == pRoot)return true;// 检测根节点是否满足情况if (BLACK != pRoot->_color){cout << "违反红黑树性质二:根节点必须为黑色" << endl;return false;}// 获取任意一条路径中黑色节点的个数size_t blackCount = 0;PNode pCur = pRoot;while (pCur){if (BLACK == pCur->_color)blackCount++;pCur = pCur->_pLeft;}// 检测是否满足红黑树的性质,k用来记录路径中黑色节点的个数size_t k = 0;return _IsValidRBTree(pRoot, k, blackCount);}bool _IsValidRBTree(PNode pRoot, size_t k, const size_t blackCount){//走到null之后,判断k和black是否相等if (nullptr == pRoot){if (k != blackCount){cout << "违反性质四:每条路径中黑色节点的个数必须相同" << endl;return false;}return true;}// 统计黑色节点的个数if (BLACK == pRoot->_color)k++;// 检测当前节点与其双亲是否都为红色PNode pParent = pRoot->_pParent;if (pParent && RED == pParent->_color && RED == pRoot->_color){cout << "违反性质三:没有连在一起的红色节点" << endl;return false;}return _IsValidRBTree(pRoot->_pLeft, k, blackCount) &&_IsValidRBTree(pRoot->_pRight, k, blackCount);}

1.8 -> 红黑树与AVL树的比较

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

2 -> 红黑树模拟实现STL中的map与set

2.1 -> 红黑树的迭代器

迭代器的好处是可以方便遍历,是数据结构的底层实现与用户透明。如果想要给红黑树增加迭代器,需要考虑以下问题:

  • begin()和end()

STL明确规定,begin()与end()代表的是一段前闭后开的区间,而对红黑树进行中序遍历后,可以得到一个有序的序列,因此:begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置,关键是最大节点的下一个位置在哪里呢?能否给成nullptr呢?

答案是行不通的,因为对end()位置的迭代器进行--操作,必须要能找到最后一个元素,此处就不行,因此最好的方式是将end()放在头节点的位置

  • operator++()与operator--() 
// 找迭代器的下一个节点,下一个节点肯定比其大void Increasement(){//分两种情况讨论:_pNode的右子树存在和不存在// 右子树存在if (_pNode->_pRight){// 右子树中最小的节点,即右子树中最左侧节点_pNode = _pNode->_pRight;while (_pNode->_pLeft)_pNode = _pNode->_pLeft;}else{// 右子树不存在,向上查找,直到_pNode != pParent->rightPNode pParent = _pNode->_pParent;while (pParent->_pRight == _pNode){_pNode = pParent;pParent = _pNode->_pParent;}// 特殊情况:根节点没有右子树if (_pNode->_pRight != pParent)_pNode = pParent;}}// 获取迭代器指向节点的前一个节点void Decreasement(){//分三种情况讨论:_pNode 在head的位置,_pNode 左子树存在,_pNode 左子树不存在// 1. _pNode 在head的位置,--应该将_pNode放在红黑树中最大节点的位置if (_pNode->_pParent->_pParent == _pNode && _pNode->_color == RED)_pNode = _pNode->_pRight;else if (_pNode->_pLeft){// 2. _pNode的左子树存在,在左子树中找最大的节点,即左子树中最右侧节点_pNode = _pNode->_pLeft;while (_pNode->_pRight)_pNode = _pNode->_pRight;}else{// _pNode的左子树不存在,只能向上找PNode pParent = _pNode->_pParent;while (_pNode == pParent->_pLeft){_pNode = pParent;pParent = _pNode->_pParent;}_pNode = pParent;}}

2.2 -> 改造红黑树

#pragma once// set ->key
// map ->key/valueenum Colour
{RED,BLACK
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class T>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T> Self;Node* _node;__TreeIterator(Node* node):_node(node){}T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}Self& operator--();Self& operator++(){if (_node->_right){// 下一个就是右子树的最左节点Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else{// 左子树 根 右子树// 右为空,找孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};// set->RBTree<K, K, SetKeyOfT> _t;
// map->RBTree<K, pair<K, T>, MapKeyOfT> _t;
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __TreeIterator<T> iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){return iterator(nullptr);}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* parent = nullptr;Node* cur = _root;KeyOfT kot;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{return make_pair(iterator(cur), false);}}// 新增节点给红色cur = new Node(data);Node* newnode = cur;cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;cur->_parent = parent;}else{parent->_left = cur;cur->_parent = parent;}while (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (parent == grandfather->_left){//     g//   p   u// cNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上更新处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_left){// 单旋//     g//   p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 双旋//     g//   p//     cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else  // parent == grandfather->_right{//     g//   u   p //          c//Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){// 变色parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续往上处理cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//   u   p //     c//RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;subR->_left = parent;Node* parentParent = parent->_parent;parent->_parent = subR;if (subRL)subRL->_parent = parent;if (_root == parent){_root = subR;subR->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subR;}else{parentParent->_right = subR;}subR->_parent = parentParent;}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;if (subLR)subLR->_parent = parent;Node* parentParent = parent->_parent;subL->_right = parent;parent->_parent = subL;if (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (parentParent->_left == parent){parentParent->_left = subL;}else{parentParent->_right = subL;}subL->_parent = parentParent;}}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}// 根节点->当前节点这条路径的黑色节点的数量bool Check(Node* root, int blacknum, const int refVal){if (root == nullptr){//cout << balcknum << endl;if (blacknum != refVal){cout << "存在黑色节点数量不相等的路径" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "有连续的红色节点" << endl;return false;}if (root->_col == BLACK){++blacknum;}return Check(root->_left, blacknum, refVal)&& Check(root->_right, blacknum, refVal);}bool IsBalance(){if (_root == nullptr)return true;if (_root->_col == RED)return false;//参考值int refVal = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK){++refVal;}cur = cur->_left;}int blacknum = 0;return Check(_root, blacknum, refVal);}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 ? leftHeight + 1 : rightHeight + 1;}size_t Size(){return _Size(_root);}size_t _Size(Node* root){if (root == NULL)return 0;return _Size(root->_left)+ _Size(root->_right) + 1;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_kv.first < key){cur = cur->_right;}else if (cur->_kv.first > key){cur = cur->_left;}else{return cur;}}return NULL;}private:Node* _root = nullptr;
};

2.3 -> map的模拟实现

map的底层结构就是红黑树,因此在map中直接封装一棵红黑树,然后将其接口包装下即可。

#pragma once
#include"RBTree.h"namespace fyd
{template<class K, class V>class map{public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};// 对类模板取内嵌类型,加typename告诉编译器这里是类型typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};
}

2.4 -> set的模拟实现

set的底层为红黑树,因此只需在set内部封装一棵红黑树,即可将该容器实现出来。

#pragma once
#include"RBTree.h"namespace fyd
{template<class K>class set{public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, K, SetKeyOfT> _t;};
}

感谢各位大佬支持!!!

互三啦!!!

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

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

相关文章

【代码随想录07】344.反转字符串 541. 反转字符串II 54.替换数字

目录 344. 反转字符串题目描述做题思路参考代码 541. 反转字符串 II题目描述参考代码 54. 替换数字题目描述参考代码 344. 反转字符串 题目描述 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空…

昇思25天学习打卡营第17天|应用实践之SSD目标检测

基本介绍 今天要学习的内容是计算机视觉领域中的目标检测任务。与图像分类相比&#xff0c;目标检测更难&#xff0c;因为目标检测不仅要检测出图片中的物体的类别&#xff0c;还要检测出该物体的位置。现主流的目标检测算法大致可分为两种&#xff0c;一种是基于CNN的&#xf…

UML时序图的绘制

一分钟学会绘制时序图 目录 1、简单了解时序图元素 2、进入实例理解 实例1 实例2 3、四种常用的组合片段 Opt:包含一个可能发生或不发生的序列 Alt:片段中两个只会发生其一 Loop:片段可重复一定次数 Par:片段中事件可多线程并行处理 4、分清几个消息 同步消息&#…

【CUDA】 Trust基本特性介绍及性能分析

Trust简介 Thrust 是一个实现了众多基本并行算法的 C 模板库,类似于 C 的标准模板库(standard template library, STL)。该库自动包含在 CUDA 工具箱中。这是一个模板库,仅仅由一些头文件组成。在使用该库的某个功能时,包含需要的头文件即可。该库中的所有类型与函数都在命名空…

体验完这款售价29999元起苹果新品,我大受震撼

讲道理&#xff0c;数码圈已经很久没有出现过让人耳目一新的产品了。 整个圈子近些年各家新品逻辑给我的一种感觉是普遍主打循规循距&#xff0c;用高情商话来说那叫稳扎稳打不易出错&#xff0c;而低情商嘛&#xff0c;说白了叫创新精神严重缺失。 「科技最后以换皮为准」这…

Java 8革新:现代编程的全新标准与挑战

文章目录 一、方法引用二、接口默认方法三、接口静态方法四、集合遍历forEach()方法 一、方法引用 方法引用是Java 8中一种简化Lambda表达式的方式&#xff0c;通过直接引用现有方法来代替Lambda表达式。 方法引用使得代码更加简洁和易读&#xff0c;特别是在处理函数式接口时&…

揭秘小红书矩阵系统:源码助力一键自动发布,多平台管理,效率飙升!

在数字化时代&#xff0c;社交媒体已成为品牌和个人展示自我、推广产品的重要舞台。小红书&#xff0c;作为备受年轻人喜爱的社交平台&#xff0c;其影响力不容小觑。然而&#xff0c;面对日益激烈的竞争&#xff0c;如何高效地在小红书上发布内容、管理多平台账号&#xff0c;…

数模打怪(一)之层次分析法

一、什么是层次分析法 层次分析法&#xff08;AHP&#xff09;主要用于解决评价类问题&#xff08;可打分&#xff09; 比如哪种方案更好、哪位运动员更优秀等 二、层次分析法的三个步骤 1、建立层次结构 分析题目&#xff0c;找出评价类问题的三要素&#xff1a; &#x…

通过Xftp向linux系统传文件,出现Permission is not allowed错误怎么办?

使用xftp出现如下情况&#xff0c;就是说明权限不够。什么权限呢&#xff1f;是我们准备传输的linux系统上面的目标文件夹的权限不够&#xff0c;给linux上面这个目标文件夹提升权限即可。 注意点&#xff1a; 777后面跟的是目录名&#xff0c;比如你想往/usr/local/src这个目…

MySQL 数据库基础概念

一、什么是数据库&#xff1f; 数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库。 每个数据库都有一个或多个不同的 API 用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。 我们也可以将数据存储在文件中&…

用python生成词频云图(python实例二十一)

目录 1.认识Python 2.环境与工具 2.1 python环境 2.2 Visual Studio Code编译 3.词频云图 3.1 代码构思 3.2 代码实例 3.3 运行结果 4.总结 1.认识Python Python 是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。 Python 的设计具有很强的可读性&a…

Python导包问题

文章目录 1问题背景2参考资料及分析3可以兼顾的方法 1问题背景 需要在当前文件中导入当前文件的上级目录下某个文件夹中的文件&#xff0c;如下图所示 即在CBOW.py文件中导入utils\Embedding.py文件中的类&#xff1b; 2参考资料及分析 如何将Python的上级目录的文件导入&am…

react基础语法,模板语法,ui渲染,jsx,useState状态管理

创建一个react应用 这里使用create-react-app的脚手架构建项目&#xff08;结构简洁&#xff0c;基于webpack-cli&#xff09;&#xff0c; npx create-react-app [项目名称] 使用其他脚手架构建项目可以参考&#xff1a;react框架&#xff0c;使用vite和nextjs构建react项目…

数学建模国赛入门指南

文章目录 认识数学建模及国赛认识数学建模什么是数学建模&#xff1f;数学建模比赛 国赛参赛规则、评奖原则如何评省、国奖评奖规则如何才能获奖 国赛赛题分类及选题技巧国赛赛题特点赛题分类 国赛历年题型及优秀论文数学建模分工技巧数模必备软件数模资料文献数据收集资料收集…

【7月长沙】2024年土木、水利与智能建造国际会议(CHEIC 2024)

在21世纪的今天&#xff0c;随着科技的迅猛发展&#xff0c;土木工程、水利工程与智能建造领域正迎来前所未有的变革。为了汇集全球范围内的智慧&#xff0c;推动这一领域的进步与发展&#xff0c;土木、水利工程与智能建造国际会议&#xff08;CHEIC 2024&#xff09;应运而生…

华为浏览器,Chrome的平替,插件无缝连接

文章目录 背景插件书签 背景 不知道各位小伙伴有没有这样的痛点&#xff0c;办公电脑、家里的电脑还有手机、平板等&#xff0c;收藏了一个网址或者在手机上浏览了某个网页&#xff0c;保存起来&#xff0c;可是一换平台或者换个电脑&#xff0c;在想要浏览之前收藏的东西&…

Selenium 中的 JUnit 注解

JUnit 是一个基于 Java 的开源框架&#xff0c;可帮助测试人员执行单元测试。JUnit 主要用于测试应用程序的每个单元或组件&#xff0c;例如类和方法。它有助于编写和运行可重复的自动化测试&#xff0c;以确保项目代码按预期运行。还可以使用 JUnit 执行 Selenium 自动化测试用…

E. Beautiful Array(cf954div3)

题意&#xff1a;给定一个数组&#xff0c;可以先对数组进行任意排序&#xff0c;每次操作可以选择一个ai&#xff0c;将它变成aik&#xff0c; 想让这个数组变成一个美丽数组&#xff08;回文数组&#xff09;&#xff0c;求最少操作次数 分析&#xff1a; 先找出相同的数字…

Linux--深入理与解linux文件系统与日志文件分析

目录 一、文件与存储系统的 inode 与 block 1.1 硬盘存储 1.2 文件存取--block 1.3 文件存取--inode 1.4 文件名与 inode 号 ​编辑 1.5 查看 inode 号码方法 1.6 Linux 系统文件的三个主要的时间属性 1.7 硬盘分区结构 1.8 访问文件的简单了流程 1.9 inode 占用 1.…

从0-1搭建一个web项目(页面布局详解)详解

本章分析页面布局详解详解 ObJack-Admin一款基于 Vue3.3、TypeScript、Vite3、Pinia、Element-Plus 开源的后台管理框架。在一定程度上节省您的开发效率。另外本项目还封装了一些常用组件、hooks、指令、动态路由、按钮级别权限控制等功能。感兴趣的小伙伴可以访问源码点个赞 地…