红黑树也是搜索树的一种,是一种弱平衡二叉树,它的出现是为了解决平衡二叉
树(AVL 树)不适合频繁进行插入和删除的场景
,因为 AVL 树对平衡性要求很高,所以插入和删除都需要进行平衡处理。而红黑树加入了节点颜色标志,可以减少这种强平衡要求,在一些需要频繁插入和删除的结构中经常使用。例如,C++的 STL 中的set,map,以及 linux 的虚拟内存管理都是通过红黑树去实现的。
红黑树的结构特性
1. 每一个节点要么是红色的要么是黑色的。
2. 根节点是黑色的。
3. 每个叶子节点是黑色的,注意这里说的叶子节点是指为空的叶子节点
4. 如果一个节点是红色的,则它的子节点必须是黑色的
5. 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点,所以没有
一条路径会比其他路径长出两倍,因而,红黑树是相对是接近平衡的二叉树
红黑树的时间复杂度为 O(lgn)
红黑树的结构定义
1.
enum
Color // RED = 0
红,
BLACK = 1
黑
2.
{
3.
RED = 0;
4.
BLACK = 1;
5.
};
6.
7.
struct
RBTreeNode
8.
{
9.
struct
RBTreeNode*left, *right, *parent; //left 左孩子,right
右孩子
,parent
父结点
10.
int
key; //
其他信息,如高度,结点数
11.
int
data; //
数据域
12.
Color color; //
颜色域
13.
};
红黑树的元素插入步骤
第一步:将红黑树当作一颗二叉查找树,将节点插入
第二步:将插入的节点着色为“红色”
为什么要将插入节点初始化为红色节点呢,因为这样首先不会违背红黑树的第五条特性:从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。如果插入的节点是“黑色”的话将直接违背结构特性。
第三步:通过一系列的旋转或者着色操作,使之重新成为一颗符合规定的红黑树
在进行第二步之后,插入的红色节点虽然不会违背特性 5,但是其他特性我们来分析一下
对于特性 1,显然是不会违背,对于特性 2,只要插入节点不是根节点也不会违背,对于特性 3,因为叶子节点都是空节点,所以也不会违背,对于特性 4,有可能插入的父节点原本是“红色”的,所以可能会违背,那么我们就是通过旋转着色处理使特性 4 也得到满足就好了
插入情形处理
情形 1.插入节点是根节点
将节点颜色域变黑色就行
情形 2.父节点是黑色
无须操作
情形 3.父节点红叔节点(父节点的兄弟节点)红
将父节点和叔节点涂黑,祖父节点(父节点的父节点)涂红
情形 4.父红叔黑
这个时候分为父节点和待插入节点是否在同一边,也就是父节点是右孩子或左孩子,待插入节点也是右孩子或左孩子
情形 4.1.父节点和待插入节点在同一边
这个时候又分同在左边还是同在右边
情形 4.1.1.同在左边
对祖父节点进行右旋操作,然后将父节点涂黑,将祖父节点涂红,也可以说是祖父节点和父节点颜色互换
情形 4.1.2.同在右边
对祖父节点进行左旋操作,然后将父节点涂黑,将祖父节点涂红,也可以说是祖父节点和父节点颜色互换
情形 4.2.父节点和待插入节点不在同一边
在个时候又分为父节点是左孩子,待插入节点插入在右孩子位置或者父节点是右孩子,待插入节点插入在左孩子位置
情形 4.2.1.父左 N 右
这个时候对父节点先进行左旋处理,颜色不用变,这个时候就变成了情形 4.1.1 的场景了,然后进行情形 4.1.1 的操作即可
情形 4.2.2.父右 N 左
这个时候对父节点先进行右旋处理,颜色不用变,这个时候就变成了情形 4.1.2 的场景了,然后进行情形 4.1.2 的操作即可
例图:以 10,20,15,30,5,8,构建红黑树
红黑树的元素删除步骤
情形 1:删除节点没有子节点,也就是没有孩子
情形 1.1:删除的节点是红色的直接删除掉,不会影响黑色节点的数量
情形 1.2:删除的节点是黑色的需要对删除的节点进行平衡处理,在后面进行分析操作
情形 2:
当删除的节点只有一个子节点时(只要一个子节点的节点的颜色一定是黑色的,且子节点颜色一定是红色的,因为这样才符合红黑树的特性),那么用待删除节点的子节点接到父节点,然后把子节点变成黑色节点。
情形 3:
当删除的节点有两个子节点时,与二叉搜索树一样,使用后继节点作为替换
的删除节点,情形转至为 1 或 2 处理。
例图:
我们发现,删除情形 3 总是会转换为情形 1 和 2 的,而情形 1.1 和情形 2 处理平衡非常简单,后面主要讨论的是情形 1.2:删除黑色的叶子节点
。因为一旦该节点被拿掉,红黑树中通过该节点的路径黑色节点数量将会减 1,而且无法像情形 2 那样将子节点涂黑来达到平衡。此时只能自底向上进行平衡操作。
我们先约定好节点的名称
情形 1:删除节点为根节点
直接删除,无须操作
情形 2:兄弟节点为黑色(S = 黑)
情形 2.1.兄弟节点全为黑色(SL&SR=黑)
兄弟节点的子节点全为黑色,也就意味着兄弟节点(S)变红不会和子节点冲突,s 涂红后就实现了平衡,但是在这个时候我们又要看父节点(P)是红色还是黑色
情形 2.1.1.父节点是黑色(P=黑)
则父节点(P)作为新的平衡节点 N, 但是有 p 到叶子节点的黑节点数却比删除前少 1了,也就是 h(GP->P->叶子)会比 h(GP -> U ->叶子)少 1,所以需要递归上去进行平衡处理
情形 2.1.1.父节点是红色(P=红)
此时将 S 涂红后,P 涂黑,由 p 到叶子节点的黑节点数没变,所以不用递归到上面处理,平衡结束
情形 2.2:兄弟的子节点不全黑
所谓的不全黑包括:[SL 红, SR 红]、[SL 黑,SR 红]、[SL 红,SR 黑]。以全黑/非全黑作为分类,是因为全黑时无论 N 是在左子还是右子,其处理方式是一样的。而非全黑则要看 N 所处的位置(或者说 S 所处的位置)进行特定方向的旋转。
为了方便理解和记忆,以 S 进行分组:
S 为左子时(即 N 为右子),主要分两组 [SL=红]、[SL=黑]。
S 为右子时(即 N 为左子),主要分两组 [SR=红]、[SR=黑]。
【S 为左子,SL 红】与【S 为右子,SR 红】处理方式对称;
【S 为左子,SL 黑】与【S 为右子,SR 黑】处理方式对称。
情形 2.2.1:S 为左子,SL 红;S 为右子,SR 红
情形(1):S 为黑色,S 为左子,SL 红时
以 P 为支点右旋,交换 P 和 S 的颜色,SL 涂黑,平衡结束
对称的情形(2):S 为黑色,S 为右子,SR 红时:
以 P 为支点左旋;交换 P 和 S 颜色(S 涂为 P 原颜色,P 涂黑),SR 涂黑;平衡结束。
情形 2.2.2:S 为左子,SL 黑;S 为右子,SR 黑
情形(1) :S 为黑色,S 为左子,SL 黑
以 S 为支点左旋,交换 S 和 SR 颜色(SR 涂黑,S 涂红) ,此时转至情形 2.2.1-(1) S 左-SL 红 进行处理。
对称的情形(2) S 为黑色,S 为右子,SR 黑
以 S 为支点右旋,交换 S 和 SL 颜色(SL 涂黑,S 涂红),此时转至 2.2.1-(1) S 右-SR红进行处理。
情形 3:兄弟节点为红色(S=红)
情形(3.1):S 为左子时,以 P 进行右旋
情形(3.2):S 为右子时,以 P 进行左旋
旋转后交换 P 和 S 的颜色(S 涂黑,P 涂红),N 兄弟节点变为黑色,进入情形 2-兄弟节点为黑色进行处理。
删除图例:
有一颗红黑树如下
1.删除根节点元素 50
2.再删除 70,即删除黑色叶子节点
3.再删除 60
4.再删除 10
5.再删除 20
红黑树在面试中的经典问题
1.红黑树用于 C++中 STL 中的哪些类?
答:set 和 map
2.红黑树的性质
1.每一个节点要么是红色的要么是黑色的。
2.根节点是黑色的。
3.每个叶子节点是黑色的,注意这里说的叶子节点是指为空的叶子节点
4.如果一个节点是红色的,则它的子节点必须是黑色的
5.从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点,所以没有一条路径会比其他路径长出两倍,因而,红黑树是相对是接近平衡的二叉树
3.红黑树的查找操作的时间复杂度是多少?
答:能保证在最坏情况下,基本的动态几何操作的时间均为 O(log2
n)
4.红黑树相比于 BST 和 AVL 树有什么优点?
答:红黑树相对于 AVL 树,牺牲了严格的平衡性,所以在查找效率是没有 AVL 树高,
但是也正是因为牺牲了严格平衡性,使得红黑树在插入操作和删除操作可以不需要像
AVL 树一样去花大量开销去进行平衡操作。