红黑树的原理_关于红黑树原理的一些介绍
1. 简述
红黑树(Red Black Tree)是一种重要的数据结构,也经常会用到,c++的map就是使用红黑树来实现的。在程序员找工作的面试中,红黑树也是经常被问到的一个知识点,我也是当时从找工作的时候尝试去理解红黑树的一些结构和原理,但是由于找工作时间比较紧张,而且要了解的知识点比较多,所以当时对它也没有更深入的了解,后来入职后也搁置了,最近利用一些时间尝试去读懂红黑树的一些知识,这里全当做个总结吧。
2. 介绍
红黑树的优势是相对于普通的二叉查找树(Binary Search Tree)来说的, 理想情况下,时间复杂度为o(logn)。
具体说明:普通的二叉查找树右最多有两个子节点,分别是左子节点和右子节点,左子节点和右子节点分别为左子树和右子树的根,二叉查找树满足左子树上的所有值都小于当前节点的值,当前节点值小于所有右子节树上的值。这样的好处在于,查找某元素是否在集合中,最多只需要从跟节点便利到叶子节点。既然有理想情况,那么就会有性能很差的情况:
案例:红黑树的左子节点均为空
瘸腿的二叉树
这种情况的二叉树,他的左子节点均为空,二叉树退化成了链表,这样查找的时间复杂度为o(n)。为了避免这种情况的出现,因此提出了二叉平衡树的概念。红黑树就是一种二叉平衡树,它保证二叉树的最大分支高度不超过最小分支高度的两倍(高度概念是二叉树中的一个概念,没获取一次子节点,高度+1)。
3. 红黑树的性质
红黑树是一种自平衡的二叉查找树,除了满足二叉查找树的性质外,还需要满足如下五个条件:
- 节点是红色或黑色
- 根节点为黑色
- 所有叶子节点都是黑色
- 每个红色节点都必须有两个黑色的子节点
- 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点
RED = 0
BLACK = 1
class RBTreeNode
def __init__(self, value, parent, left, right, color):
self.parent = parent
self.left = left
self.right = right
self.value = value
self.color = color
4. 红黑树的基本操作
红黑树的基本操作有三个,分别是变色、左旋和右旋,红黑树的插入、删除、查找、遍历动作都可以找到这三个基本操作的影子,首先介绍这三个基本操作。
4.1 变色
变色操作比较简单,不改变该节点的值以及该节点在红黑树中的位置,仅改变该节点的颜色,红黑树颜色有两种,所以,变色分为两种,红色变为黑色和黑色变为红色
4.2 左旋
对值为4的节点做左旋操作,就是将4节点的右子节点6节点上升,4节点作为6节点的左子节点,原6节点的左子节点作为4节点的右节点
def rotate_left(self):
if self.right is None:
return False
self.right.parent = self.parent
self.right.left = self
self.parent = self.right
self.right = self.right.left
return True
4.2 右旋
对值为6的节点做右旋操作,就是将6节点的左子节点4节点上升,6节点作为4节点的右子节点,原4节点的右子节点作为6节点的左子节点
def rotate_left(self):
self.left.parent = self.parent
self.left.right = self
self.left = self.self.right
self.parent = self.left
5. 查找
从根节点查找某元素是否存在与红黑树中,采用的方法与普通的二叉查找树相同,因为二叉查找树的性质,所以小于的往左找,大于的往右找,等于的话就找到了,否则找到叶子节点也没找到,那就说明该元素不存在于二叉树中
def find(self,· element):
if self.value == element:
return self
if element < self.value:
if self.left is None:
return None
return self.left.find(element)
else:
if self.right is None:
return None
return self.right.find(element)
6. 插入
对于插入和后续的删除动作,需要注意红黑树的上面5条规则不被打破,其中规则1、规则2和规则3都比较容易满足,重点要保证规则4和规则5在插入后依然满足。
对于插入动作,首先根据二叉查找树的方式,将新的元素插入到树的叶子节点的位置。当新插入节点的父节点为黑色时,新插入节点的颜色设置为红色,此时不打破规则4,很容易证明从根节点到任意叶子节点的黑色节点数量保持不变,则规则5不被打破,那么整个插入动作完成。但是,当新插入的节点的父节点为红色时,由于规则4的限制,新插入的节点颜色为黑色,不过这样做会打破规则5,还好,我们有左旋、右旋、变色三个武器。这部分代码比较复杂,可以参考红黑树的维基百科介绍(无法使用维基百科的同学可以通过百度搜索红黑树维基百科,有很多国内的帖子复制维基百科的内容,讲解也很详细)。
7. 删除
我参考其他贴子的时候(包括维基百科),上来就提到如果删除的节点有两个叶子节点的话,可以转化为删除的节点最多只有一个叶子节点的情况,然后巴拉巴拉就只介绍如何删除最多一个叶子节点的节点。我当时看完就感觉一头雾水,大概这就是学渣和大神的距离吧。哈哈,不扯废话了,下面是正经的。
在删除节点时,当待删除的节点有两个非空子节点时,首先找到右子树的最小元素(或者左子树的最大元素),然后把右子树最小元素的值替换原待删除节点的值,之后待删除元素就是右子树的最小元素了,右子树的最小元素最多只有一个子节点,那么接下来就转化为了删除只有一个或0个非空子节点的情况了…
不清楚读者看到上面一段话读完什么感受,我觉得还是有必要再解释一下,究竟怎么做的,以及为什么可以这么做。
用下面一个例子看一下究竟是怎么做的
上图中,要删除的10节点,10节点有7和16两个非空子节点,10节点的右子树上的最小节点为13节点,那么就把10这个节点值修改为13,然后删除原值为13的节点。为什么可以这么做呢?这么做岂不是打破规则4和规则5了吗?首先,这么做只是完成了删除的第一步操作,删除的整个动作并没有完成,这么做从查找二叉树的角度是满足的,所以可以这么做,再者,打破规则4和规则5,但是和插入动作一样,我们有变色,左旋,右旋三件武器,恰当的使用这三件武器,就可以完成删除的动作(像上面的情况,只需要把14节点变色为黑色,就可以了),和插入动作类似,请读者参考红黑树的维基百科(或者百度搜索红黑树维基百科)。