【高阶数据结构】红黑树(C++实现)

⭐博客主页:️CS semi主页
⭐欢迎关注:点赞收藏+留言
⭐系列专栏:C++进阶
⭐代码仓库:C++进阶
家人们更新不易,你们的点赞和关注对我而言十分重要,友友们麻烦多多点赞+关注,你们的支持是我创作最大的动力,欢迎友友们私信提问,家人们不要忘记点赞收藏+关注哦!!!

【高阶数据结构】红黑树

  • 一、红黑树的概念
  • 二、红黑树的性质
  • 三、红黑树结点的定义
  • 四、红黑树结点的插入
    • 对红黑树是否需要调整,怎么调整?
      • 情况一、插入的叔叔结点存在且为红色
      • 情况二、插入的叔叔结点存在且为黑色
        • 一条直线型
        • 折线
      • 情况三、插入结点的叔叔结点不存在
        • 一条直线型
        • 折线
    • 代码操作
  • 五、验证是否是红黑树
  • 六、红黑树的高度
  • 七、红黑树的查找
  • 八、红黑树的删除
    • (一)情况一
    • (二)情况二
      • 1、brother为红色
      • 2、brother为黑色,且其左右孩子都是黑色结点或为空
      • 3、brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空
      • 4、brother为黑色,且其右孩子是红色结点
    • (三)右边子树
    • (四)情况说明
    • (五)删除操作
    • 代码
  • 九、红黑树与AVL树的比较


一、红黑树的概念

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

在这里插入图片描述

二、红黑树的性质

红黑树的五大性质:

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

我们需要思考一个问题:为什么满足上面的性质,红黑树就能保证:其最长路径中节点个数不会超过最短路径节点个数的两倍?

根据性质三,说明红节点后必定是黑节点,红黑树中不可能出现连续的红色结点,再根据性质四,说明在每个结点的后代结点的简单路径上均包含相同数目的黑色结点。
假如我们假设有N个黑色结点,那么最短路径就是全部由黑色结点构成的路径,其长度为N。

在这里插入图片描述

最长可能路径是一黑一红间隔往下排列,所以总长度为2N。

在这里插入图片描述
所以红黑树从根到叶子的最长可能路径不超过最短可能路径的2倍。

三、红黑树结点的定义

我们定义了一个K-V的模型的红黑树。为了方便后续的旋转操作,我们将红黑树的结点定义为三叉链的结构,我们还加入了一个定义结点颜色的枚举类型,表示结点的颜色。

// 定义结点颜色
enum Col
{RED,BLACK
};// 定义红黑树结点
template<class K, class V>
struct RBTreeNode
{// 构造函数RBTreeNode(const pair<K, V> kv):_left(nullptr),_right(nullptr),_parent(nullptr),_col(RED),_kv(kv){}// 三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;// 存储颜色int _col;// 存储键值对pair<K, V> _kv;
};

四、红黑树结点的插入

插入之前我们先思考一个简单的问题,每次插入插入什么颜色的结点呢?
答案是红色结点,为什么呢?
因为我们假如插入的是黑色结点,发现,这条路径下的黑色结点比别的路径下的黑色结点多了一个,就会违背性质四,所以就需要调整黑色结点和红色结点,调整的结点数有可能是全部,而加入说我们插入的是红色结点,这条路上的黑色结点是没有增加的,而且其他路的黑色结点也都是没有增加的,所以是平衡的,但有一种情况是其父节点是红色的,插入的是红色结点就会导致连续的红色结点,即违背了性质三,所以也是需要调整的,我们在下面进行总结:

1、插入黑色结点:失误点在于必然导致这一条路径下黑色结点增多,违背性质四,必须要调整颜色。
2、插入红色结点:失误点在于可能其父节点为红色,需要调整,但如果其父节点本来就是黑色那就不需要调整。

所以,我们在根据权衡利弊后,决定需要插入红色结点。

插入过程:(三段步骤,和AVL树的插入整体思路大致相同)

1、找:根据二叉搜索树的插入方法,找到待插入位置。
2、插:将待插入结点插入到树中。
3、调:若插入结点的父结点是红色的,则需要对红黑树进行调整。

对红黑树是否需要调整,怎么调整?

并不是所有的红黑树都需要调整,有一种情况是需要调整的结点的父节点为黑色结点,我们插入红色的结点并不会对本树有影响,所以这种情况是不需要进行调整的。

所以,只有插入结点的父节点为红色结点才需要进行调整的,因为这样会出现连续的红色结点,此时也说明了其父节点绝对不是根节点,因为性质二是根节点(root)是黑色的,所以其插入结点的祖父结点一定存在,此时,红黑树的调整需要判断父节点的兄弟结点,即叔叔结点,所以根据叔叔结点来判断一下不同的情况:

情况一、插入的叔叔结点存在且为红色

为了保持没有连续的红色结点,我们可以将父节点变黑,因为要保证每一条路径的黑色结点是一样的,所以就需要将叔叔节点也变成黑色的。同样也解决了红色结点连续的问题。
在这里插入图片描述

但此时调整并没有完,因为我们不确定祖父节点是不是整个红黑树的根节点,所以就需要进行判断:

如果祖父节点是根节点,我们仅需要将组父节点变成黑色的即可,也就是每条路上多增加了一个黑色结点,不影响。

而如果祖父节点不是根节点,我们就需要继续往上判断,判断祖父的父节点的颜色,再根据这些进行判断叔叔节点。
在这里插入图片描述

此时不管cur在父节点的左孩子还是右孩子,其调整都是一样的。
在这里插入图片描述

情况二、插入的叔叔结点存在且为黑色

这种情况我们可以进行具体的分析一下,我们画张图表示:
在这里插入图片描述
在这里插入图片描述

我们插入的话是这样的,但是不知道大家有没有发现一个错误,这我们不是说每条路径下黑色节点是一样的吗?我们假设组父节点g之上的黑色结点数为x个,叔叔结点之下的黑色节点为y个,则在插入cur之前,我们的左右两边的黑色结点数分别为:x+1(路径一直到NIL空结点)和x+y+2。明显右子树的黑色结点多,根本不满足红黑树的要求!所以这个插入情况肯定不是在新插入时候的情况,而是在情况一往上进行更新的过程中存在的。

注意小贴士:
1、我们算黑色结点是需要算到空的,也就是我们的NIL结点,不是到叶子结点结束,而是算到它下面的空结点。
2、出现这种情况我们单纯靠变色是肯定不行的,所以就需要我们进行像AVL树的旋转操作,而旋转操作以后是不需要继续往上调整了。

两种情况:

一条直线型

仅用单旋(左旋或者是右旋),我们这里列举一种情况(用右单旋–p在g的左孩子,cur在p的左孩子):
在这里插入图片描述
左单旋的情况是p在g的右孩子,cur在p的右孩子。

折线

若cur,p,g这三个结点成为一条折线的情况下,需要进行双旋操作再进行变色处理,我们这里讲解一下左右双旋,即先左单旋再右单旋,我们画个抽象图:

在这里插入图片描述

当p是g的右孩子,cur是p的左孩子的时候,用的是右左双旋。

情况三、插入结点的叔叔结点不存在

我们还是分析一下,这个结点cur是否是新插入的结点?我们先来画一张图来解决一下:
在这里插入图片描述

一条直线型

我们下面图是使用右单旋:
在这里插入图片描述

当p是g的右孩子,cur也是p的右孩子的时候,我们使用的是左单旋,之后再进行变色处理即可。

折线

当cur、p、g三个结点成为一条折线的时候,是需要进行双旋的,我们下面使用的是左右双旋:

在这里插入图片描述

若p是g的右孩子,cur是p的左孩子的时候,是右左双旋,再进行改变颜色。

代码操作

我们在进行代码书写之前,我们需要了解一下pair中的尖括号有一个很奇妙的作用,也就是前面存储指针,后面存储bool值,显示是不是插入成功。我们可以简单了解一下:pair<Node*, true>和<Node*, false>,前面一个代表插入的指针,后面一个变量表明是否插入成功。

// 插入pair<Node*, bool> Insert(const pair<K, V>& kv){// 一棵空树if (_root == nullptr){// 创建新结点 + 颜色初始化为黑色_root = new Node(kv);_root->_col = BLACK; // 根节点得是黑的return make_pair(_root, 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 make_pair(cur, false);}}// 将当前结点的值插入进去cur = new Node(kv); // new一个新的结点cur->_col = RED;Node* newnode = cur; // 记录新插入的结点// 新插入的节点值小于父节点的节点值,插入到parent的左边if (kv.first < parent->_kv.first){parent->_left = cur;cur->_parent = parent;}// 新插入的节点值小于父节点的节点值,插入到parent的左边else{parent->_right = cur;cur->_parent = parent;}// 新插入结点的父节点是红色的,需要做出调整while (parent && parent->_col == RED){Node* grandfather = parent->_parent; // parent是红色,则其父结点一定存在// 以grandparent左右孩子为分界线,分成if和elseif (parent == grandfather->_left) // 左孩子{Node* uncle = grandfather->_right;if (uncle && uncle->_col == RED)// 情况一:uncle存在且为红色{// 颜色调整grandfather->_col = RED;uncle->_col = parent->_col = BLACK;// 继续往上处理cur = grandfather;parent = cur->_parent;}else// 情况二:uncle存在且为黑色 / 情况三:uncle不存在{// 用左右孩子分为两半,一半是if用来表示在左孩子,一半是else用来表示在右孩子if (cur == parent->_left){//   g//  p// c// 右单旋RoateR(grandfather);// 颜色调整parent->_col = BLACK;grandfather->_col = RED;}else{//    g//  p//      c// 左右双旋RoateLR(grandfather);// 颜色调整cur->_col = BLACK;grandfather->_col = RED;}break;}}else // 右孩子{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED)// 情况一:uncle存在且为红色{// 颜色调整grandfather->_col = RED;uncle->_col = parent->_col = BLACK;// 继续往上处理cur = grandfather;parent = cur->_parent;}else// 情况二:uncle存在且为黑色 / 情况三:uncle不存在{// 用左右孩子分为两半,一半是if用来表示在左孩子,一半是else用来表示在右孩子if (cur == parent->_right){//   g//     p//      c// 左单旋RoateL(grandfather);// 颜色调整parent->_col = BLACK;grandfather->_col = RED;}else{//    g//      p//    c// 右左双旋RoateRL(grandfather);// 颜色调整cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return make_pair(newnode, true);}// 左单旋void RoateL(Node* parent){// 三叉链Node* subr = parent->_right;Node* subrl = subr->_left;Node* ppnode = parent->_parent;// subrl与parent的关系parent->_right = subrl;if (subrl)subrl->_parent = parent;// subl和parent的关系subr->_left = parent;parent->_parent = subr;// ppnode和subr的关系if (ppnode == nullptr){_root = subr;subr->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subr;}else{ppnode->_right = subr;}subr->_parent = ppnode;}}// 右单旋void RoateR(Node* parent){// 三叉链Node* subl = parent->_left;Node* sublr = subl->_right;Node* ppnode = parent->_parent;//sublr和parent之间的关系parent->_left = sublr;if (sublr)sublr->_parent = parent;//subl和parent的关系subl->_right = parent;parent->_parent = subl;//ppnode 和 subl的关系if (ppnode == nullptr){_root = subl;subl->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subl;}else{ppnode->_right = subl;}subl->_parent = ppnode;}}// 左右双旋void RoateLR(Node* parent){RoateL(parent->_left);RoateR(parent);}// 右左双旋void RoateRL(Node* parent){RoateR(parent->_right);RoateL(parent);}

五、验证是否是红黑树

先验证一下是否是平衡二叉树。

	// 中序遍历void InOrder(){return _InOrder(_root);}void _InOrder(Node* root){if (root == nullptr)return;// 中序_InOrder(root->_left);cout << root->_kv.first << " ";_InOrder(root->_right);}
	// 验证是否平衡// 先检查检查颜色bool CheckColour(Node* root, int blacknum, int blenchnum) // 基准值{if (root == nullptr){// 每个路径黑色不相等if (blacknum != blenchnum){return false;}return true;}// 黑色增加if (root->_col == BLACK){++blacknum;}// 连续红色结点情况if (root->_col == RED && root->_parent && root->_parent->_col == RED){cout << root->_kv.first << "出现连续的红色结点" << endl;return false;}// 递归return CheckColour(root->_left, blacknum, blenchnum)&& CheckColour(root->_right, blacknum, blenchnum);}// 再检查是否平衡bool IsRBTree(){return _IsRBTree(_root);}bool _IsRBTree(Node* root){if (root == nullptr){return true;}if (root->_col == RED){return false;}// 找最左路径作为黑色结点数目的参考值int blenchnum = 0;Node* cur = _root;while (cur){if (cur->_col == BLACK)++blenchnum;cur = cur->_left;}return CheckColour(root, 0, blenchnum);}

六、红黑树的高度

其实很简单,红黑树的高度算的是左右子树中最高的那个树的层数的高度,所以我们仅仅需要计算一下左右子树中最高的那棵子树即可。

	// 计算树的高度int Height(){return _Height(_root);}int _Height(Node* root){if (root == nullptr){return 0;}int leftcount = _Height(root->_left);int rightcount = _Height(root->_right);return leftcount > rightcount ? leftcount + 1 : rightcount + 1;}

七、红黑树的查找

与搜索二叉树的查找方式是一模一样的:
1、要找的值比当前结点小,往左子树走
2、要找的值比当前结点大,往右子树走
3、要找的值等于当前结点,找到了
4、找到空都没找到,则返回空

	// 红黑树的查找Node* Find(const pair<K, V>& kv){Node* cur = _root;while (cur){// 当前结点的值大于寻找的结点的值if (cur->_kv.first > kv.first){cur = cur->_left;}else if(cur->_kv.first < kv.first){cur = cur->_right;}else{// 找到了return cur;}}return nullptr;}

八、红黑树的删除

第一步:先找待删除的结点

与搜索二叉树大致相同,如果我们找到待删除的结点的左右子树不为空,则需使用替换法进行删除,所以我们最终需要删除的都是左右子树至少有一个为空的结点。

第二步:调整红黑树

同样跟AVL树一样,先需要判断是否对红黑树的颜色有影响,如果破坏了红黑树的四条性质那就需要调整红黑树。

如果本次删除结点为红色结点的话,那么本次删除操作不会破坏红黑树的性质,因此我们不需要对红黑树进行调整。然而如果本次删除的结点为黑色结点的话,那么本次删除操作必然会破坏红黑树的性质,因为黑色结点的减少必然会破坏性质四,所以就需要对红黑树做出调整。

(一)情况一

待删除的结点只有一个孩子,左孩子/右孩子,即待删除的结点是只有一个孩子为空的情况。

在这里插入图片描述

(二)情况二

待删除结点的左右孩子的结点都是空的。
我们以待删除结点是其父结点的左孩子为例,分为以下四种情况:

1、brother为红色

在这里插入图片描述
当待删结点的brother为红色的情况下,先以parent为旋转点进行左单旋,将brother当老大,再进行颜色的调整,brother变成黑色,parent变成红色,我们再对cur做分析,这样就是下面三种情况了。

2、brother为黑色,且其左右孩子都是黑色结点或为空

在这里插入图片描述

3、brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空

在这里插入图片描述
我们先以brother为旋转点进行右单旋,再将brother颜色变红,brotherleft颜色变黑,再需要根据情况变成情况四的情况。

4、brother为黑色,且其右孩子是红色结点

在这里插入图片描述

我们进入到第四种情况时候,就结束了调整,以parent为旋转点进行一次左单旋,然后将parent的颜色赋值给brother,再将parent的颜色变为黑色,最后将brotherRight变为黑色。

(三)右边子树

右边子树和左边子树是大同小异的。

(四)情况说明

我们总共有四种情况,最重要的是我们需要走到第四种情况,这样红黑树的调整才是好了,所以,我们有下面的关系:

1->2,3,4
2->1,2,3,4
3->4
4->结束

我们知道了关系,所以,4是一定能退出的,3能够转化到4然后退出的,1能够转化为2,3,4也是有办法退出的,而只有2这种情况最纠结了,因为刚进入2这种情况parent的颜色是个迷,不管是黑色还是红色都是没有问题的!

(五)删除操作

根据搜索二叉树的删除规则,连接这个结点的左/右孩子即可。

代码

	// 删除bool Erase(const K& key){// 用于遍历二叉树找结点Node* parent = nullptr;Node* cur = _root;// 用于标记实际的删除结点及其父结点Node* delparentpos = nullptr;Node* delpos = nullptr;// 先找到while (cur){// 所给key值小于当前节点的值 -- 往左树走if (key < cur->_kv.first){parent = cur;cur = cur->_left;}// 所给key值大于当前结点的值 -- 往右树走else if (key > cur->_kv.first){parent = cur;cur = cur->_right;}// 找到了else{// 左子树为空if (cur->_left == nullptr){// 待删除结点是根节点if (cur == _root){// 让根节点的右子树作为新的结点_root = _root->_right;if (_root)_root->_parent = nullptr;delete cur; // 删除原节点return true;}else // 不是根节点{delparentpos = parent; // 标记当前待删除结点的父节点delpos = cur; // 标记当前待删除的结点}break; // 删除结点有祖先的结点,需要更新平衡因子}// 右子树为空else if (cur->_right == nullptr){// 待删除结点是根节点if (cur == _root){// 让根节点的左子树作为新的结点_root = _root->_left;if (_root)_root->_parent = nullptr;delete cur; // 删除原节点return true;}else // 不是根节点{delparentpos = parent; // 标记当前待删除结点的父节点delpos = cur; // 标记当前待删除的结点}break; // 删除结点有祖先的结点,需要更新平衡因子}// 左右子树都不为空else{// 替换法// 寻找待删除结点的右子树中的最小值Node* minparent = cur;Node* minright = cur->_right;while (minright->_left){minparent = minright; // 记录一下父节点minright = minright->_left; // 往左子树走}cur->_kv.first = minright->_kv.first;// 将待删除结点first替换为右子树的最小值cur->_kv.second = minparent->_kv.second;// 将待删除结点second替换为右子树的最小值// 记录一下要删除的父节点delparentpos = minparent;// 记录一下实际要删除的结点delpos = minright;break; // 祖先结点的平衡因子需要改变}}}// 没有被修改过,说明没找到当前要删除的结点if (delparentpos == nullptr)return false;// 记录当前要删除结点和当前要删除结点的父节点Node* del = delpos;Node* delP = delparentpos;// 调整红黑树if (delpos->_col == BLACK){if (delpos->_left && delpos->_left->_col == RED) //待删除结点有一个红色的左孩子{//     delpos// _leftdelpos->_left->_col = BLACK; //将这个红色的左孩子变黑}else if (delpos->_right && delpos->_right->_col == RED) //待删除结点有一个红色的右孩子{// delpos//     _rightdelpos->_right->_col = BLACK; //将这个红色的右孩子变黑}else // 待删除结点的左右均为空{while (delpos != _root){// 待删除的结点是其父节点的左孩子if (delpos == delparentpos->_left){//      delparentpos//  delpos       brotherNode* brother = delparentpos->_right; // 兄弟结点是其父结点的右孩子// 情况1:brother为红色if (brother->_col){// 先左旋再调颜色RoateL(delparentpos);delparentpos->_col = RED;brother->_col = BLACK;// 继续向上调整brother = delparentpos->_right;}// 情况2:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;// 分情况if (delparentpos->_col == RED){delparentpos->_col = BLACK;break;}// 向上调整delpos = delparentpos;delparentpos = delpos->_parent;}else{// 情况3:brother为黑色,且其右孩子是红色结点// 左旋RoateL(delparentpos);brother->_col = delparentpos->_col;delparentpos->_col = BLACK;brother->_right->_col = BLACK;break;// 情况4:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空if ((brother->_right == nullptr) || (brother->_right->_col == BLACK)){brother->_left->_col = BLACK;brother->_col = RED;RoateR(brother);brother = delparentpos->_right; }}}// 待删除的结点是其父节点的右孩子else{Node* brother = delparentpos->_right; // 兄弟结点是其父结点的右孩子// 情况1:brother为红色if (brother->_col == RED){RoateR(delparentpos);delparentpos->_col = RED;brother->_col = BLACK;// 继续向上调整brother = delparentpos->_left;}// 情况2:brother为黑色,且其左右孩子都是黑色结点或为空if (((brother->_left == nullptr) || (brother->_left->_col == BLACK))&& ((brother->_right == nullptr) || (brother->_right->_col == BLACK))){brother->_col = RED;if (delparentpos->_col == RED){delparentpos->_col = BLACK;break;}delpos = delparentpos;delparentpos = delpos->_parent;}else{// 情况3:brother为黑色,且其右孩子是红色结点RoateR(delparentpos);brother->_col = delparentpos->_col;delparentpos->_col = BLACK;brother->_left->_col = BLACK;break;// 情况4:brother为黑色,且其左孩子是红色结点,右孩子是黑色结点或为空if ((brother->_left == nullptr) || (brother->_left->_col == BLACK)){brother->_right->_col = BLACK;brother->_col = RED;RoateL(brother);brother = delparentpos->_left;}}}}}}// 进行删除// 删除的结点的左子树是空树if (del->_left == nullptr){if (del == delP->_left) //删除结点是其父结点的左孩子{delP->_left = del->_right;if (del->_right)del->_right->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_right;if (del->_right)del->_right->_parent = delP;}}else // 删除的结点的右子树是空树// del->_right == nullptr {if (del == delP->_left) //实际删除结点是其父结点的左孩子{delP->_left = del->_left;if (del->_left)del->_left->_parent = delP;}else //实际删除结点是其父结点的右孩子{delP->_right = del->_left;if (del->_left)del->_left->_parent = delP;}}delete del;return true;}

九、红黑树与AVL树的比较

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


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

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

相关文章

Flink状态

8.1 Flink中的状态 8.1.1 概述 状态的分类 1&#xff09;托管状态&#xff08;Managed State&#xff09;和原始状态&#xff08;Raw State&#xff09; Flink的状态有两种&#xff1a;托管状态&#xff08;Managed State&#xff09;和原始状态&#xff08;Raw State&#…

Zookeeper-集群介绍与核心理论

Zookeeper集群 4.Zookeeper集群4.1) 介绍4.2) 核心理论 4.Zookeeper集群 4.1) 介绍 Leader选举&#xff1a; Serverid&#xff1a;服务器ID。比如有三台服务器&#xff0c;编号分别是1,2,3。编号越大在选择算法中的权重越大。Zxid&#xff1a;数据ID。服务器中存放的最大数据…

idea中导入eclipse项目后没显示src文件

因为没有导入modules 选择import module 选择整个项目 选择eclipse 一直下一步就行 这样就出来了

【挑战开发100个项目 | 1. C语言学生管理系统】

本项目是一个简易的学生信息管理系统&#xff0c;用户可以通过命令行界面完成学生信息的增加、删除、修改、查询、排序和列表展示等功能。数据以txt文件形式存储&#xff0c;实现了数据持久化。项目采用模块化设计&#xff0c;具有较好的可读性和扩展性。适用于初学者学习c语言…

JDK21新特性 有序集合

有序集合 描述常用有序集合体系LinkedHashMapLinkedHashSetLinkedBlockingDequeArrayDeque 三级目录 描述 Java集合体系中&#xff0c;原来就有有序集合实现&#xff0c;但是没有规范支持有序操作的接口。 JDK21 新增了两个接口 SequencedCollection&#xff0c;SequencedMa…

知识图谱:信息抽取简易流程

目录 一、标注训练数据 二、训练数据模型 三、实现NER 一、标注训练数据 使用工具:Brat ## BRAT安装 0、安装条件 (1)运行于Linux系统 (2)brat(v1.3p1)仅支持python2版本运行使用,否则会报错 File "standalone.py", line 257except SystemExit, sts:^Syn…

HTML+VUE+element-ui通过点击不同按钮展现不同页面

完整源码可以私聊我&#xff0c;需要一点点费用~ 页面展示 方案一 方案二 代码解释 方案一&#xff1a;使用v-show 这种方案只适合有两种页面 现在form表单中设置好要展现的页面名&#xff08;此处的url没有作用不用管&#xff09; 通过一个两个for循环分别将form表单填入…

PWN环境搭建

虚拟机Ubuntu安装 工具&#xff1a;Vmware 16 以及 Ubuntu 18或20 来源&#xff1a;清华大学开源软件镜像站 | Tsinghua Open Source Mirror 虚拟机安装流程 安装很简单&#xff0c;按照提示一步步来即可 处理器可以多给一些&#xff0c;我给了8个&#xff0c;内核数量不…

004:vue使用relation-graph实现关系图谱

文章目录 1. 效果2. relation-graph简介3. 安装及使用4. 其他更多示例 1. 效果 2. relation-graph简介 这是一个Vue关系图谱组件&#xff0c;可以展示如组织机构图谱、股权架构图谱、集团关系图谱等知识图谱&#xff0c;可提供多种图谱布局&#xff0c;包括树状布局、中心布局…

ChatGPT推出全新功能,引发人工智能合成声音担忧|百能云芯

人工智能AI科技企业OpenAI公司25日宣布&#xff0c;其聊天应用程序ChatGPT如今具备「看、听、说」能力&#xff0c;至少能够理解口语、用合成语音回应并且处理图像&#xff1b;但专家忧心&#xff0c;以假乱真与深度伪造的乱象可能变本加厉。 国家广播公司新闻网(NBC News)报导…

全新UI基于Thinkphp的最新自助打印系统/云打印小程序源码/附教程

这是一款全新的基于Thinkphp的最新自助打印系统&#xff0c;最新UI界面设计的云打印小程序源码&#xff0c;带有简单的教程。 下载地址&#xff1a;https://bbs.csdn.net/topics/617324130

简述ceph文件储存系统

Ceph 是一个统一的分布式存储系统和共享机制&#xff0c;它定义了数据如何存储在一个或多个节点上并呈现给其他机器以供文件访问。 Ceph特点 高性能 a. 摒弃了传统的集中式存储元数据寻址的方案&#xff0c;采用CRUSH算法&#xff0c;数据分布均衡&#xff0c;并行度高。 b.考…

WSL2编译安卓11源码,,刷入pixel设备,并使用asfp查看源码

目录 WSL2编译安卓11源码,&#xff0c;刷入pixel设备源码下载驱动下载编译刷机源码导入Android Studio for platformADB调试 WSL2编译安卓11源码,&#xff0c;刷入pixel设备 aosp编译完成后&#xff0c;刷入手机其实非常简单&#xff0c;但是使用wsl有一个问题&#xff0c;就是…

crypto:变异凯撒

题目 下载题目所给的压缩包后解压得到文本提示 由题目名可知为凯撒密码 根据提示格式为flag{}&#xff0c;所以猜测前四个字符原文为flag 先来推测一下偏移量 a->f 偏移量为-5&#xff0c;按道理来说每个字符的偏移量都是一样的&#xff0c;但是对照过后发现后面的字符对…

Java面向对象高级

文章目录 面向对象高级Object类的常用方法常用方法一&#xff08;面向对象阶段&#xff09;** 和 equals 的区别** 关键字native**单例设计模式&#xff08;Singleton&#xff09;**前情回顾&#xff08;学习基础&#xff09;静态修饰符Static设计模式概念开发步骤**两种实现方…

springboot集成quartz并实现定时任务管理

依赖&#xff1a; <quartz.version>2.3.0</quartz.version><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>${quartz.version}</version><exclusions><exclus…

“的修“报修工单管理系统有哪些功能和作用?

“的修"报修工单管理系统是基于微信平台的&#xff0c;无需下载和安装&#xff0c;只需扫码即可使用。它是一个维保过程管控的理想解决方案&#xff0c;适用于企业、学校、医院、物业等单位需要设备维保的场景。“的修"报修小程序具有独立部署和可控的数据安全性&…

手把手教你实现:将后端SpringBoot项目部署到华为云服务器上

前言 前提&#xff1a;有一个后端项目&#xff0c;项目能够运行在本地&#xff0c;可以通过本地访问&#xff08;localhost&#xff09; 如果没有可以看这篇&#xff1a;一个基于SpringBoot的后端项目 注册华为云账号 华为云官网 购买云服务器 产品 -> 华为云耀云服务器…

【数据结构-树】哈夫曼树

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

图片编辑小程序源码/拼图小程序源码

图片编辑小程序源码&#xff0c;拼图小程序源码。全能、便捷的图片编辑工具。实现了图片裁剪、添加文字、涂鸦、拼长图、拼相框等图片编辑功能&#xff0c;另外还有一个简易的表情包制作功能。 主要有以下几个功能&#xff1a;图片裁剪、添加文字、涂鸦功能、拼长图、拼相框、表…