手把手教你实现红黑树——从图示到代码

前置知识:二叉搜索树

红黑树是进阶版的二叉搜索树,普通的二叉搜索树在顺序键构造时,复杂度为O(n):
在这里插入图片描述

而红黑树则通过为节点额外添加一些属性改善了这一问题,无论以何种顺序构造红黑树,都能得到O(log n)的复杂度

要想学习红黑树,首先需要了解2-3树

2-3树

对于二叉搜索树,每个节点有一个键和两个链接,称这种节点为2-节点,在它的基础0上引入3-节点,它有两个键和三个链接

在这里插入图片描述

查找

2-3树的查找操作与二叉搜索树基本一致,只是在3-节点处需要比较的键由一个变成了两个

插入

2-3树的插入操作也与二叉搜索树相似,首先将给定的键在树中查找,如果命中,就更新对应的值,如果未命中则要复杂一些,分为以下几种情况:

向2-节点插入

如果查找未命中结束于一个2-节点,只需要将新键插入到这个2-节点中合并成为一个3-节点

在这里插入图片描述

向3-节点插入

如果查找未命中结束于一个3-节点,先将这个新键插入到3-节点中合并为一个临时的4-节点,再将这个4-节点分解为由三个2-节点构成的高度为2的树

在这里插入图片描述

向上传递

在向3-节点插入时,如果3-节点有父结点,就要将插入操作向上传递,思想是将新生成的高度为2的子树的根节点作为新的要插入的节点,向它的父结点插入

当父节点是2-节点时:

在这里插入图片描述

当父结点是3-节点时:

在这里插入图片描述

按照这种方式一层一层向上传递,直到根节点

从上面的插入操作可以看出,在2-3树中,当一侧的节点增加时,会将节点合并并向上传递,最终在另一侧生长出节点,这样就保证了树的平衡性(同一根节点的左右子树高度之差不超过1

所以无论以何种顺序生成2-3树,都不会出现单侧生长的情况,能够得到O(log n)的复杂度

红黑树

2-3树已经满足了平衡性的要求,但是在2-3树中,有的节点有一个键,有的节点有两个键,实际实现起来很不方便

所以红黑树出现了——它将2-3树中的3-节点拆分成了一个根节点和通过红色链接指向的左节点(当然红色右链接也可以,思想是一样的),这种红黑树称为左偏红黑树,在本文中统一称为红黑树(左偏红黑树比普通的红黑树要简单一些,但是思想是相似的,理解了左偏红黑树之后理解普通红黑树会很容易)

在这里插入图片描述

所以就有了下面的等价:

在这里插入图片描述

为了表示方便,将红链接指向的节点称为红节点,黑链接指向的节点称为黑节点:

在这里插入图片描述

红黑树的性质

通过上面的方法从2-3树转换成的红黑树具有以下性质:

  • 1.红节点都是左孩子

  • 2.没有连续的红节点

  • 3.根节点是黑节点

  • 4.黑节点完美平衡:每一条从根节点到叶子节点的路径上黑节点的数量相同

红黑树的属性

相比于二叉搜索树,红黑树的节点仅仅多了一个颜色属性,用true表示红节点,false表示黑节点

var RED bool = true
var BLACK bool = false

type Node struct {
   
	Key     int     // 用于比较的键
	Value   int     // 该节点存储的值
	Left    *Node   // 左孩子
	Right   *Node   // 右孩子
	N       int     // 以当前节点为根的子树具有的节点数
	Color   bool    // 节点的颜色
}

// 返回以当前节点为根节点子树中节点总数
func (n *Node) size() int {
   
	if n == nil {
   
		return 0
	}
	return n.N
}

// 检查当前节点是否为红节点,默认空节点为黑色
func (n *Node) isRed() bool {
   
	if n == nil {
   
		return false
	}
	return n.Color == RED
}

查找

红黑树的查找操作和二叉搜索树完全一致

插入

红黑树的插入操作要复杂一些,因为它要保持红黑树的性质

首先有一个前提:每当插入一个新节点,无论在什么位置,都先将其假定为红节点

插入操作的核心思想是:消除红色右节点连续的红色左节点

而实现这种思想用到的方法是旋转变色

插入操作有以下几种情况:

1.向黑色节点的左侧插入(直接)

这是最简单的一种情况,等价于向2-3树的2-节点插入,而且新节点就是左孩子,所以不需要做任何变换

sl6Vf0.png

2.向黑色节点的右侧插入,且黑节点的左孩子是黑色(左旋转)

这种情况等价于2-节点的右侧插入,但由于我们定义了红色节点只能是左孩子,而此时出现了红色的右节点,所以通过一次左旋操作来置换黑色父结点和红色右节点

在这里插入图片描述

左旋转操作代码实现:

func (n *Node) rotateLeft() *Node {
   
	x := n.Right
	n.Right = x.Left
	x.Left = n
	x.Color = n.Color
	n.Color = RED
	x.N = n.N // x现在的N是n之前的N
	n.N = n.Left.size() + n.Right.size() + 1
	return x
}

3.向黑色节点的右侧插入,而黑色节点的左孩子是红节点(变色)

这种情况对应于2-3树中向3-节点右侧插入的情况,此时将两个孩子都变为黑色(分解临时4-树),并将父结点变为红色(向上传递)

在这里插入图片描述

变色操作代码实现

func (n *Node) flipColorRootRed() 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值