红黑树详解

Posted rotepad

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了红黑树详解相关的知识,希望对你有一定的参考价值。

 

1.为什么需要红黑树?

对于二叉搜索树,如果插入的数据是随机的,那么它就是接近平衡的二叉树,平衡的二叉树,它的操作效率(查询,插入,删除)效率较高,时间复杂度是O(logN)。但是可能会出现一种极端的情况,那就是插入的数据是有序的(递增或者递减),那么所有的节点都会在根节点的右侧或左侧,此时,二叉搜索树就变为了一个链表,它的操作效率就降低了,时间复杂度为O(N),所以可以认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间,视情况而定。那么为了应对这种极端情况,红黑树就出现了,它是具备了某些特性的二叉搜索树,能解决非平衡树问题,红黑树是一种接近平衡的二叉树。

2.红黑树的特性有哪些?

首先,红黑树是一个二叉搜索树,它同时满足以下特性:

(1) 每个节点要么是黑色,要么是红色

(2) 根节点是黑色

(3) 如果节点是红色的,那么它的子节点必须是黑色的(反之,不一定需要成立)

(4) 从根节点到叶节点或空子节点的每条路径,都包含相同数目的黑色节

通过看图来理解以上四个特性

技术图片

 

3.红黑树的效率

红黑树的查找,插入和删除操作,时间复杂度都是O(logN)。查找操作时,它和普通的相对平衡的二叉搜索树的效率相同,都是通过相同的方式来查找的,没有用到红黑树特有的特性。但,如果插入的时候是有序数据,那么红黑树的查询效率就比二叉搜索树要高了,因为此时二叉搜索树不是平衡树,它的时间复杂度O(N)。插入和删除操作时,由于红黑树的每次操作平均要旋转一次和变换颜色,所以它比普通的二叉搜索树效率要低一点,不过时间复杂度仍然是O(logN)。总之,红黑树的优点就是对有序数据的查询操作不会慢到O(logN)的时间复杂度。

4.对旋转的理解

在红黑树中,插入或者删除数据时,为了保持红黑树的那五个特性,需要进行旋转和变换颜色的操作。旋转必须要一次性做两件事情:

* 使一些节点上升,一些节点下降,帮助树平衡

* 保证不破坏二叉搜索树的特征

旋转分为左旋转和右旋转,那么我们就看下左旋转和右旋转是怎么回事。

4.1 左旋转

此处,以50为支点进行逆时针旋转,然后75成为了顶点,50成为了75的左子节点,65成为了50的右子节点,这个操作就是左旋转。

技术图片

4.2 右旋转

此处,以75为支点顺时针旋转,然后50成为了顶点,75成为了50的右子节点,65成为了75的左子节点,这就是右旋转操作。

技术图片

5.插入操作

在介绍插入操作前,先约定一下各个节点的名称。看图:
技术图片

在红黑树中,插入一个节点,都执行了哪些操作呢?

首先,查找要插入节点的位置,然后给予颜色(红色或者黑色),但是为了保证红黑树的特性,需要进行旋转或者更改颜色,需要注意的是,在旋转前和旋转后,红黑树一直都是一个二叉搜索树,二叉搜索树的特征从未改变过。

这里,对于新插入的节点,我们给予的颜色是红色,是因为为了和红黑树的第(4)条特性不冲突: 从根节点到叶节点或空子节点的每条路径,都包含相同数目的黑色节点。这样就少了很多操作。然后看其它几个特性

* 对于(1)特性:每个节点要么是黑色,要么是红色,不冲突。

* 对于(2)特性:根节点是黑色,如果插入的节点不是根节点,也不冲突。如果是根节点,那么直接给予颜色黑色

那么,唯一需要满足的特性就是(3):如果节点是红色的,那么它的子节点必须是黑色的。这里,当我们给予新插入的节点颜色是红色时,需要根据父节点的不同情况做不同的处理,以满足这个特性。有以下几种情况:

根据代码来理解这几种情况:

技术图片
/* 红黑树修正程序 */
void insert_repair_tree(struct node* n) {
//情况一:节点N的父节点为null,说明该节点是根节点 if (parent(n) == NULL) { insert_case1(n); } else if (parent(n)->color == BLACK) {
//情况二:父节点是黑色 insert_case2(n); } else if (uncle(n)->color == RED) {
//情况三: 父节点和叔叔节点都是红色 insert_case3(n); } else {
//情况四:父节点是红色,叔叔节点是黑色 insert_case4(n); } }
技术图片

5.1:如果新插入节点是根节点,那么给予该节点颜色是黑色

看代码:

 

技术图片
/* 情况一:插入的节点是根节点 */
void insert_case1(struct node* n)
{
 if (parent(n) == NULL)
  n->color = BLACK;
}
技术图片

 

5.2:如果新插入节点的父节点是黑色,那么给予该节点是红色不会对该红黑树有任何影响,所以不做任何处理。

看代码:

/* 情况二:父节点是黑色 */
void insert_case2(struct node* n)
{
  return; /* Do nothing since tree is still valid */
}

5.3:如果新插入节点的父节点是红色,同时叔叔节点也是红色

       需要将父亲节点和叔叔节点重新绘制为黑色,祖父节点绘制为红色。但是,如果此处祖父节点为根节点,那么需要调用红黑树修正程序,将祖父节点绘制为黑色。

看代码:发现看代码比看图要更容易理解,并且逻辑也更严谨,对于父亲节点和叔叔节点都是红色时,发现其他文章都没有考虑祖父节点为根节点的情况,这里图就省了。

技术图片
/* 情况三:父节点和叔叔节点都是红色 */
void insert_case3(struct node* n)
{
 parent(n)->color = BLACK;
 uncle(n)->color = BLACK;
 grandparent(n)->color = RED;
//调用红黑树修正程序 insert_repair_tree(grandparent(n)); }
技术图片

5.4:如果新插入节点的父节点是红色,同时叔叔节点是黑色

看代码:

技术图片
/* 情况四:第一步 */
void insert_case4(struct node* n)
{
 struct node* p = parent(n);
 struct node* g = grandparent(n);

 if (n == g->left->right) {
  /* 如果父节点是祖父节点的左子节点,新插入节点是父节点的右子节点,那么以父节点为支点左旋 */
  rotate_left(p);
  n = n->left;
 } else if (n == g->right->left) {
    /* 如果父节点是祖父节点的右子节点,新插入节点是父节点的左子节点,那么以父节点为支点右旋 */
  rotate_right(p);
  n = n->right; 
 }

 insert_case4step2(n);
}
技术图片
技术图片
/* 情况四:第二步 */
/* 在第二步时:当前节点n肯定处于 祖父节点左子节点的左侧,或者祖父节点右子节点的右侧 */ void insert_case4step2(struct node* n) {
struct node* p = parent(n); struct node* g = grandparent(n); if (n == p->left)
/* 以祖父节点为支点进行右旋 */ rotate_right(g); else rotate_left(g); p->color = BLACK; g->color = RED; }
技术图片

在满足父节点是红色,叔叔节点是黑色的条件下,我们可以分以下几种情况:

情况(a):父节点p是祖父节点g的左子节点,新插入节点n是父节点p的右子节点

处理:以父节点为支点左旋,然后以祖父节点为支点进行右旋,最后给父节点(第一次旋转后的父节点)绘制为黑色,给祖父节(第一次旋转后的祖父节点)点绘制为红色

看图:

技术图片

情况(b):父节点p是祖父节点g的右子节点,新插入节点n是父节点p的左子节点

处理:以父节点为支点进行右旋,然后以祖父为支点进行左旋,最后给父节点(第一次旋转后的父节点)绘制为黑色,给祖父节(第一次旋转后的祖父节点)点绘制为红色

看图:

技术图片

情况(c):父节点p是祖父节点g的左子节点,新插入节点n是父节点p的左子节点

处理:以祖父节点为支点进行右旋,最后给父节点绘制为黑色,给祖父节点绘制为红色,也就是直接调用第二步的方法 insert_case4step2

情况(d):父节点p是祖父节点g的右子节点,新插入节点n是父节点p的右子节点

处理:以祖父为支点进行左旋,最后给父节点绘制为黑色,给祖父节点绘制为红色,也就是直接调用第二步的方法 insert_case4step2

 6 删除操作

对于删除操作,处理的逻辑是:根据二叉搜索树的特点找到被删除的节点,将其删除掉,然后再通过改变颜色和旋转操作保持其红黑树的特性。此处我们统一规定被删除节点是D,真正被删除节点为RD,真正被删除节点的兄弟节点是B,真正被删除节点的父节点是P,B的两个子节点是BL和BR。删除操作有三种情况:

6.1 被删除节点为叶子节点,分为两种情况:

情况(a):该叶子节点是红色

处理:直接删除

情况(b):该叶子节点是黑色,

处理:删除节点,然后旋转和变换颜色。

6.2 被删除节点只有一个子节点,那么使被删除节点的父节点指向被删除节点的子节点,然后通过改变节点颜色和旋转来保持红黑树特性

6.3 被删除节点D有两个子节点,那么可以将被删除节点D的后继节点(D的右子树中的最小元素RD)的值赋值给被删除节点D,但是被删除节点D的颜色不做改变,此时删除操作就转化为了删除后继节点RD的情况了。那么删除RD的情况又分为五种:

情况(a): RD节点是红色的

处理:如果RD节点是红色的,那么它的父节点和右子节点应该是黑色的,当删除RD节点时,直接把RD节点的父节点指向RD节点的右子节点即可。

下面四种情况RD节点都是黑色的

情况(b):RD的兄弟节点B为红色

处理:如果B节点为红色,那么P节点和B的两个子节点都是黑色。交换B节点和P节点的颜色,然后以P节点为支点进行左旋转。这样处理后,就将情况(b)转为(c),(d),(e)中的一种。

看图:

技术图片

 

 情况(c):

 

后续更新。。。

 

 

参考资料:

模拟红黑树:https://www.cs.usfca.edu/~galles/visualization/RedBlack.html

维基百科红黑树:https://en.wikipedia.org/wiki/Red%E2%80%93black_tree#Insertion

以上是关于红黑树详解的主要内容,如果未能解决你的问题,请参考以下文章

[数据结构] 红黑树的详解

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

[C/C++]详解STL容器7--红黑树的介绍及部分模拟实现

红黑树特性和实现详解——C++进阶数据结构