编程语言
首页 > 编程语言> > Java集合 | HasnMap之红黑树

Java集合 | HasnMap之红黑树

作者:互联网

HashMap中红黑树详解

红黑树基础讲解

定义

红黑树是一种自平衡二叉查找树,是一种特殊的平衡二叉树,在进行插入和删除操作时会通过特定操作来保持二叉查找树的平衡,从而获取良好的查找性能。相比于链表只能循环遍历去找寻某个特定节点o(n),其能在o(log n)内做查找,也因此被用在了HashMap中。
性质:

  1. 每个节点都带有颜色属性,只能为黑色或红色。
  2. 根节点是黑色
  3. 所有叶子节点(NIL)是黑色
  4. 每个红色节点的两个子节点都是黑色(每个叶子到根的所有路径上不能有两个连续的红色节点)
  5. 从任一节点到其每个叶子结点(NIL结点)的所有路径都包含相同数目的黑色节点
    5.1 从5推出:如果一个结点存在黑色子节点,那么该结点肯定有两个子节点。

这些性质保证了红黑树最关键的性质:从根到叶子的最长路径不多于最短路径的两倍,所以称为平衡。性质4与5保证了这一关键性质。最短路径上都是黑色结点,最长路径为红黑节点交替,性质5保证了最长路径中包含的黑色节点数组等于最短路径的黑色节点,而加之性质4保证了最长路径上红色结点的数目不多于黑色节点的数目,所以从根到叶子的最长路径不多于最短路径的两倍,所以红黑树的高度小于等于2*log(n+1)。
红黑树

比较与总结

  1. 其每个结点有颜色属性
  2. 左右子树的高度差有可能大于1。

操作

获取/查找

步骤:

  1. 若根节点的关键值等于查找的关键值,则成功
  2. 若小于,递归查找左子树
  3. 若大于,递归查找右子树。
  4. 若子树为空,查找不成功。

伪代码实现:

递归式:
Tree-Search (x, k)
if x == NULL or k ==x.key
	return x
if k < x.key
	return Tree-Search(x.left, k)
else return Tree-Search(x.right, k)

非递归式:
Iterative-Tree-Search(x, k)
 while x != NULL and k != x.key
  	if k < x.key
  		x = x.left
  	else x = x.right
 return x

插入

插入步骤分为两步:

  1. 寻找位置并插入(与普通二叉查找树的插入操作一样),设插入节点为红色
  2. 调整(包括左旋、右旋、变色调整)。
插入操作
RB_INSERT(Tree T, Node z) 
y = T.nil //y为后续的插入结点z的父节点,先置为空, NIL代表为红黑树为空的结点
x = T.root //x为后续的插入节点的z的位置,先置为根节点

//寻找插入节点的位置,步骤类似寻找操作
//不断地循环遍历直至 x为空。此时y为一个真实结点,为x的父节点
//最后的插入位置x一定是一个T.nil结点,而此时的y为x的父节点
while x != T.nil
	y = x
	if z.key < x.key
		x = x.left
	else x = x.right	
//设置插入节点的父节点为y
z.parent = y
//设置父节点y的子节点为插入节点
if y == T.nil
	T.root = z
	else if z.key < y.key
			y.left = z
	else y.right = z
//设置插入节点z的子节点为nil
z.left = T.nil
z.right = T.nil
z.color = RED
//进行插入后的调整
RB_INSERT_FIXUP(T, z)		

此时我们已经完成了插入操作,并将插入结点设为了红色,但这样做可能会导致不再满足红黑树的性质,因此我们需要进行相应的调整操作(左旋、右旋和着色)。在介绍如何进行调整之前我们先介绍一下基础操作:左旋,右旋。

上面我们讲了插入一个结点z并置其颜色为红色,当且仅当其父节点为红色时会违反性质2与4。那么根据不同的情况我们需要分开讨论,在这里我们仅用插入节点的父节点为祖父节点的左子树做说明。
FIX_UP函数总体思路:父节点为红色,则祖父节点一定为黑色,那么就根据叔父结点来进行不同的操作。通过循环来不断地将冲突结点往上走,只要当前结点的父节点仍然为红则不断迭代。根据父节点为左子树还是右子树分成两个category,每个category再分为三个case,本文用父节点为左子树示例,另一个category为左右颠倒即可。

情况列表情况说明解决方法
case1叔父结点为红父结点,叔父结点置黑,祖父节点置红(三个置反)
case2叔父结点为黑,当前结点z为右子树上移至父节点,左移
case3叔父结点为黑,当前结点z为左子树父节点置黑,祖父结点置红,以祖父结点为支点右旋(右旋 + 两个置反)

总结:红黑树的灵魂在于利用颜色标识来实现树的黑高度平衡,区别于AVL树的左右子树高度平衡(不大于1),他实现的是黑高度平衡(性质5: 每条路的黑色节点个数相等),那么在插入操作时自然选择设置插入结点z为红色,这样最能保持黑高度平衡。z的父节点为黑,则不需要进行任何操作;当z的父节点为红色时,不满足性质4(不能有两个连续的红色结点),此时有两个解决策略: 1. 颜色取反(对应case1),2. 旋转+颜色取反。

伪代码:

调整操作
RB_INSERT_FIXUP(Tree T, Node z)
//只要z的父节点为红色则不断迭代
while z.parent.color == RED
	//z的父节点为左子树
	//仅以这个情况举例,若父节点为右子树则左右颠倒
	if z.parent == z.parent.parent.left
		// y为叔父结点
		y = z.parent.parent.right
			//case1:叔父结点为红
			//操作:三个置反 + 上移至祖父节点
			if y.color = RED
				z.parent.color = BLACK
				y.color = BLACK
				z.parent.parent.color = RED
				z = z.parent.parent
			//case2:叔父结点为黑 AND z为右子树(从右边插入)
			// z上移至父节点 + 以z左旋
			else if z = z.parent.right
					z = z.parent
					LEFT_ROTATE(T, z)
			//case 3: 叔父结点为黑 AND z为左子树(从左边插入)
			// 父祖父置反 + 以z的祖父右旋
			else
			z.parent.color = BALCK
			z.parent.parent.color = RED
			RIGHT_ROTATE(T, z.parent.parent)
	//z的父节点为右子树
	else
	...
	
//置根节点颜色为黑
T.root.color = BLACK

删除

删除操作相比插入操作更加负载,但抽象来概括也是分为两部:

  1. 寻找结点并且删除(与普通二叉查找树相似)
  2. 调整

在介绍删除操作前,我们先讲解一些后续需要用到的专业名词。

下面介绍删除操作的基本思想,首先最重要的就是删除被删结点z之后仍然能保持二叉查找树的有序性,那么根据结点z有无子节点分为三种情况。

在进行删除操作的伪代码编写前,我们先抽象出来一个函数TRANSPLANT(T, u, v)用于把以v为根的子树移植到以u为根的子树上,即:1. v成为u的父节点的孩子结点;2. v的父节点为u的父节点。

移植操作:本质是完成u与v的父结点的链接
RB_TRANSPLANT(Tree T, Node u, Node v)
//1. v成为u的父节点的孩子节点
if u.parent = T.nil
	T.root = v
else if u == u.parent.lChild
	u.parent.lChild = v
else u.parent.rChild = v
//2. 完成v的父节点的赋值
v.parent = u.parent

下面我们进行删除操作RB_DELETE()的伪代码编写。

删除操作
RB_DELETE(Tree T, Node z)
y = z	//y代表z的后继结点,初始赋值为z
//后继结点的初始颜色,如果为黑则需要进行FIXUP
y-original-color = y.color
//case2: z仅有右子节点的情况
if z.lChild == T.nil
	//x为后继结点y的代替节点,此刻y为z,x为z的右子结点
	x = z.rChild
	//把z的右孩子上移代替z
	RB_TRANSPARENT(T, z, z.rChild)

//case2: z仅有左子节点的情况
else if z.rChild == T.nil
	x = z.lChild
	//把z的左孩子上移代替z
	RB_TRANSPARENT(T, z, z.lChild)

//case3:z有两个子节点的情况下
//寻找z的右子树的最小结点即z的后继结点
else y = TREE_MINIMUM(z.right)
	//y改变,y-original-color随之重新赋值
	y-original-color = y.color
	//x为y的右子结点(可能为T.nil)
	x = y.rChild
	//case3.2: y不是z的右子结点,先行成以y为根的右子树(y上移+x上移),再把z的右子结点y上移代替z
	if y.parent != z
		//x上移至y原来的位置
		RB_TRANSPARENT(T, y, y.rChild)
		//y上移至z的右子结点位置
		y.rChild = z.rChild
		y.rChild.parent = y
	//case3.1: y是z的右子结点,用z的右子结点y上移代替z
	RB_TRANSPLANT(T, z, y)
	//完成y跟z的左子结点的链接
	y.lChild= z.lChild
	y.lChild.parent = y
	//y移置z的位置,那么相应的y的颜色也需要改成原来z的颜色
	y.color = z.color
	if y-original-color = BLACK
		RB_DELETE_FIXUP(T, x)

相似于插入操作,对红黑树进行元素的删除后也要考虑保持红黑树的性质。

在我们进行FIXUP()函数的分析前,我们先来温习一下红黑树的五个性质。

  1. 每个节点都带有颜色属性,只能为黑色或红色。
  2. 根节点是黑色
  3. 所有叶子节点(NIL)是黑色
  4. 每个红色节点的两个子节点都是黑色(每个叶子到根的所有路径上不能有两个连续的红色节点)
  5. 从任一节点到其每个叶子结点(NIL结点)的所有路径都包含相同数目的黑色节点
    5.1 从5推出:如果一个结点存在黑色子节点,那么该结点肯定有两个子节点。

y-original-color为黑,通过RB_DELETE函数,我们知道删除了y结点后,x占据了y原来的结点,整个路径上少了一个黑色结点,那么很简单我们把这个黑色加在x结点上(x的color属性仍然为它自身本身的属性,只是它额外多了一个黑色),这样就能保证黑高在操作前后没有变动。
现在,x不仅包含它原来的颜色属性,还多出来了一个额外的黑色,即x为“黑黑”/“红黑”,这样违反了性质1。现在我们要做的就是消去这多余的颜色属性,使得x的颜色属性只包含一个。根本思想:将x所包含的额外黑色不断上移,直到:

  1. x为红黑节点,此时将x着色为单个黑色
  2. x为根节点,此时直接移除额外的黑色。
  3. x为黑+黑结点且非根节点,根据不同case执行对应的着色和旋转。

下面我们来详细描述一下上述情况3中不同的case如何操作,在这里我们以x为父节点的左孩子作为示例进行讲解,我们定义x的兄弟结点为w(由于x为双重黑色结点,所以w一定为非叶子结点,否则违反性质5)。

注:x基本上是双重黑色的NIL结点。因为x是后继结点y的右孩子结点,根据性质可以知道如果x是黑色的非NIL结点,那么y必定有左孩子结点,与后继结点(右子树的最左结点)定义冲突,所以可以得知x基本上是双重黑色的NIL结点。
特殊情况:x属于case2的情况下上移后不一定是双重黑色的NIL结点。

总结

红黑树作为一种特殊的二叉查找树,它的各种操作首先是以对应的二叉查找树的操作以基础,在此之上由于它特殊的颜色性质(最主要的就是不能有两个连续的红色结点 + 黑高一致),进行对应操作的后续FIXUP过程,插入和删除操作有很高的相似性。

HashMap中的红黑树操作

用法

源码解析

标签:之红,结点,HasnMap,Java,parent,右子,插入,操作,节点
来源: https://blog.csdn.net/heroes134/article/details/110173522