红黑树实现原理

本文深入讲解红黑树的定义、性质、与AVL树的区别,以及插入、删除节点的详细算法。通过具体案例分析,帮助读者理解红黑树的动态平衡调整机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、红黑树介绍

1、定义

  1. 每个结点或者为黑色或者为红色
  2. 根结点为黑色
  3. 每个叶结点(实际上就是NULL指针)都是黑色的
  4. 如果一个结点是红色的,那么它的两个子节点都是黑色的(也就是说,不能有两个相邻的红色结点
  5. 对于每个结点,从该结点到其所有子孙叶结点的路径中所包含的黑色结点数量必须相同

2、二叉树和AVL树比较

1、二叉树

  • 任意节点左子树不为空,则左子树的值均小于根节点的值.
  • 任意节点右子树不为空,则右子树的值均大于于根节点的值.
  • 任意节点的左右子树也分别是二叉查找树
  • 没有键值相等的节点.
    由二叉查找树的性质可知,构成二叉树的结构和插入数据的顺序有关,存在不稳定性。最好情况可以构建成平衡二叉树查找效率O(lgn),最坏情况也有可能构造成线性树(查找效率O(n))
    二叉树
    2、AVL树
    AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡。
    左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。
    AVL树的插入 删除元素必须保持绝对的平衡,所以从插入删除元素起,要一直进行旋转操作一直到根节点,所以
    插入/删除/查找操作比较稳定,时间复杂度是O(logn)
    AVL树
    3、红黑树
    红黑树也是一种平衡二叉树,但是它与AVL树相比是一种相对宽松的平衡,平衡因子是从任意非叶子节点到其所有叶子节点的路径上黑色节点保持一致(红黑树性质5),所以当非叶子节点A到其一条路径的叶子节点包含n个黑色节点,则A到其他路径的所有节点最多只能为2n个,以此来保证易中动态平衡。
    这种平衡与AVL树相比能减少 增加和删除节点时的时间消耗,因为红黑树性质保证了新增删除节点复杂度介于O(1)到O(logn)之间;
    查找时,红黑树时间复杂度也为O(logn),相比AVL树的O(logn)只是多了一点系数,相对会慢一点。
    但是综合增加、删除和查找性能看,红黑树还是优于AVL树的。

二、红黑树实现

1、左旋、右旋

在红黑树中新加节点和删除节点时,如果需要调整红黑树时就会涉及到节点的左旋和右旋。
左旋和右旋是一组对称的操作
左旋右旋

1.1、左旋

左旋静态实现
左旋
动态实现
左旋
如上图,E-S轴指向右下方,向左旋转E-S轴时其指向右上方

1.2、右旋

静态实现
右旋
动态实现
右旋
E-S轴向右旋转

2、插入节点

2.1、插入过程

1)首先找插入位置。这个和二叉树流程一样,从根开始遍历到叶子结点找到该插入的位置。
2) 插入值节点(红色)
3) 确认插入值是否影响红黑树性质,影响就要做调整操作。

/*插入伪代码*/
B-INSERT(T, z)

/*找待插入位置*/
y ← nil
x ← T.root
while x ≠ T.nil
	do y ← x
	if z.key < x.key
		then x ← x.left
	else x ← x.right
	
/*插入元素*/
z.p ← y
if y == nil[T]
	then T.root ← z
else if z.key < y.key
	then y.left ← z
else y.right ← z
z.left ← T.nil
z.right ← T.nil
z.color ← RED

/*调整红黑树*/
RB-INSERT-FIXUP(T, z)

插入节点的颜色选黑色,必定会破坏性质5(黑色节点增加),可能需要一层一层调整红黑树 ,而插入颜色选红色,则有一半几率破坏性质4(父子节点都为红), 而且性质4调整比5调整更加简单,所以插入颜色选择红色。

2.2、插入调整

如果待插入节点的父节点为黑,则插入红色不破坏红黑树性质,不需要调整,所以需要调整必然情况必然是父节点为红色。

RB-INSERT-FIXUP(T, z)
while z.p.color == RED
	do if z.p == z.p.p.left
		then y ← z.p.p.right
		if y.color == RED
			then z.p.color ← BLACK               ▹ Case 2
			y.color ← BLACK                    ▹ Case 2
			z.p.p.color ← RED                    ▹ Case 2
			z ← z.p.p                            ▹ Case 2
		else if z == z.p.right
			then z ← z.p                          ▹ Case 3
			LEFT-ROTATE(T, z)                   ▹ Case 3
		z.p.color ← BLACK                        ▹ Case 4
		z.p.p.color ← RED                         ▹ Case 4
		RIGHT-ROTATE(T, z.p.p)                  ▹ Case 4
	else (same as then clause with "right" and "left" exchanged)
T.root.color ← BLACK

现分以插入节点的父节点是左子树的几种情况(右子树同理):
1、如果树是空树,则插入的是根。
调整方案: 则直接颜色涂黑(终态)。

2、父节点是红,叔叔节点是红。
调整方案: 则父节点和叔叔节点都涂黑,祖父节点图红(将红从父辈转嫁到祖辈),将祖父节点设为待插入节点继续递推(调整为初始态)。

调整前:N为插入元素
在这里插入图片描述
调整后:
在这里插入图片描述
3、父节点是红,叔叔节点是黑,待插入节点是右子树。
调整方案:将当前节点的父节点作为新的当前节点,以新当前节点左旋(调整为状态4)。

调整前:
在这里插入图片描述
调整后:
在这里插入图片描述
4、父节点是红,叔叔节点是黑,待插入节点是左子树。
调整方案: 将祖父节点设为红,父节点设为黑,以祖父节点右旋,相当于把父节点这边的多余红色转移到了叔叔节点那一侧,修复了红黑树性质(终态)。

调整前:
在这里插入图片描述
调整后:
在这里插入图片描述

3、删除节点

定义: 删除节点是D, 删除节点的父节点是P, 删除节点的叔叔节点是U, 删除节点的孩子节点是X

3.1、删除过程

1、定位删除节点并删除

  1. 删除节点D没有子节点,直接删除。
  2. 删除的节点只有一个子节点,则删除。
  3. 删除的节点有两个子节点,则找到这个结点的后继结点(successor),也就是它的右子树中最小的那个结点。然后我们将这两个节点中的数据元素互换,将后继节点作为待删除节点删除。(后继节点必然没有左子树,所以将情况转换成2)

2、调整删除节点的父节点P和子节点X的位置

  1. 如果删除节点D没有孩子节点,且他是根,则置树为空,否则D的父节点P指向D的指针置空。
  2. 如果删除节点D有孩子节点X,且他是根,则将X置为根节点,否则调整父节点P的孩子指针, 由原来指向D的指针指向X

删除节点

 /*确定删除节点y,如果删除节点z有左右子树则找z的后继节点,否则z作为待删除节点y*/
 1 if left[z] = nil[T] or right[z] = nil[T]  
 2    then y ← z  
 3    else y ← TREE-SUCCESSOR(z)  
 
/*确定待删除节点y的子节点x*/
 4 if left[y] ≠ nil[T]  
 5    then x ← left[y]  
 6    else x ← right[y]  
 
  /*x替代待删除的y节点*/
 7 p[x] ← p[y]  
 8 if p[y] = nil[T]  
 9    then root[T] ← x  
10    else if y = left[p[y]]  
11            then left[p[y]] ← x  
12            else right[p[y]] ← x  

/*如果y是z的后继节点,则需要把y的数据拷贝到原待删除节点z上*/
13 if y ≠ z  
14    then key[z] ← key[y]  
15         copy y's satellite data into z  

/*如果待删除节点y是黑色的,则需要做调整*/
16 if color[y] = BLACK  
17    then RB-DELETE-FIXUP(T, x)  
18 return y  
3.2、红黑树调整

因为删除了一个D节点,如果D节点是红色的话,不影响红黑树任何性质,所以可以不做调整,但是如果D是黑色,则D这一支子树就会少一个黑节点,影响性质5,而且P的颜色和X的颜色如果都是红色也会影响性质4,所以需要调整。所以调整时默认D是黑色,后面假设条件不做说明。
所以我们的思路是 可以将删除的D节点的黑色标记附加在X上,把X节点叫做标记节点,颜色可以说成是红+黑或者黑+黑,暂时保证性质5,然后我们在慢慢调整,将X上附加的颜色一层层往上推,最终调整红黑树保持平衡。

红黑树删除调整,必然是X节点为黑(额外带了删除的黑节点,即黑+黑), 而关键点在于兄弟节点U和U的两个儿子Ul,Ur的颜色情况。
case1: X是红, 调整结束;

case2: X是(黑+黑), U是红(由已知条件得出 Ul=Ur=黑), 则想办法让X的兄弟变黑,即U置黑,P置红,P点左旋,然后Ul就变成了X的兄弟U,然后新U的儿子情况最终就只能变成4种(黑黑,红黑, 红红, 黑红),然后衍生出了 剩下的case3、case4、case5(Ur为红即可)

case3: X是(黑+黑),U,Ul, Ur是黑,则将U置红,X向上推进到P,这是能推进的情况,最终走向case1或者case5结束调整;

case4: X是(黑+黑), U是黑,Ul红,Ur黑, 这种情况只能演变为Ur是红 即case5,即 U变红,Ul变黑,然后以U右旋;

case5:X是(黑+黑), U是黑,Ul任意,Ur红, 这种最终能走向调整结束,即把U的黑色给Ur,P的颜色给U,P再继承X上多余的黑色,然后以U左旋让U代替P的位置,Ul变为P的右结点,最终达到平衡。

 1 while x ≠ root[T] and color[x] = BLACK  
 2     do if x = left[p[x]]  
 3           then w ← right[p[x]]  
 4                if color[w] = RED  
 5                   then color[w] ← BLACK                        ▹  Case 2
 6                        color[p[x]] ← RED                       ▹  Case 2
 7                        LEFT-ROTATE(T, p[x])                    ▹  Case 2 
 8                        w ← right[p[x]]                         ▹  Case 2
 9                if color[left[w]] = BLACK and color[right[w]] = BLACK  
10                   then color[w] ← RED                          ▹  Case 3  
11                        x ← p[x]                                ▹  Case 3 
12                   else if color[right[w]] = BLACK  and  color[left[w]]  = RED case 4
13                           then color[left[w]] ← BLACK          ▹  Case 4
14                                color[w] ← RED                  ▹  Case 4
15                                RIGHT-ROTATE(T, w)              ▹  Case 4  
16                                w ← right[p[x]]                 ▹  Case 4
17                         color[w] ← color[p[x]]                 ▹  Case 5 
18                         color[p[x]] ← BLACK                    ▹  Case 5 
19                         color[right[w]] ← BLACK                ▹  Case 5  
20                         LEFT-ROTATE(T, p[x])                   ▹  Case 5  
21                         x ← root[T]                            ▹  Case 5 
22        else (same as then clause with "right" and "left" exchanged)  
23 color[x] ← BLACK   Case1

1、如果标记节点X是红+黑或者X是根节点。
调整: 直接将X设置成黑色,然后调整结束。

  • 因为X若是红色,则可以把标记附加色设置进来,则D子树的黑色节点数没变,保证了性质5,同时也避免了X和P同时为红的情况保证了性质4。
  • 因为X若是根节点,则不管X原色是啥,都必须直接置黑,同时因为已经递推到了根,必然已经保证了性质5,所以附加的黑节点可以直接丢弃。

***后面几种情况都是讨论X是叶子节点的情形(D无子节点) 即 X是黑色,且X不是根结点。因为D只有一个子节点或者没有子节点,所以由性质5知 black_num(P->D->X) = black)num(P->D->nil),所以X必为叶子节点 ***

2、如果标记节点X是黑+黑,且叔叔节点U是红色,叔叔节点U必有左右孩子且为黑色。
调整:将父节点P设成红色,叔叔节点U设为黑色,然后以P为节点左旋,之后U的左孩子作为P的右孩子替代U,且U为黑色。这样调整主要是让U节点满足黑色条件,然后继续进入调整流程。

调整前:
调整前

调整后:
调整后
3、如果标记节点X是黑+黑,且叔叔节点U是黑色,叔叔节点的孩子节点全是黑色。

调整: 将叔叔节点U置成红色,同时X上的附加黑色上移到P节点,然后以P节点作为当前节点,继续执行调整操作。

因为将U节点置成红色,则保证了P的左右子树黑色总数一致,然后以P为当前节点继续推进,但叔叔节点U置成红色的前提是2个孩子节点都是黑色。

调整前:(其中的X左边的黑色为附加色)
调整前
调整后:
调整后

4、如果标记节点X是黑+黑,且叔叔节点U是黑色,叔叔节点的左孩子是红色,右孩子是黑色。
调整: 将叔叔节点U置成红色,U的左孩子置成黑色,然后以U为节点右旋,最终U的左孩子变成U节点且为黑色,U的右孩子为红色。然后重新进入调整算法。

调整前:
调整前
调整后:
调整后

5、如果标记节点X是黑+黑,且叔叔节点U是黑色,右孩子是红色色,左孩子是任意色。

调整: 将U的右子节点设置成黑色,叔叔节点U的颜色设置成父节点P的颜色,父节点P的颜色置黑(X上的附加黑色设置到P),然后以P为节点左旋。这种调整实际上将U子树多余的红节点移入到X子树,保证了性质5,最终整个红黑树保持平衡,到达终态。

调整前:
调整前
调整后:
调整后

参考:
1、红黑树(red-black tree)算法
2、教你初步了解红黑树
3、左右旋图参考

<think>我们被要求解释红黑树实现原理并提供C++代码示例。根据引用,红黑树是一种自平衡二叉搜索树,常用于实现关联容器如map和set(C++STL中的map和set底层就是红黑树)。红黑树通过约束节点的颜色和旋转操作来保持树的平衡,从而保证在最坏情况下基本操作(插入、删除、查找)的时间复杂度为O(log n)。红黑树的五个基本性质(规则):1.每个节点要么是红色,要么是黑色。2.根节点是黑色的。3.每个叶子节点(NIL节点,空节点)是黑色的。4.如果一个节点是红色的,则它的两个子节点都是黑色的(即不能有两个连续的红色节点)。5.从任意节点到其每个叶子节点的所有路径都包含相同数目的黑色节点(称为黑高相同)。当插入或删除节点时,可能会破坏这些性质,因此需要通过旋转和重新着色来调整。旋转操作:旋转分为左旋和右旋,用于调整树的结构而不破坏二叉搜索树的性质(即中序遍历有序)。左旋:将当前节点(设为x)的右子节点(设为y)提升为新的父节点,x成为y的左子节点,而y原来的左子节点成为x的右子节点。右旋:将当前节点(设为x)的左子节点(设为y)提升为新的父节点,x成为y的右子节点,而y原来的右子节点成为x的左子节点。插入操作:新插入的节点默认为红色(这样可以不破坏性质5,但可能破坏性质2或4)。插入后,根据父节点、叔节点和祖父节点的颜色进行调整。调整分为三种情况(以父节点是祖父节点的左孩子为例,对称情况类似):情况1:叔节点为红色。则将父节点和叔节点变为黑色,祖父节点变为红色,然后将祖父节点作为当前节点继续调整。情况2:叔节点为黑色,且当前节点是父节点的右孩子。则对父节点进行左旋,转换为情况3。情况3:叔节点为黑色,且当前节点是父节点的左孩子。则将父节点变为黑色,祖父节点变为红色,然后对祖父节点进行右旋。删除操作:删除操作较为复杂,首先执行标准二叉搜索树的删除,然后根据被删除节点的颜色和位置进行调整。如果被删除的节点是黑色,则可能会破坏性质5(黑高),需要从兄弟节点借调黑色节点或调整颜色。具体调整情况较多,这里不展开。下面我们根据引用[4]和引用[2]中的代码片段,展示一个简化的红黑树实现(包括节点定义和旋转操作)。注意:为了简化,我们使用一个特殊的NIL节点(叶子节点)来表示空节点,且颜色为黑色。代码实现步骤:1.定义节点结构(包括颜色、数据、左右子节点和父节点指针)。2.定义红黑树类,包含根节点和NIL节点。3.实现旋转操作(左旋和右旋)。4.实现插入操作及插入后的修复(fixup)函数。由于完整实现较长,我们只展示关键部分(节点定义、旋转操作和插入修复)。删除操作和详细情况可自行扩展。参考引用[4]的节点定义和旋转操作,以及引用[2]中的结构,我们编写如下代码:</think>### 红黑树实现原理及C++代码示例 红黑树是一种**自平衡二叉搜索树**,通过特定规则维持树的平衡性,确保最坏情况下操作时间复杂度为$O(\log n)$。以下是其核心原理和C++实现: --- #### **红黑树五大性质** 1. **节点颜色**:每个节点为红色或黑色 2. **根节点**:根必须是黑色 3. **叶子节点**:所有叶子(NIL节点)为黑色 4. **红色约束**:红色节点的子节点必须为黑色(无连续红节点) 5. **黑高一致**:从任意节点到叶子节点的路径包含相同数量黑节点 --- #### **核心操作原理** ##### 1. 旋转操作(维护平衡的关键) ```cpp // 左旋操作(引用[4]) void leftRotate(Node* x) { Node* y = x->right; // 设置y为x的右子节点 x->right = y->left; // 将y的左子树变为x的右子树 if (y->left != nil) y->left->parent = x; y->parent = x->parent; if (x->parent == nil) root = y; else if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; y->left = x; // 将x设为y的左子节点 x->parent = y; } // 右旋操作(对称实现,略) ``` **作用**:改变子树结构但不破坏二叉搜索树性质,用于调整树高 ##### 2. 插入修复(维护红黑性质) 插入新节点(默认为红色)后,根据**叔父节点颜色**分三种情况处理: - **Case 1**:叔父节点为红色 → 父节点和叔父变黑,祖父变红,递归处理祖父节点 - **Case 2**:叔父节点为黑且新节点在右子树 → 对父节点左旋转为Case 3 - **Case 3**:叔父节点为黑且新节点在左子树 → 父节点变黑,祖父变红,对祖父右旋 --- #### **C++完整实现框架** ```cpp #include <iostream> using namespace std; enum Color { RED, BLACK }; struct Node { int key; Color color; Node *left, *right, *parent; Node(int k) : key(k), color(RED), left(nullptr), right(nullptr), parent(nullptr) {} }; class RBTree { private: Node *root; Node *nil; // 哨兵叶子节点(引用[2]) public: RBTree() { nil = new Node(0); nil->color = BLACK; root = nil; } // 插入入口 void insert(int key) { Node *z = new Node(key); Node *y = nil; Node *x = root; // 标准BST插入 while (x != nil) { y = x; x = (z->key < x->key) ? x->left : x->right; } z->parent = y; if (y == nil) root = z; else if (z->key < y->key) y->left = z; else y->right = z; z->left = nil; z->right = nil; insertFixup(z); // 修复红黑性质 } // 修复函数(引用[4]) void insertFixup(Node *z) { while (z->parent->color == RED) { if (z->parent == z->parent->parent->left) { Node *y = z->parent->parent->right; // 叔父节点 if (y->color == RED) { // Case 1: 叔父为红 z->parent->color = BLACK; y->color = BLACK; z->parent->parent->color = RED; z = z->parent->parent; } else { if (z == z->parent->right) { // Case 2: 叔父为黑且z在右子树 z = z->parent; leftRotate(z); } // Case 3: 叔父为黑且z在左子树 z->parent->color = BLACK; z->parent->parent->color = RED; rightRotate(z->parent->parent); } } else { // 对称处理(父节点在右子树) // ... } } root->color = BLACK; // 确保根为黑 } // 左旋/右旋实现(略) }; ``` --- #### **实际应用场景** 1. **C++ STL容器** `std::map`/`std::set`底层使用红黑树(引用[1][5]) ```cpp std::map<std::string, int> m; // 红黑树实现 ``` 2. **高性能调度** Linux CFS调度器用红黑树管理进程(引用[2]) 3. **网络框架** Nginx定时器管理、epoll事件管理(引用[2]) --- #### **性能优势** | 操作 | 普通BST | 红黑树 | |------------|---------|---------| | **插入** | $O(n)$ | $O(\log n)$ | | **删除** | $O(n)$ | $O(\log n)$ | | **查找** | $O(n)$ | $O(\log n)$ | | **旋转次数**| - | 最多3次/插入[^4] | > 红黑树通过牺牲严格平衡性(允许高度差≤2倍),减少旋转次数,提高动态操作效率[^5]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值