红黑树


我们知道,一棵高度为h的二叉查找树可以实现任何一种基本的动态集合操作,增删查找等,其时间都是O(h)。这样,当树的高度比较低时,这些操作就会执行得比较快;但是,当树的高度较高且偏向于只有一根树枝时,这些操作的性能可能不比用链表好。红黑树(red-black tree)是许多的“平衡的”查找树中的一种,他能保证在最坏的情况下,基本的动态集合操作时间为O(lg n)。

红黑树基础性质理解

红黑树是一种二叉查找树,但在每个节点上增加一个存储位表示节点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长处两倍,因而是接近平衡的。
树中每个节点包括五个域:color, key, left, right和p(parent)。如果某节点没有一个子节点或父节点,则该节点相应的指针(p)域包含值NIL。我们将把这些NIL视为指向二叉查找树的外节点(叶子)的指针,而把带关键字的节点视为树的内节点。

在这里插入图片描述

特性:
平衡二叉树,动态集合操作时间O(lgn)
节点只能是红色或黑色
根节点为黑色
每个叶子节点到根节点的黑色节点个数相同(不包括叶子节点NIL)
叶子节点均为黑色NIL
从叶子到根的路径上不能出现两个连续的节点相同颜色(红色节点必有两个黑色子节点)

旋转操作

下图描述对X进行旋转操作
在这里插入图片描述

旋转操作伪代码:

LEFT-ROTATE(T, x)

y <- right[x]
right[x] <- left[y]
p[left[y]] <- x
p[y] <- p[x]
if p[x] == nil[T]
	then root[T] <- y
else if x == left[p[x]]
	then left[p[x]] <- y
else right[p[x]] <- y
left[y] <- x
p[x] <- y

插入元素

插入:
1.插入前先找到要插入的位置,此步即为查询操作
2.判断插入节点对相邻元素的影响来进行相应操作

循环开始时:
z始终是红色的
如果p[z]是根,则p[z]是黑色
如果z是根,直接置黑结束循环

此时根据p[z]是p[p[z]]的左孩子还是右孩子分为两种情况,下面只列举左孩子情况(同理右孩子情况只是把左右置换了):

z的父节点为黑色:
没有影响性质

z的父节点为红色:
case1: z的叔叔y是红色的
将z的父亲和叔叔节点涂黑,祖父节点涂红,z上升到p[p[z]],继续循环
case2: z的叔叔y是黑色的,且z是右孩子
z上升到p[z],对z进行左旋,此时z只是从右孩子变成了左孩子,因为原先的z和p[z]都是红色,旋转对红黑树性质无影响
case3: z的叔叔y是黑色的,且z是左孩子
无论是否通过case2进入case3,z的叔叔总是黑色的,将z的父节点涂黑,祖父节点涂红,对祖父节点p[p[z]]进行右旋

下面列举两种插入元素可能性:
可能性1:
在这里插入图片描述
插入节点父节点为黑色,插入成功,不违反红黑树特性,无需其他操作

可能性2:
在这里插入图片描述
在这里插入图片描述

RB-INSERT-FIXUP(T, z)

//如果z的父节点是红色,则进入以下循环,如果是黑色则跳出无需操作
while color[p[z]] == RED
	//如果z的父节点是z的祖父节点的左节点,则将y指向z的父节点的叔叔节点
	do if p[z] == left[p[p[z]]]
		then y <- right[p[p[z]]]
			//如果z的叔叔节点为红色,1.将z的父节点设为黑色,2.将祖父节点设为红色,3.将z指向其祖父节点
			if color[y] == RED
				then color[p[z]] <- BLACK
					 color[p[p[z]]] <- RED
					 z <- p[p[z]]
			//如果z的叔叔节点不为红色,而且z是父节点的右节点,1.将z指向其父节点,2.对z点进行左旋
			else if z == right[p[z]]
				then z <- p[z]
					 LEFT-ROTATE(T, z)
			color[p[z]] <- BLACK
			color[p[p[z]]] <- RED
			RIGHT-ROTATE(T, p[p[z]])
	//如果z的父节点是z的祖父节点的右节点,与上同理
	else p[z] == right[p[p[z]]]
		then y <- left[p[p[z]]]
			if color[y] == RED
				then color[p[z]] <- BLACK
					 clolor[p[p[z]]] <- RED
					 z <- p[p[z]]
			else if z == left[p[z]]
				then z <- p[z]
					 RIGHT-ROTATE(T, z)
			color[p[z]] <- BLACK
			color[p[p[z]]] <- RED
			LEFT-ROTATE(T, p[p[z]])

删除元素

删除:
1.根据二叉树的删除规则删除节点
2.根据删除节点的结果进行相应的修复红黑树性质的操作

如果后继y是红的,y不可能是根,树中黑高度无变化,删除后红黑树性质仍然得以保持

传给RB-DELETE-FIXUP的节点x:
y没有孩子,x为nil[T]
y有孩子,x取不为空的左孩子或右孩子(左孩子优先)

case1: x的兄弟w是红色的
w涂黑,x的父亲p[x]涂红,对p[x]进行左旋,将w重新变为x的兄弟节点,x的新兄弟节点是旋转之前w的某个孩子,其颜色为黑色
case2: x的兄弟w是黑色的,而且w的两个孩子都是黑色的
此时w是黑色的,孩子也是黑色的,所以将w涂红,x上升到p[x],继续循环
case3: x的兄弟w是黑色的,w的左孩子是红色的,右孩子是黑色的
case2不成立才走case3,case4,将左孩子涂黑,w涂红,对w进行右旋
case4: x的兄弟w是黑色的,而且w的右孩子是红色的
不论是否经过case3,w的右孩子都会是红色的,将w涂为父节点的颜色,父节点p[x]涂黑,w的右节点涂黑,对父节点p[x]进行左旋,最后将x置为根节点结束循环

举例删除元素15
在这里插入图片描述

上图例子中:
(1)删除元素(执行RB-DELETE(T, z)):z的左右子节点均不为空,则将y指向z的后继(z的右子树的最左节点);y的右子节点为空,则将x指向y的左子节点;将x的父节点指向y的父节点;y是父节点的左子节点,则将y的左子节点指向x;y和z不是同一节点,则将y的数据拷贝给z
(2)y的颜色为黑,则执行恢复红黑树性质的函数RB-DELETE-FIXUP:(case4)将w指向x的叔叔节点;将w的颜色置为x父节点颜色,将x父节点颜色设为黑色,将w左子节点的颜色设为黑色
(3)对x父节点进行右旋

删除操作伪代码如下:

RB-DELETE(T, z)

//z的左右子节点为空
if left[z] == nil[T] or right[z] == nil[T]
	then y <- z
//z的左右子节点均不为空,则将y指向z的后继(z的右子树的最左节点)
else y <- TREE-SUCCESSOR(z)
//y的左子节点不为空,则将x指向y的左子节点
if left[y] != nil[T]
	then x <- left[y]
//y的左子节点为空,则将x指向y的右子节点
else x <- right[y]
//将x的父节点指向y的父节点
p[x] <- p[y]
//y的父节点为空,则将x设为父节点
if p[y] == nil[T]
	then root[T] <- x
//y是父节点的左子节点,则将y的左子节点指向x
else if y == left[p[y]]
	then left[p[y]] <- x
//y是父节点的右子节点,则将y的右子节点指向x
else right[p[y]] <- x
//y和z不是同一节点,则将y的数据拷贝给z
if y != z
	then key[z] <- key[y]
		 copy y's satellite data into z //将y的数据拷贝给z
//y的颜色为黑,则执行恢复红黑树性质的函数RB-DELETE-FIXUP
if color[y] == BLACK
	then RB-DELETE-FIXUP(T, x)
return y
RB-DELETE-FIXUP(T, x)

//x不是根节点,而且x的颜色为黑,则进入循环体
while x != root[T] and color[x] == BLACK
	//如果x是父节点的左节点,则将w指向x的叔叔节点
 	do if x == left[p[x]]
 		then w <- right[p[x]]
 		//w为红色,则将w设为黑色,x的父节点设为红色,对x的父节点进行左旋,将w指向x的父节点
		if color[w] == RED
			then color[w] <- BLACK
				 color[p[x]] <- RED
				 LEFT-ROTATE(T, p[x])
				 w <- right[p[x]]
		//经过执行上个if代码段,此时w必为黑色
		//w的左右子节点为黑色,则将w设为红色,x指向x的父节点
		if color[left[w]] == BLACK and color[right[w]] == BLACK
			then color[w] <- RED
				 x <- p[x]
		//经过执行上个if代码段,此时w的左右子节点不都为黑色
		//w的右子节点为黑色,则将w的左子节点设为黑色,w设为红色,对w进行右旋,将w指向x的叔叔节点
		else if color[right[w]] == BLACK
			then color[left[w]] <- BLACK
				 color[w] <- RED
				 RIGHT-ROTATE(T, w)
				 w <- right[p[x]]
		//经过上面一系列判断,最后执行以下操作:将w的颜色x父节点颜色,将x父节点颜色设为黑色,将w右子节点的颜色设为黑色,对x父节点进行左旋
		color[w] <- color[p[x]]
		color[p[x]] <- BLACK
		color[right[w]] <- BLACK
		LEFT-ROTATE(T, p[x])
		x <- root[T]
	//如果x是父节点的右节点,与上同理
	else if x == right[p[x]]
 		then w <- left[p[x]]
		if color[w] == RED
			then color[w] <- BLACK
				 color[p[x]] <- RED
				 RIGHT-ROTATE(T, p[x])
				 w <- left[p[x]]
		if color[right[w]] == BLACK and color[left[w]] == BLACK
			then color[w] <- RED
				 x <- p[x]
		else if color[left[w]] == BLACK
			then color[right[w]] <- BLACK
				 color[w] <- RED
				 LEFT-ROTATE(T, w)
				 w <- left[p[x]]
		color[w] <- color[p[x]]
		color[p[x]] <- BLACK
		color[left[w]] <- BLACK
		RIGHT-ROTATE(T, p[x])
		x <- root[T]
	color[x] <- BLACK

源码地址:
https://github.com/yangbijia/algorithm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值