由之前所说的二叉查找树可以看出,二叉查找树是在动态存储的过程中使得序列变成了有序序列,一旦有序之后,就方便我们进行各种操作。相对于无序数据而言,在我们分析数据和查找数据上都会带来很大的不便,所以我们需要先对数据进行排序,将其变成为有序数据,这样将会对后期我们进行数据处理带来很多方便之处,包括数据分类,查找,插入和删除等操作,这些操作建立在有序表上的话就方便很多。
之前所说的二叉排序就是在存储数据的过程中,我们就刻意的让数据进行有序的存储,这样在后来使用过程中就不需要对数据进行排序了,减少了不必要的操作。
但是建立二叉排序树时,也要可能退化成线性表的形式,比如:
2
3
4
5
6
这时候就变成一个链表了,查找效率还是不高,所以我们就需要将其变成一棵优化的二叉树:
4
3 5
2 6
这样查找的时候我们就可以控制时间复杂度在 O ( l o g 2 n ) O(log_2^n) O(log2n),于是引出了平衡二叉树的概念。
平衡二叉树(Balanced Binary Tree):
又称为AVL树,它要么是一棵空树,要么具有如下性质的二叉树:它的左子树和右子树都是平衡二叉树,且左子树和右子树的深度之差的绝对值不超过1.若将二叉树上的结点的平衡因子BF(Balance Factor)定义为该节点的左子树的深度减去它的右子树的深度,则平衡二叉树上的所有结点的平衡因子只可能是-1,0,1.只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就不是平衡的。
为了便于查找我们构建了二叉排序树,为了不让其退化为链表,我们知道了平衡二叉树,所以我们就想构建一个平衡的二叉排序树BBST(Balance Binary Sorted Tree),或者说是一棵近似于平衡的二叉排序树,这样我们就能保证该树不但是有序的,并且还能使其查找时间复杂度 O ( l o g 2 n ) O(log_2^n) O(log2n)和树的深度是同数量级的。
那么重点来了,怎样在构建二叉排序树的同时,让其尽可能的成为一棵平衡二叉树呢???于是乎,红黑树就来了。
红黑树:
(1)性质一: 节点不是红色就是黑色的;(红黑树名字的由来)
(2)性质二: 根节点是黑色;(树的根节点必须是黑色的,即树中没有父节点那个节点,或者说入度为0的那个节点)
(3)性质三: 每个叶子节点(NIL或空节点)是黑色;
这里的叶子结点并不是树中最底层的结点,即图中6,22,27,而是null结点,即每个结点都满足有左孩子和右孩子,只不过,有可能左右孩子有可能为null而已。
性质四: 每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);(例如上图中的8和1不能同时为红色,8和11,17和15,17和25,25和22,25和27不能同时为红色)
性质五: 从任意一节点到其每个叶子节点的所有路径上都包含相同数目的黑色节点;
可以通过数学证明得出,满足上述五个性质的二叉排序树都能够使其查找和删除时间控制在 l o g 2 n log_2n log2n内。
为了能使得我们建立出来的二叉排序树满足上述五个性质,并且在增加结点和删除结点时,也满足这五个性质,我们就要对树进行操作,使其满足五个性质。一般我们有两种操作来使该树满足五个性质:
(1)变色: 即将红色结点变为黑色,黑色结点变为红色。
(2)旋转: 旋转又分为左旋转和右旋转
左旋:
E的右孩子S左旋替代E的位置,E作为S的左孩子,S的左孩子与E向相连作为E
的右孩子。
右旋:
S的左孩子E右旋替代S的位置,S作为E的右孩子,E的右孩子与S相连作为S的左孩子。
所以通过上述三种方式,或者三种方式的组合变换来使得我们在插入和删除结点时确保该树还保持着五个特性,即插入或者删除结点后该树还是一棵红黑树。
好了,该说的都说了,现在到了实现的时间了。
红黑树的python实现:
# 颜色常量
RED = 0
BLACK = 1
#左旋
def left_rotate(tree, node):
'''
左旋方法
:param tree: Tree
:param node: TreeNode
:return:
'''
if not node.right:
return False
node_right = node.right
node_right.p = node.p
if not node.p:
tree.root = node