了解红黑树之前需要先了解一种树:2-3查找树。
2-3查找树
为了保证二叉查找树的平衡性,需要一些灵活性,因为我们允许树中的一个结点保存多个键。确切的说,将一棵标准的二叉查找树中的结点称为2-结点(含有一个键和两条链),而现在我们引入3-结点,它含有两个键和三条链。2-结点和3-结点中的每条链都对应着其中保存的键所分割产生的一个区间。
定义:一颗2-3查找树要么为空,要么满足下面两个要求:
- 2-结点:含有一个键(及其对应的值)和两条链,左连接指向2-3树中的键都小于该结点,右连接指向的2-3树中的键都大于该结点。
- 3-结点:含有两个键(及其对应的值)和三条链,左连接指向的2-3树中的键都小于该结点,中连接指向的2-3树中的键都位于该节点的两个键之间,右连接指向的2-3树中的键大于该结点。
2-3查找树实现起来比较复杂,在某些情况下插入后的平衡操作可能会使得效率降低。但是2-3查找树作为一种比较重要的概念和思路对于红黑树,B树和B+树非常重要。
红黑树
红黑树主要对2-3树进行编码,红黑树背后的基本思想是用标准的二叉查找树(完全由2-结点构成)和一些额外的信息(替换了3-结点)来表示2-3树。将树中的连接分为两种类型:
- 红链接:将两个2-结点连接起来构成一个3-结点。
- 黑链接:则是2-3树中的普通连接。
确切的说,我们将3-结点表示为由一条左斜的红色链接(两个2-结点 其中之一是另一个的左子结点)链接的两个2-结点。这种表示法的一个优点是:我么无需修改就可以直接使用标准的二叉查找树的get方法。
定义:红黑树是含有红黑连接并满足下列条件的二叉查找树:
- 红链接均为左连接。
- 没有任何一个结点同时和两条红链接项链。
- 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。
平衡化:
- 左旋:当某个结点的左子结点为黑色,右子结点为红色,此时需要左旋。
- 右旋:当某个结点的右子结点是红色,且左子结点的左子结点也是红色,需要右旋。
- 颜色反转:当一个结点的左子结点和右子结点的color都为RED时,也就是出现了临时的4-结点,此时只需要把左子结点和右子结点的颜色变为BLACK,同时让当前结点的颜色变为RED即可。
向单个2-结点中插入新键
一棵只含有一个键的红黑树只含有一个2-结点。插入另一个键后,我们就马上将他们旋转:
- 如果新键小于当前结点的键,我们只需要新增一个红色结点即可,新的红黑树和单个3-结点完全等价。
- 如果新键大于当前结点的键,那么新增的红色结点将会产生一条红色的右链接,此时我们需要通过左旋把红色右链接变成左链接,插入操作才算完成。形成新的红黑树依然和3-结点等价,其中含有两个键,一条红色链接。
向底部的2-结点插入新键
用和二叉查找树相同的方式向一棵红黑树中插入一个新键,会在树的底部新增一个结点(可以保证有序性),唯一区别的地方是我们会用红链接将新结点和它的父结点相连。如果他的父结点是一个2-结点,那么刚才的方式仍然适用。
向一棵双键树(即一个3-结点)中插入新键
这种情况又可以分为三种子情况:
- 新键大于原树中的两个键。
- 新键小于原树中的两个键。
- 新键介于原树中的两个键。
根节点的颜色总是黑色的。
python简单实现红黑树及插入功能及查询功能:
## 红黑树的实现
RED = "red"
BLACK = "black"
class Node(object):
def __init__(self, key, value):
self.key = key
self.value = value
self.right = None
self.left = None
self.color = RED
class RedBlackTree(object):
def __init__(self):
self.root = None
self.N = 0
## 获取树中元素的个数
def size(self):
return self.N
## 判断当前结点的父指向链接是否为红色
def isRed(self, x):
if x:
return x.color == RED
return False
## 左旋转 当某个结点的左子节点为黑色,右子节点为红色,此时需要左旋
def rotateLeft(self, h):
## 获取h结点的右子结点,标识为x
x = h.right
## 让x结点的左子结点成为h结点的右子结点
h.right = x.left
## 让h成为x结点的左子结点
x.left = h
## 让x结点的color属性等于h结点的color属性
x.color = h.color
## 让h结点的color属性变为红色
h.color = RED
return x ## 左旋之后父链接指向了x,所以返回x
## 右旋转 当某个结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋
def rotateRight(self, h):
## 获取h结点的左子结点,表示为x
x = h.left
## 让x结点的右子结点成为h结点的左子结点
h.left = x.right
## 让h结点成为x结点的右子结点
x.right = h
## 让x结点的color属性等于h结点的color属性
x.color = h.color
## 让h结点的color属性变为红色
h.color = RED
return x
## 颜色反转 相当于完成拆分4-结点
def flipColor(self, h):
## 当前结点变为红色
h.color = RED
## 左子结点和右子结点变为黑色
h.left.color = BLACK
h.right.color = BLACK
return h
## 在树上插入键值对
def insert(self, key, value):
self.root = self._insert(self.root, key, value)
self.N += 1
self.root.color = BLACK
## 向指定树中插入键值对
def _insert(self, root, key, value):
## 比较root结点和node结点的键的大小
if root == None:
return Node(key, value)
cmp = key - root.key
if cmp < 0:
## 继续往左
root.left = self._insert(root.left, key, value)
elif cmp > 0:
## 继续往右
root.right = self._insert(root.right, key, value)
else:
## 发生值的替换
root.value = value
self.N -= 1
## 进行左旋(当当前结点的左子节点为黑色,右子节点为红色,此时需要左旋)
if self.isRed(root.right) and not self.isRed(root.left):
root = self.rotateLeft(root)
## 进行右旋(当当前结点的左子结点是红色,且左子结点的左子结点也是红色,需要右旋)
if self.isRed(root.left) and self.isRed(root.left.left):
root = self.rotateRight(root)
## 进行颜色反转(当前结点的左子结点和右子结点都为红色时,需要进行颜色反转)
if self.isRed(root.left) and self.isRed(root.right):
root = self.flipColor(root)
return root
## 根据key,查找对应的值
def get(self, key):
return self._get(self.root,key)
## 从指定的树中查找key对应的值
def _get(self, root, key):
if root == None:
return None
## 比较root结点和key的大小
cmp = key - root.key
if cmp < 0:
return self._get(root.left,key)
elif cmp > 0:
return self._get(root.right,key)
else:
return root.value
if __name__ == '__main__':
red_black_tree = RedBlackTree()
red_black_tree.insert(1,"张三")
red_black_tree.insert(2,"李四")
red_black_tree.insert(3,"王五")
red_black_tree.insert(4,"刘一")
red_black_tree.insert(5,"刘二")
red_black_tree.insert(6,"刘三")
red_black_tree.insert(7,"刘四")
red_black_tree.insert(8,"刘五")
red_black_tree.insert(9,"刘六")
red_black_tree.insert(10,"刘七")
red_black_tree.insert(10,"刘八")
r1 = red_black_tree.get(7)
r2 = red_black_tree.get(10)
print(r1)
print(r2)
n = red_black_tree.size()
print(n)