文章目录
前言
红黑树是一种高效的二叉搜索树,平均查找性能要比普通的二叉树要高效,也经常出现在各种面试当中,但是红黑树的构建也确实相当复杂。相比其他博客一上来就介绍各种复杂的概念,本文从普通的二叉树开始,介绍如何构造一棵红黑树。
一、二叉搜索树是什么?
定义:
- 二叉搜索树的每个节点最多有两个节点,即二叉树的定义
- 二叉搜索树的根节点的左子树的值比根节点的值小
- 二叉搜索树的根节点的右子树的值比根节点的值大
- 左子树和右子树都是一颗二叉搜索树
缺陷:一个有序数组构建二叉搜索树时,会退化成一个链表,搜索时间效率变成O(n)
二、平衡树是什么?
为了修复二叉搜索树的缺陷,一个直观的想法就是约束左右子树的高度,不能让所有节点全部插入右子树,这就是平衡树。
定义:
- 平衡树的每个节点最多有两个节点,即二叉树的定义
- 平衡树的根节点的左子树高度和右子树的高度相差
<=1
- 左子树和右子树都是一颗二平衡树
缺陷:很明显,在插入新节点的时候会不停的校验是否符合平衡树的定义,代码的复杂度增加了,插入操作和删除操作都需要重新进行平衡。
三、B树是什么?
m阶B树定义:
- 根节点最少有1个关键字
- 非根节点的关键字数目在
[floor((m - 1)/2) , m - 1]
- 节点关键字大小从左到右依次增加,每个关键字的左子树的关键字比右子树的小
- 所有叶子节点都是同一层
- m阶指的是节点的子树的最大个数,而非关键字个数
3阶B树如下:
四、红黑树是什么?
定义:
红黑树是什么?
- 一颗二叉搜索树
- 根节点是黑色
- 红黑树的叶子节点是一个空指针,标记为黑色
- 任意节点节点不是黑色就是红色
- 不存在连续两个红色节点
- 从根节点到任意叶子节点的路径上的黑色节点个数都相同
红黑树实质上是一颗4阶B树,一个黑色节点的子树如果是红色节点,可以把这个红色节点和黑色节点合并放到同一行,示例:
如上图,就是一颗4阶B树,也是一棵红黑树,这样我们可以分析一下红黑树的第5、6条的性质背后的原因:
- 如果出现连续的红色节点,那么某个节点就会可能出现5个子树,不满足4阶B树的性质
- 如果路径上黑色节点个数不同,那么B树的叶子节点就不可能在同一层了
- B树保证了不会出现类似二叉搜索树的极端情况,但同时也没有平衡树那么严格的平衡性
五、构造一棵红黑树
1.构造一棵二叉搜索树
节点数据结构定义如下(parent字段可暂不定义):
class Node:
def __init__(self, left, right, parent, value):
self.left = left
self.right = right
self.parent = parent
self.value = value
二叉搜索树数据结构定义,关于代码的解读应该问题不大,就没有赘述了,代码中缺少删除节点、遍历操作以及输出二叉树结构
的方法,为了节省篇幅以及重点突出,暂时只展示插入操作:
class Node:
def __init__(self, left, right, parent, value):
self.left = left
self.right = right
self.parent = parent
self.value = value
class BinaryTree:
def __init__(self, elements=None):
self.root = None
self.left = None
self.right = None
self.parent = None
self.value = None
if elements is None:
elements = []
for element in elements:
self.insert(element)
def _insert(self, sub_tree, value):
if value < sub_tree.value:
if sub_tree.left is None:
sub_tree.left = Node(None, None, sub_tree, value)
else:
self._insert(sub_tree.left, value)
else:
if sub_tree.right is None:
sub_tree.right = Node(None, None, sub_tree, value)
else:
self._insert(sub_tree.right, value)
def insert(self, value):
if self.root is None:
self.root = Node(None, None, None, value)
else:
self._insert(self.root, value)
展示二叉搜索树插入代码的主要目的:
红黑树实质也是一棵二叉搜索树,所以插入代码是可以复用的,无非是插入操作不是简单将节点放在left和right指向上,需要染色以及旋转保持红黑树的性质而已
。
2.展示一棵二叉树
直接输出所有节点是简单,但是不直观。
上述图片是一个左旋转90度的二叉树,然后每个节点都是可以独占一行,可以利用一个二维数组来展示一棵二叉树,从上往下,每个节点可以构造一个列表: 节点深度 * \t + 节点的值
。这样最终的二维数组就是 节点数 个列表
。注意:二叉树的中序遍历正好满足从上到下遍历的要求
,最后,将获得的二维数组变换一下正常输出即可。
def traverse(self, positions_list, sub_tree, depth):
if sub_tree is None:
return
depth += 1
self.traverse(positions_list, sub_tree.right, depth)
# print(self.root.value)
print(sub_tree)
positions_list.append(["\t"] * depth + ["%-4d" % (sub_tree.value)])
self.traverse(positions_list, sub_tree.left, depth)
depth -= 1
def __str__(self):
if self.root is None:
return 'Empty tree'
depth = -1
positions_list = []
self.traverse(positions_list, self.root, depth)
# print(positions_list)
lines = []
for line in itertools.zip_longest(*positions_list, fillvalue="\t"):
# print(line)
lines.append("".join(line[::-1]))
return "\n".join(lines)
3.构造一棵红黑树
由二叉树插入部分代码可知,无非是插入左边还是右边,插入的新节点一定是在最后一层,而非中间节点,那么被插入的子节点所属的结构有四种情况:红黑红、红黑、黑红、黑
:
新的节点插入就有12种情形,插入左侧六种,插入右侧六种,这两大类无非是方向换了一下,插入的核心代码没有区别。
1.左侧六种情况
现在先假定插入的节点都在子节点的左侧,那么一共有六种情况,下图中蓝色方框中的红色节点就是要插入的节点,插入的节点默认是红色的
:
2. 第4、6种情况无需变色以及变换,直接插入即可
3. 第3、5种情况需要变色以及旋转
右旋:
代码:
def right_rotation(self, root):
# root节点指的是H节点,注意:(left或right)引用变化和parent引用变化一定是一对,不能单个出现
if not root:
return
grandparent_node = root.parent
parent_node = root
child_node = root.left
# H节点变红,H节点的左边指向N节点,N节点的父亲引用指向H节点
parent_node.color = RED
parent_node.left = child_node.right
if child_node.right:
child_node.right.parent = parent_node
parent_node.parent = child_node
child_node.right = parent_node
child_node.color = BLACK
if grandparent_node:
child_node.parent = grandparent_node
if grandparent_node.left and grandparent_node.left.value == parent_node.value:
grandparent_node.left = child_node
else:
grandparent_node.right = child_node
else:
# 根节点的变化记得初始化root指针
child_node.parent = None
self.root = child_node
先右旋再左旋:
右旋代码在上文,左旋代码如下,和右旋代码类似:
def left_rotation(self, root):
# root是I节点
if not root:
return
grandparent_node = root.parent
parent_node = root
child_node = root.right
parent_node.color = RED
parent_node.right = child_node.left
if child_node.left:
child_node.left.parent = parent_node
parent_node.parent = child_node
child_node.left = parent_node
child_node.color = BLACK
if grandparent_node:
child_node.parent = grandparent_node
if grandparent_node.left and grandparent_node.left.value == parent_node.value:
grandparent_node.left = child_node
else:
grandparent_node.right = child_node
else:
# 根节点的变化记得初始化root指针
child_node.parent = None
self.root = child_node
4. 第1、2种情况需要变色以及旋转以及向上溢出
注意:1、2中情况都是插入左侧,其实插入右侧的情况也是一样的
上述操作展示了溢出操作全过程,代码如下:
def left_overflow(self, sub_tree):
"""
溢出操作,溢出的节点是红色的会放到上一层节点中,所以相当于以溢出节点作为根节点的子树,
插入上一层的节点,即调用_insert方法插入即可
:param sub_tree:
:return:
"""
if not sub_tree:
return
left_node = sub_tree.left
right_node = sub_tree.right
middle_node = sub_tree
parent_node = sub_tree.parent
left_node.color = BLACK
right_node.color = BLACK
if parent_node:
middle_node.color = RED
if parent_node.left and parent_node.left.value == middle_node.value:
parent_node.left = None
middle_node.parent = None
else:
parent_node.right = None
middle_node.parent = None
self._insert(parent_node, middle_node)
else:
middle_node.color = BLACK
5.右侧六种情况
代码的核心部分也是左旋、右旋以及染色,只不过进入的条件和左侧不一样。
6.插入操作
RED = "red"
BLACK = "black"
class ColorNode:
def __init__(self, left, right, parent, value, color):
self.left = left
self.right = right
self.parent = parent
self.value = value
self.color = color
def __str__(self):
left_value = self.left.value if self.left else "None"
right_value = self.right.value if self.right else "None"
parent_value = self.parent.value if self.parent else "None"
return "Node(value: %s, color:%s, left:%s, right:%s, parent:%s)" % (
self.value, self.color, left_value, right_value, parent_value)
class RedBlackTree:
def __init__(self, elements=None):
self.root = None
self.left = None
self.right = None
self.parent = None
self.value = None
if elements is None:
elements = []
for element in elements:
self.insert(element)
def right_rotation(self, root):
# 前文已描述
pass
def left_rotation(self, root):
# 前文已描述
pass
def left_overflow(self, sub_tree):
# 前文已描述
pass
def _insert(self, sub_tree, node):
"""
此处的代码和二叉搜索树的代码框架大致相同,无非真正插入时,根据被插入节点的子结构的情况
选择合适的方法进行插入
:param sub_tree:
:param node:
:return:
"""
if node.value < sub_tree.value:
if sub_tree.left:
self._insert(sub_tree.left, node)
return
# 两种情况:节点是黑色,插入在节点的左侧
if sub_tree.color == BLACK:
sub_tree.left = node
node.parent = sub_tree
return
# 1种情况,节点红色,节点的父节点的右子树不存在或者是黑色
if sub_tree.color == RED and (not sub_tree.parent.right or sub_tree.parent.right.color == BLACK):
sub_tree.left = node
node.parent = sub_tree
self.right_rotation(sub_tree.parent)
return
# 1种情况,节点红色,节点的父节点的左子树不存在或者是黑色
if sub_tree.color == RED and (not sub_tree.parent.left or sub_tree.parent.left.color == BLACK):
sub_tree.left = node
node.parent = sub_tree
parent_node = sub_tree.parent
self.right_rotation(sub_tree)
self.left_rotation(parent_node)
return
# 节点红色,节点的父节点的左右子树存在,且都是红色,但是前面两个条件都过滤了红色这个条件
if sub_tree.color == RED and sub_tree.parent.right and sub_tree.parent.left:
sub_tree.left = node
node.parent = sub_tree
self.left_overflow(sub_tree.parent)
else:
if sub_tree.right:
self._insert(sub_tree.right, node)
return
# 两种情况:节点是黑色,插入在节点的右侧
if sub_tree.color == BLACK:
sub_tree.right = node
node.parent = sub_tree
return
# 1种情况,节点红色,节点的父节点的左子树不存在或者黑色
if sub_tree.color == RED and (not sub_tree.parent.left or sub_tree.parent.left.color == BLACK):
sub_tree.right = node
node.parent = sub_tree
parent_node = sub_tree.parent
self.left_rotation(parent_node)
return
# 1种情况,节点红色,节点的父节点的右子树不存在或者黑色
if sub_tree.color == RED and (not sub_tree.parent.right or sub_tree.parent.right.color == BLACK):
sub_tree.right = node
node.parent = sub_tree
parent_node = sub_tree.parent
self.left_rotation(sub_tree)
self.right_rotation(parent_node)
return
# 节点红色,节点的父节点的左右子树存在而且都是红色
if sub_tree.color == RED and sub_tree.parent.right and sub_tree.parent.left:
sub_tree.right = node
node.parent = sub_tree
self.left_overflow(sub_tree.parent)
def insert(self, value):
if self.root is None:
self.root = ColorNode(None, None, None, value, BLACK)
else:
node = ColorNode(None, None, None, value, RED)
self._insert(self.root, node)
红黑树的输出展示和二叉树一样,就不再赘述了。
总结
以上就是今天要讲的内容,本文从二叉搜索树开始,然后引出红黑树,介绍了红黑树插入的各种情况,最后还介绍了如何输出一个直观的二叉树。删除操作可能在后续篇章给出。