深入理解数据结构——红黑树

本篇文章深入讲述红黑树的原理与实现。红黑树是一个非常重要的数据结构,其本质上是一个平衡查找树,可是其“平衡”的条件和AVL树不同。在深入了解红黑树之前要先熟悉平衡查找树的相关知识,这里就不再介绍。

红黑树简介

红黑树性质

首先,什么是红黑树?红黑树(RB-Tree)是一种平衡二叉搜索树,除此之外还要满足以下规则:

  1. 每个节点不是红色就是黑色
  2. 根节点为黑色
  3. 如果节点为红色,其子节点必为黑色
  4. 任意节点至NULL(树末尾)的任何路径所含的黑色节点数相同
  5. 每一个NULL视为黑色节点

根据以上五条规则,可以推出一些推论:

  • 新插入的节点必为红色节点(规则4所示,如果新增节点为黑色则会使这条路径上的黑色节点数比其他路径多1,则必须要调整红黑树)
  • 一颗n个节点的红黑树,其树高度至多为2log(n+1)
    一颗典型红黑树

红黑树应用场景与比较

由于红黑树插入、删除、查找的时间复杂度都为O(logn),所以十分利于在存储大量数据中进行查找。接下来说一下红黑树的具体应用场景。

  1. 首先在STL中,关联容器中map与set都是以红黑树作为底层实现。
  2. linux内核中有大量用到红黑树的地方,最典型的就是epoll将监听事件的fd存储在红黑树中,可以快熟查找和添加,又比如高精度计时器使用红黑树树组织定时请求,EXT3文件系统也使用红黑树树来管理目录,虚拟存储管理系统也有用红黑树进行管理等。

但是普通的平衡查找树的查询时间复杂度也是O(logn),那红黑树的优势在哪呢?实际上,平衡查找树对于平衡条件的控制非常严格,如AVL树要求任何一个节点的左右子树高度差不超过1,但是其实维持树平衡状态的开销很大,红黑树的优势就在这,红黑树对于“平衡条件”的控制不太严格,只要满足其性质就是属于平衡状态,所以其对于维持树平衡的开销较小,也是其最大的优势。
与此同时,跳表(skiplist)的插入和查询时间复杂度也是O(logn),并且跳表的实现较红黑树而言简单太多,因此Redis,LevelDB的MemTable中都采用跳表作为基本数据结构;除此之外,由于平衡查找树和跳表都是有序的(而哈希表是无序的),在范围查找的过程中跳表也比红黑树容易很多。具体参考Redis的数据结构

红黑树节点插入

接下来正式进入分析。
红黑树插入操作的时间复杂度为O(logn),具体步骤如下:

  1. 先把其当做二叉搜索树,将节点插入适当的位置(若插入的节点值比当前节点小则往左子树移动,否则往右子树移动);
  2. 按照推论1,将新插入的节点设置为红色;
  3. 将树经过转化变成一颗红黑树。分成三种情况:①若插入节点为根节点,则把该节点变成黑色即可(性质2)②若插入节点的父节点为黑色,则不需要做任何事情还是一颗红黑树(因为没有破坏任何一个性质)③若插入节点的父节点为红色,则又分成三种情况,如表格所示:
条件 步骤
新加入的节点的叔节点为红色(根据性质,祖父节点必为黑色) 1. 父亲节点设为黑节点 2.叔叔节点设为黑节点 3.祖父节点设为红节点 4. 以祖父节点为新节点继续重复
新加入的节点的叔节点为黑色,且新加入的节点是内侧插入(双旋转,也可以理解为两次单旋转) 1. 将父亲节点进行左旋 2. 改变祖父节点和自己节点的颜色 3. 祖父节点进行右旋
新加入的节点的叔节点为黑色,且新加入的节点是外侧插入(单旋转) 1. 将祖父节点设为红色 2. 父节点设为黑色 3. 右旋祖父节点

到此,红黑树的插入规则就阐述完了。

红黑树节点删除

红黑树删除操作时间复杂度也为O(logn),具体的实施步骤比插入操作复杂一点。但是也可以分成多步骤进行。

  1. 按照二叉查找树的方式定位并删除节点。分成三种情况:①被删除的节点没有子节点,则直接删除即可 ②如果被删除的节点只有一个子节点,则删除该节点并用其唯一子节点替代其位置 ③如果被删除的节点有两个子节点,则选取后继子节点(即左子树的最右节点),将后继节点的值替换到被删除的节点,然后删除后继节点(如果后继节点有左子节点,则连接上即可)。
  2. 调整平衡。调整平衡的过程中分成三种情况: ①删除的节点是红色,则不需调整 ②如果删除的节点是黑色且为根节点,则不需调整 ③如果删除的节点是黑色且不为根节点,则又可以分成四种情况,如下所示:
条件 策略
如果被删除节点的兄弟节点是红色(此时父节点与兄弟节点的子节点都是黑色) 1. 将兄弟节点设为黑色 2. 将父节点设为红色 3. 对父节点进行左旋 4. 左旋后,重新设置兄弟节点。
如果被删除节点的兄弟节点是黑色,且兄弟节点的子节点都是黑色 1. 将兄弟节点设为红色 2. 设置父节点为“新的被删除节点”。
如果被删除节点的兄弟节点是黑色,且兄弟节点的左孩子是红色,右孩子是黑色 1. 将兄弟节点的左孩子设为黑色 2. 将兄弟节点设为红色 3. 对兄弟节点进行右旋 4. 右旋后,重新设置兄弟节点。
如果被删除节点的兄弟节点是黑色,且兄弟节点的右孩子是红色 1. 将父节点颜色赋值给兄弟节点 2. 将父节点设为黑色 3. 将兄弟节点的右子节设为黑色 4. 对父节点进行左旋 5. 设置被删除节点为根节点。

红黑树的实现

参考:
1. 《算法导论》
2. 《STL源码剖析》
3. 红黑树原理概述
4. 红黑树删除操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章