左倾红黑树的定义
含有红黑链接(边)并满足下列条件的二叉查找树:
- 红链接均为左链接
- 没有任何一个结点同时和两条红链接相连;
- 该树是完美黑色平衡的,即任意空链接到根结点的路径上的黑链接数量相同。
红链接将两个2-结点(普通二叉树结点)链接起来构成一个3-结点(含有两个键,3个链接);黑链接则是2-3树中的普通链接。
满足这样定义的红黑树和相应的2-3树是一一对应的。 将红链接画平时,一颗红黑树就是一颗2-3树
红黑树性质
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),性质5保证红黑树中最大长度不超过最小长度2倍 。(最小长度全部由黑结点组成,最大长度由红黑结点交替构成)。
将左倾红黑树转化为2-3树就能明白上述性质。
Node数据结构
将链接的颜色保存在表示结点的Node数据类型的布尔变量color中;如果指向它的链接是红色的,那么该变量为true,黑色则为false。
当我们提到一个结点的颜色时,指的是指向该结点的链接的颜色。
type Color bool
type Key string
type Value int
const (
RED = true
BLACK = false
)
type RBT struct {
root *Node
}
// 红链接将两个2-结点链接起来构成一个3-结点
// 黑链接则是2-3树中的普通链接。
type Node struct {
key Key
val Value
left, right *Node
color Color // 由其父结点指向它的链接的颜色,true-红链接,false-黑链接
n int // 这棵子树中的结点总书
}
func NewNode(key Key, val Value, n int, color Color) *Node {
return &Node{
key: key,
val: val,
color: color,
n: n,
}
}
旋转
插入
新插入的结点,总是用红链接将其与它的父结点相连;
插入新结点后:
- 使得父结点有两条红链接,那么直接将其转为两个黑链接即可;(0次旋转)
- 如果产生一条红色的右链接,就对父结点执行左旋操作;(1次旋转)
- 如果产生连续的两条红右链接,就对上层的红链接执行一次右旋转,转为情况1;(1次旋转)
- 如果产生连续的两条连续的红链接,一条红色左链接,一条红色右链接;只需将下层的红色右链接执行一次左旋转,转为情况3;(2次旋转)
颜色转换
删除
参考:
- 从2-3树谈到左倾红黑树_Addyz的博客-CSDN博客,这里详细介绍了删除代码的优化过程,有助于理解。
- 有人能讲清楚《Algorithms》中左倾红黑树(LLRB)删除操作的每一行代码吗?
实现
package red_black
type Color bool
type Key int
type Value int
const (
RED = true
BLACK = false
)
type RBT struct {
root *Node
}
// 红链接将两个2-结点链接起来构成一个3-结点
// 黑链接则是2-3树中的普通链接。
type Node struct {
key Key
val Value
left, right *Node
color Color // 由其父结点指向它的链接的颜色,true-红链接,false-黑链接
size int
}
func NewNode(key Key, val Value, color Color, size int) *Node {
return &Node{
key: key,
val: val,
left: nil,
right: nil,
color: color,
size: size,
}
}
func isRed(node *Node) bool {
if node == nil {
return false
}
return node.color == RED
}
func (this *RBT) isEmpty() bool {
return this.root == nil
}
func size(h *Node) int {
if h == nil {
return 0
}
return h.size
}
func (this *RBT) Size() int {
return size(this.root)
}
func findMin(h *Node) *Node {
for h.left != nil {
h = h.left
}
return h
}
// 左旋
func rotateLeft(h *Node) *Node {
x := h.left
h.left = x.right
x.right = h
x.color = h.color
h.color = RED
x.size = h.size
h.size = 1 + size(h.left) + size(h.right)
return x
}
// 右旋
func rotateRight(h *Node) *Node {
x := h.right
h.right = x.left
x.left = h
x.color = h.color
h.color = RED
x.size = h.size
h.size = 1 + size(h.left) + size(h.right)
return x
}
func flipsColor(h *Node) {
h.color = !h.color
h.left.color = !h.left.color
h.right.color = !h.right.color
}
func fixUp(h *Node) *Node {
// 存在红色右链接,需要右转
if !isRed(h.left) && isRed(h.right) {
h = rotateRight(h)
}
// 存在两天连续的红色左链接,需要左转
if isRed(h.left) && isRed(h.left.left) {
h = rotateLeft(h)
}
if isRed(h.left) && isRed(h.right) {
flipsColor(h)
}
h.size = 1 + size(h.left) + size(h.right)
return h
}
func (this *RBT) Put(key Key, val Value) {
this.root = put(this.root, key, val)
this.root.color = BLACK
}
func put(h *Node, key Key, val Value) *Node {
if h == nil {
return NewNode(key, val, RED, 1)
}
if key < h.key {
h.left = put(h.left, key, val)
} else if key > h.key {
h.right = put(h.right, key, val)
} else {
h.key = key
h.val = val
}
return fixUp(h)
}
// 让左孩子结点变红,即3-结点
func moveRedLeft(h *Node) *Node {
flipsColor(h)
// 如果h的右子结点是一个2-结点,只用反转颜色即可
// 若h的右子结点是个3-结点,需要从h的右子结点借一个给h的左子结点
if isRed(h.right.left) {
// 左旋右子节点
h.right = rotateLeft(h.right)
// 右旋h
h = rotateRight(h)
// 颜色翻转
flipsColor(h)
}
return h
}
// 让右子结点变成红结点,即3-结点
func moveRedRight(h *Node) *Node {
flipsColor(h)
// h.right是一个2-结点
// h.left是h.right的兄弟结点,且h.left是一个3-结点,需要借给h.left一个键
// 这里只进行一次左旋即可
if isRed(h.left.left) {
h = rotateLeft(h)
flipsColor(h)
}
return h
}
// delete 就是在查找路径上进行和deleteMin、deleteMax相同的变换
// 要求保证在查找过程中任意当前结点不是2-结点。
// 如果被删除的值在树的底部,我们可以直接删除它。
// 如果不在,我们需要和二叉查找树中的删除一样:
// 用其右子树的最小的值代替该节点的值,并删除那个节点(右子树的最小的值)。
// 然后问题已经转化为在一棵根节点的子树中删除最小值,可以使用上面写的删除最小值的方法。
func delete(h *Node, key Key) *Node {
if key < h.key {
// 向左子树搜索,保证左节点不是2-结点
if !isRed(h.left) && !isRed(h.left.left) {
h = moveRedLeft(h)
}
h.left = delete(h.left, key)
} else {
// h.left为红便左旋,使得h.right变红
if isRed(h.left) {
h = rotateLeft(h)
}
// 找到目标结点
if key == h.key && h.right == nil {
// 左子数不为红,且右子树为空,那么左子数只能是空;不然黑节点的高度不平衡
return nil
}
// 无论如何都要删除右子树中的结点
// 如果要删的是h,与右子树的min对调后删除右子树的min
// 否则目标节点在右子树中
// 所以需要让右子结点变红
if !isRed(h.right) && !isRed(h.right.right) {
h = moveRedRight(h)
}
// 找到目标节点,将其替换为右子树的最小值
// 并删除右子树的最小值
if key == h.key {
t := findMin(h.right)
h.key = t.key
h.val = t.val
h.right = delete(h.right, t.key) // 等同于 deleteMin(h.right)
} else {
h.right = delete(h.right, key)
}
}
return fixUp(h)
}
func (this *RBT) Delete(key Key) {
if !this.Contain(key) {
return
}
this.root = delete(this.root, key)
if this.root != nil {
this.root.color = BLACK
}
}
Keys
func keys(h *Node, res *[]Value) {
if h == nil {
return
}
keys(h.left, res)
*res = append(*res, h.val)
keys(h.right, res)
return
}
func (this *RBT) Keys() []Value {
ans := make([]Value, 0)
keys(this.root, &ans)
return ans
}
Contains
func contrains(h *Node, key Key) *Node {
if h == nil {
return nil
}
if key == h.key {
return h
} else if key < h.key {
return contrains(h.left, key)
} else {
return contrains(h.right, key)
}
}
func (this *RBT) Contains(key Key) bool {
return contrains(this.root, key) != nil
}
DeleteMin、DeleteMax
func deleteMax(h *Node) *Node {
// h.left是一个红节点,不是h.right的兄弟结点
// 需要向左旋转,使得h.left变红,方便从右子树里删除
if isRed(h.left) {
h = rotateLeft(h)
}
if h.right == nil {
return nil
} else if !isRed(h.right) && !isRed(h.right.left) {
// h.right 是一个 2-结点
// 两种情况:
// 1. h.left也是一个2-结点,秩序反转颜色即可
// 2. h.left是一个3-结点,需要从
h = moveRedRight(h)
}
h.right = deleteMax(h)
return fixUp(h)
}
func (this *RBT) DeleteMax() {
this.root = deleteMax(this.root)
if this.root != nil {
this.root.color = BLACK
}
}
func deleteMin(h *Node) *Node {
if h.left == nil {
return nil
}
// 左子结点是一个2-结点
if !isRed(h.left) && !isRed(h.left.left) {
// 两种情况:
// 1. 右子结点也是一个2-结点,直接颜色翻转即可
// 2. 右子结点是一个3-结点,从右子结点借一个给左子结点
h = moveRedLeft(h)
}
h.left = deleteMin(h.left)
return fixUp(h)
}
func (this *RBT) DeleteMin() {
this.root = deleteMin(this.root)
if this.root != nil {
this.root.color = BLACK
}
return
}
Rank、Get
Rank(key Key)返回key的排名,从0开始计数
Get(index int)返回排名为index的key,index从0开始计数
// 返回排名为rank的结点
// rank从0开始
func get(h *Node, rank int) *Node {
if h == nil || rank == size(h.left) {
return h
}
lsize := size(h.left)
if rank > lsize {
return get(h.right, rank-lsize-1)
} else {
return get(h.left, rank)
}
}
func (this *RBT) Get(rank int) *Node {
if rank < 0 || rank >= this.Size() {
return nil
}
return get(this.root, rank)
}
// 返回key的排名,从0开始计数
func rank(h *Node, key Key) int {
// 返回以h为根节点的子树中小于h.key的键的数量
if h == nil {
return 0
}
if key == h.key {
return size(h.left)
} else if key < h.key {
return rank(h.left, key)
} else {
return 1 + size(h.left) + rank(h.right, key)
}
}
// 红黑树中不存在key返回-1
func (this *RBT) Rank(key Key) int {
if !this.Contain(key) {
return -1
}
return rank(this.root, key)
}
Ceil
func ceil(h *node, key int) *node {
if h == nil {
return nil
}
// h 可能是一个合格的结点,向左尝试找 key <= x < h.key
if h.key >= key {
tmp := ceil(h.left, key)
if tmp != nil {
return tmp
}
return h
} else {
return ceil(h.right, key)
}
}
func (this *RBT) Ceil(key int) (bool, int) {
n := ceil(this.root, key)
if n == nil {
return false, -1
}
return true, n.val
}
Floor
func floor(h *node, key int) *node {
if h == nil {
return nil
}
// h 可能是一个合格的结点,也可以向右尝试找 h.key < x <= h.key
if h.key <= key {
tmp := floor(h.right, key)
if tmp != nil {
return tmp
}
return h
} else {
return floor(h.left, key)
}
}
func (this *RBT) Floor(key int) (bool, int) {
n := ceil(this.root, key)
if n == nil {
return false, -1
}
return true, n.val
}
例题
- leetcode 220. 存在重复元素 III:红黑树的添加与删除,实现Ceil(key)找到第一个大于key的数,Floor(key)找到最后一个小于key的数。
- leetcode 436 寻找右区间:红黑树的添加、Ceil(key)实现;将区间的左端点作为key,下标作为val构建红黑树;多于每一个区间[start,edn]调用Ceil(end)找到第一个大于等于end的左区间端点,并返回其下标即可。