红黑树简析
创始人
2024-05-23 17:27:12
0

一.   概念

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


 二.   性质

1. 每个结点不是红色就是黑色

2. 根节点是黑色的

3. 如果一个节点是红色的,则它的两个孩子结点是黑色的

4. 对于每个结点,从该结点到其所有后代叶结点的简单路径上,均 包含相同数目的黑色结点

5. 每个叶子结点都是黑色的(此处的叶子结点指的是空结点)

由于从一个节点起的每条路径中的黑色节点数目相同,因此最短为全黑,同时由于红节点不连续,因此最长为一半红一半黑,因此不会超出两倍


三.   节点定义

三叉链,key-value模型、额外的标明颜色的变量(颜色可以使用枚举常量)

同时,为了在插入时遵循第4条准则,我们将颜色变量的默认值设为红色

enum Colour
{RED,BLACK
};template
struct RBTreeNode
{RBTreeNode* _left;RBTreeNode* _right;RBTreeNode* _parent;pair _kv;Colour _col;RBTreeNode(const pair& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _col(RED), _kv(kv){}
};

四.   基本框架

template
struct RBTree
{typedef RBTreeNode Node;
public:RBTree():_root(nullptr){}
private:Node* _root;
};

五.   插入

首先,一开始与AVL树是一致的,都是通过比较key来寻找合适的位置进行插入

bool Insert(const pair& kv)
{if (!_root){_root = new Node(kv);_root->_col = BLACK;return false;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first == kv.first)return false;else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{parent = cur;cur = cur->_left;}}cur = new Node(kv);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;
}

之后,虽然我们插入红色节点会使得每条路径的黑节点数目相同,但还要注意在红节点连续时进行相应的调整

在调整时,依旧是从插入节点(cur)向上迭代来观察,而我们需要进行调整的情况为cur和parent的颜色都为红,主要分为这两种情况

同样,和AVL树一样,也有对称的情况,但操作方法也都是类似的,不多说了

而这两种情况,我们对子树4的情况进行细分,首先当节点1的右节点为红色时

 处理方法都是一样的,只需要将节点2、4的颜色改为黑色,同时,我们在改变结构时,为了减小对上面的影响,应该保证前后的路径中黑节点的数目相同,改色之前,黑节点数目为n+1,因此,我们还需要将节点1改为红色

而由于我们将节点1的颜色改为了红色,我们需要继续向上判断

while (parent&&parent->_col == RED)
{Node* par_parent = parent->_parent;Node* uncle = nullptr;if (parent == par_parent->_left)uncle = par_parent->_right;elseuncle = par_parent->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;par_parent->_col = RED;cur = par_parent;parent = cur->_parent;}
}

 

还有两种情况,是节点4不存在或节点4为黑色,这两种情况的处理情况相同,由于不需要对节点4进行处理,我们就简化一下

首先是情况1

 首先若是我们想要改变颜色,首先对于节点2,右侧路径黑节点为n个,因此左侧也应该为n个,所以节点3的颜色不能改变,因此我们只能改变节点2,这样对于节点1,两侧路径的黑节点数目就不可能相同了,因此,我们可以使用在AVL树中使用到的旋转,很容易看出,我们需要使用右旋(对称情况使用左旋)

 之后,便可以来改变颜色,首先,有两种方案,节点3改为黑色或节点1改为红色、节点2改为黑色,原本路径中黑节点数为n+1,这两种方案改变后都符合,但由于若是我们将节点2颜色改为黑色,与改变前类似,也就不需要继续向上判断了,因此采用第二种方法

else
{if (cur == parent->_left && parent == par_parent->_left){RotateR(par_parent);parent->_col = BLACK;par_parent->_col = RED;}else if (cur == parent->_right && parent == par_parent->_right){RotateL(par_parent);parent->_col = BLACK;par_parent->_col = RED;}break;
}

而左旋和右旋和AVL树是一样的 

void RotateL(Node* parent)
{Node* par_parent = parent->_parent;Node* right = parent->_right;Node* right_left = right->_left;if (par_parent){if (parent == par_parent->_left)par_parent->_left = right;elsepar_parent->_right = right;}else{_root = right;}right->_parent = par_parent;if (right_left)right_left->_parent = parent;parent->_right = right_left;right->_left = parent;parent->_parent = right;
}void RotateR(Node* parent)
{Node* par_parent = parent->_parent;Node* left = parent->_left;Node* left_right = left->_right;if (par_parent){if (parent == par_parent->_left)par_parent->_left = left;elsepar_parent->_right = left;}else{_root = left;}left->_parent = par_parent;if (left_right)left_right->_parent = parent;parent->_left = left_right;left->_right = parent;parent->_parent = left;
}

 

之后便是情况2

同样只是改变颜色无法实现平衡 

我们就需要进行双旋

之后就需要改变颜色

还是将节点3变黑,节点1变红,不需要向上调整

else
{if (cur == parent->_left && parent == par_parent->_left){RotateR(par_parent);parent->_col = BLACK;par_parent->_col = RED;}else if (cur == parent->_left && parent == par_parent->_right){RotateR(parent);RotateL(par_parent);cur->_col = BLACK;par_parent->_col = RED;}else if (cur == parent->_right && parent == par_parent->_right){RotateL(par_parent);parent->_col = BLACK;par_parent->_col = RED;}else{RotateL(parent);RotateR(par_parent);cur->_col = BLACK;par_parent->_col = RED;}break;
}

而在最后,头结点可能会被我们改变为红色,我们只需要将其手动变为黑就好了,不会对下面产生影响

总览

bool Insert(const pair& kv)
{if (!_root){_root = new Node(kv);_root->_col = BLACK;return false;}Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_kv.first == kv.first)return false;else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else{parent = cur;cur = cur->_left;}}cur = new Node(kv);if (parent->_kv.first < kv.first)parent->_right = cur;elseparent->_left = cur;cur->_parent = parent;while (parent&&parent->_col == RED){Node* par_parent = parent->_parent;Node* uncle = nullptr;if (parent == par_parent->_left)uncle = par_parent->_right;elseuncle = par_parent->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;par_parent->_col = RED;cur = par_parent;parent = cur->_parent;}else{if (cur == parent->_left && parent == par_parent->_left){RotateR(par_parent);parent->_col = BLACK;par_parent->_col = RED;}else if (cur == parent->_left && parent == par_parent->_right){RotateR(parent);RotateL(par_parent);cur->_col = BLACK;par_parent->_col = RED;}else if (cur == parent->_right && parent == par_parent->_right){RotateL(par_parent);parent->_col = BLACK;par_parent->_col = RED;}else{RotateL(parent);RotateR(par_parent);cur->_col = BLACK;par_parent->_col = RED;}break;}}_root->_col = BLACK;return true;
}void RotateL(Node* parent)
{Node* par_parent = parent->_parent;Node* right = parent->_right;Node* right_left = right->_left;if (par_parent){if (parent == par_parent->_left)par_parent->_left = right;elsepar_parent->_right = right;}else{_root = right;}right->_parent = par_parent;if (right_left)right_left->_parent = parent;parent->_right = right_left;right->_left = parent;parent->_parent = right;
}void RotateR(Node* parent)
{Node* par_parent = parent->_parent;Node* left = parent->_left;Node* left_right = left->_right;if (par_parent){if (parent == par_parent->_left)par_parent->_left = left;elsepar_parent->_right = left;}else{_root = left;}left->_parent = par_parent;if (left_right)left_right->_parent = parent;parent->_left = left_right;left->_right = parent;parent->_parent = left;
}

六.   验证 

首先就是验证是否为搜索树,和AVL树的一致

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == NULL)return;_InOrder(root->_left);cout << root->_kv.first << ":" << root->_kv.second <<':' << root->_col << "  ";_InOrder(root->_right);
}

之后,还需要验证三个问题:根节点是否为黑,路径中的黑节点数目是否相同、红节点是否不连续

首先第一点很好验证,若是验证第二点,我们可以先得到一条路径的黑节点,之后以它为基准,对每条路径进行判断,最后在遍历路径的时候顺便对红节点是否连续做一下判断

bool IsBalance()
{if (_root && _root->_col == RED){cout << "根节点不是黑色" << endl;return false;}int banchmark = 0;Node* left = _root;while (left){if (left->_col == BLACK)banchmark++;left = left->_left;}int blackNum = 0;return _IsBalance(_root, banchmark, blackNum);
}bool _IsBalance(Node* root, int& banchmark, int blackNum)
{if (!root){if (banchmark != blackNum){cout << "存在路径黑色节点的数量不相等" << endl;return false;}return true;}if (root->_col == RED && root->_parent->_col == RED){cout << "出现连续红色节点" << endl;return false;}if (root->_col == BLACK){++blackNum;}return _IsBalance(root->_left, banchmark, blackNum)&& _IsBalance(root->_right, banchmark, blackNum);
}

end


 


 

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...