目录

前言:

一、红黑树简介

二、红黑树的概念及性质

三、红黑树的等价变换

1. 旋转操作

2. 颜色重涂

四、红黑树的操作

0、基础结构

1、旋转操作

① 左旋

 ② 右旋

 2、插入操作

① cur为红,p为红,g为黑,u存在且为红

② cur为红,p为红,g为黑,u不存在/u存在且为黑

③ cur为红,p为红,g为黑,u不存在/u存在且为黑

代码示例 


前言:

既完成了二叉搜索树、平衡二叉树的学习了解后,终于来到了传说中的红黑树。了解到AVL树的性质,其实其最大的作用就是查找。平衡二叉树的一系列相关操作即使是最坏情况下也可以达到O(logn)的效率。如果在AVL树中插入或删除节点后,使得高度之差大于1。此时,AVL树的平衡状态就被破坏,它就不再是一棵二叉树;为了让它重新维持在一个平衡状态,就需要对其进行旋转处理。因此,红黑树的理论就被创建出来,比如C++ STL中的map就是是基于红黑树结构实现的。那么红黑树到底比AVL树好在哪里?

一、红黑树简介

红黑树是一种高效的自平衡的二叉查找树,在创建初期被称为平衡二叉B树(symmetric binary B-trees),后来才 修改为如今的红黑树。红黑树具有良好的效率,它可在O(logN) 时间内完成查找、增加、删除等操作。

对于二叉搜索树,如果插入的数据是随机的,那么它就是接近平衡的二叉树,平衡的二叉树,它的操作效率(查询,插入,删除)效率较高,时间复杂度是O(logN)。但是可能会出现一种极端的情况,那就是插入的数据是有序的,导致整棵树变得不那么平衡,从而退化,那么所有的节点都会在根节点的同一侧。此时,二叉搜索树就变为了一个链表,它的操作效率就降低,时间复杂度为O(N),所以可以认为二叉搜索树的时间复杂度介于O(logN)和O(N)之间。为了应对这种极端情况,红黑树就出现了。其具备了某些二叉搜索树的特性,能解决非平衡树问题,红黑树是一种接近平衡的二叉树,因其并不具备同平衡二叉树一样的所谓平衡因子,而是依靠红黑结点的性质维持,所以其本身并不像二叉搜索树->平衡二叉树这种,存在所谓类似继承的关系。

二、红黑树的概念及性质

每棵树都具有以下特点,首先的是基本构造:

parent:父结点

sibling:兄弟结点
uncle:叔父结点( parent 的兄弟结点)
grand:祖父结点( parent 的父结点)
首先,红黑树是一个二叉搜索树,它在每个节点增加了一个存储位记录结点的颜色,可以是红,也可以是黑;通过任意一条从根到叶子简单路径上颜色的约束,红黑树保证最长路径不超过最短路径的二倍,因而近似平衡

它同时满足以下特性:

1.结点是红色或黑色
2.根是黑色
3.叶子结点(外部结点,空结点)都是黑色,这里的叶子结点指的是最底层的空节点(外部结点),null结点的父结点在红黑树里不看作叶子结点
4.不能同时连续出现两个红结点
5.从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

 在进行判断是否满足性质时,一定需要注意,真正的叶子结点是NULL结点,而非具实质性键值的结点。

三、红黑树的等价变换

红黑树是一种自平衡二叉搜索树,通过颜色标记(红 / 黑)和特定规则保持平衡。等价变换是指在不破坏红黑树性质(如颜色规则、平衡特性)的前提下,对树结构进行的局部调整操作,主要通过 旋转(Rotation) 颜色重涂(Recoloring)实现。

1. 旋转操作

旋转是红黑树调整平衡的核心手段,分为左旋(Left Rotation)右旋(Right Rotation),目的是通过改变节点的父子关系,在保持二叉搜索树性质的同时调整树的高度。

左旋示例

  • 场景:当某个节点的右子节点高度过高,破坏平衡。
  • 操作:将该节点作为左子节点,其右子节点成为新的父节点,并重新连接子树。
  • 效果:降低右子树高度,提升左子树高度,保持搜索树性质。

2. 颜色重涂

通过改变节点颜色(红→黑或黑→红),配合旋转操作,恢复红黑树的颜色规则(如 “红节点的子节点必须为黑”“根节点为黑” 等)。

  • 典型场景:插入或删除节点后,可能违反颜色规则,需通过颜色重涂和旋转组合调整。

等价变换的重要性:

1. 维持树的平衡:

① 红黑树通过等价变换将树的高度控制在O(log n)级别,避免退化为链表。

② 这使得搜索、插入、删除等操作的时间复杂度稳定为O(log n),适用于高频动态数据操作场景(如 Java 的 TreeMap、C++ 的 std::map)。

2. 遵守红黑树性质:

红黑树有 5 条核心性质(如根黑、红子黑、黑高相等),等价变换是唯一能在调整结构时不破坏这些性质的手段。

3. 高效处理动态数据

① 在进行一系列操作时,红黑树只需进行局部变换,无需重构整个树,效率远高于其他平衡树(如 AVL 树的全局调整)。

② 这使其在数据库索引、内存数据结构等场景中广泛应用。

四、红黑树的操作

红黑树的基本操作和其他树形结构一样,一般都包括查找、插入、删除等操作。前面说到,红黑树是一种自平衡的二叉查找树,既然是二叉查找树的一种,那么查找过程和二叉查找树一样,较为简单。相对于查找操作,红黑树的插入和删除操作就要复杂的多

0、基础结构

//宏定义颜色
#define red 0
#define black 1
 
//数据类型Datatype
typedef char Datatype;
//红黑树节点存储结构
typedef struct node {
	Datatype data;
	int color;
	int key;//排序键值,根据key大小排序
	struct node* par;//父节点指针
	struct node* left, * right;//左右子节点指针
}Node;
 
//红黑树的定义rbtree
typedef struct tree {
	Node* root;//指向根节点指针
	Node* nil;//叶子节点(哨兵)
}rbtree;

1、旋转操作

旋转操作分为左旋和右旋,左旋是将某个节点旋转为其右孩子的左孩子,而右旋是节点旋转为其左孩子的右孩子。

① 左旋

左旋是一种将某个节点的右子节点旋转上来的操作。也就是说当前节点的右子节点顶替了自己,然后自己变为右子节点的左子节点,以保持树的平衡。

操作如下:

1、将当前节点的右子节点设为新的父节点。
2、将新的父节点的左子节点设为当前节点的右子节点。
3、如果当前节点有父节点,将新的父节点替代当前节点的位置。
4、将当前节点设为新的父节点的左子节点。

//左旋(以x为旋转点,向左旋转)
void left_rotate(rbtree* T, Node* x) {
	Node* y = x->right;//标记到右子节点
	x->right = y->left;//y的左子节点代替x的右子节点
	if (x->right != T->nil)
		x->right->par = x;//如果不为空(nil)其父节点指向x
	y->par = x->par;//把y的父节点指向x的父节点,此时x与y没有直接联系了
	if (x->par == T->nil) {//判断x的父节点是否为根结点
		T->root = y;//如果是的话,y就变为根结点
	}
	else {
		//y顶替x的位置
		if (x == x->par->left)
			x->par->left = y;//如果x是父节点的左边,那y就代替x成为左子节点
		else
			x->par->right = y;//如果x是父节点的右边,那y就代替x成为右子节点
	}
	//y的左子节点指向x,x的父节点指向y
	y->left = x;
	x->par = y;
}

 ② 右旋

同样的右旋也是将左子节点顶替自己成为父节点, 然后自己成为左子节点的右子节点。

操作如下:

1、将当前节点的左子节点设为新的父节点

2、将新的父节点的右子节点设为当前节点的左子节点

3、如果当前节点有父节点,将新的父节点替代当前节点的位置

4、将当前节点设为新的父节点的右子节点

//右旋(以x为旋转点,向右旋转)
void right_rotate(rbtree* T, Node* x) {
	Node* y = x->left;//标记到左子节点y
	x->left = y->right;//y的右子节点代替x的左子节点
	if (x->left != T->nil)
		x->left->par = x;
	y->par = x->par;//y的父节点指向x的父节点
	if (x->par == T->nil)
		T->root = y;//如果x是根结点的话,那么y代替x成为根结点
	else {
		if (x == x->par->left)
			x->par->left = y;
		else
			x->par->right = y;
	}
	//y的右子节点指向x,x的父节点为y
	y->right = x;
	x->par = y;
}

 2、插入操作

红黑树是在二叉搜索树的基础上加上其平衡限制条件,因此红黑树的插入可分为两步:

1、按照二叉搜索的树规则插入新节点
2、检测新节点插入后,红黑树的性质是否造到破坏

因为新节点的默认颜色是红色,因此:

如果其双亲节点的颜色是黑色,没有违反红黑树任何
性质,则不需要调整;但当新插入节点的双亲节点颜色为红色时,就违反了不能有连
在一起的红色节点,此时需要对红黑树分情况来讨论:

约定 :cur 为当前节点, p 为父节点, g 为祖父节点, u 为叔叔节点

情况一: cur为红,p为红,g为黑,u存在且为红
情况二: cur为红,p为红,g为黑,u不存在/u存在且为黑
情况三: cur为红,p为红,g为黑,u不存在/u存在且为黑

① cur为红,p为红,g为黑,u存在且为红

解决方式:

将p,u改为黑,g改为红,然后把g当成cur,继续向上调整

1、如果g是根节点,调整完后要将g变为黑色

2、如果g不是根节点,继续向上调整

② cur为红,p为红,g为黑,u不存在/u存在且为黑

解决方式:

p为 g 的左孩子, cur 为 p 的左孩子,则对g进行右单旋转;相反,p为g 的右孩子, cur 为 p的右孩子,则对g进行左单旋转p、g 变色 --p 变黑, g 变红

③ cur为红,p为红,g为黑,u不存在/u存在且为黑

解决方式:

p为 g 的左孩子, cur 为 p 的右孩子,则针对 p做左单旋转,再对g进行右单旋转;相反,p为g 的右孩子, cur 为 p 的左孩子,则针对 p做右单旋转,再对g进行左单旋转

代码示例 

#include <stdlib.h>
#include <stdbool.h>

// 定义颜色枚举
typedef enum { RED, BLACK } Color;

// 定义键值对结构
typedef struct {
    void* key;
    void* value;
} Pair;

// 定义节点结构
typedef struct Node {
    Pair kv;
    struct Node* left;
    struct Node* right;
    struct Node* parent;
    Color col;
} Node;

// 定义红黑树结构
typedef struct {
    Node* root;
    // 比较函数指针
    int (*compare)(const void*, const void*);
} RBTree;

// 创建新节点
Node* createNode(Pair kv) {
    Node* node = (Node*)malloc(sizeof(Node));
    if (node == NULL) return NULL;
    node->kv = kv;
    node->left = NULL;
    node->right = NULL;
    node->parent = NULL;
    node->col = RED;
    return node;
}

// 左旋函数
void RotateL(RBTree* tree, Node* x) {
    Node* y = x->right;
    x->right = y->left;
    if (y->left != NULL) {
        y->left->parent = x;
    }
    y->parent = x->parent;
    if (x->parent == NULL) {
        tree->root = y;
    } else if (x == x->parent->left) {
        x->parent->left = y;
    } else {
        x->parent->right = y;
    }
    y->left = x;
    x->parent = y;
}

// 右旋函数
void RotateR(RBTree* tree, Node* y) {
    Node* x = y->left;
    y->left = x->right;
    if (x->right != NULL) {
        x->right->parent = y;
    }
    x->parent = y->parent;
    if (y->parent == NULL) {
        tree->root = x;
    } else if (y == y->parent->right) {
        y->parent->right = x;
    } else {
        y->parent->left = x;
    }
    x->right = y;
    y->parent = x;
}

// 插入函数
bool RBTree_Insert(RBTree* tree, Pair kv) {
    if (tree == NULL) return false;
    
    Node* parent = NULL;
    Node* cur = tree->root;
    
    // 查找插入位置
    while (cur != NULL) {
        int cmp = tree->compare(kv.key, cur->kv.key);
        if (cmp > 0) {
            parent = cur;
            cur = cur->right;
        } else if (cmp < 0) {
            parent = cur;
            cur = cur->left;
        } else {
            // 键已存在
            return false;
        }
    }
    
    // 创建新节点
    Node* newNode = createNode(kv);
    if (newNode == NULL) return false;
    
    // 插入新节点
    if (parent == NULL) {
        tree->root = newNode;
        newNode->col = BLACK; // 根节点为黑色
        return true;
    }
    
    int cmp = tree->compare(kv.key, parent->kv.key);
    if (cmp > 0) {
        parent->right = newNode;
    } else {
        parent->left = newNode;
    }
    newNode->parent = parent;
    
    // 调整红黑树
    while (parent != NULL && parent->col == RED) {
        Node* grandfather = parent->parent;
        if (parent == grandfather->left) {
            Node* uncle = grandfather->right;
            if (uncle != NULL && uncle->col == RED) {
                // 情况1:叔叔节点为红色
                parent->col = BLACK;
                uncle->col = BLACK;
                grandfather->col = RED;
                // 继续向上调整
                cur = grandfather;
                parent = cur->parent;
            } else {
                // 情况2或3:叔叔节点为黑色
                if (cur == parent->right) {
                    // 情况2:需要左旋
                    RotateL(tree, parent);
                    // 交换指针
                    Node* temp = parent;
                    parent = cur;
                    cur = temp;
                }
                // 情况3:右旋
                parent->col = BLACK;
                grandfather->col = RED;
                RotateR(tree, grandfather);
                break;
            }
        } else {
            // 镜像对称情况
            Node* uncle = grandfather->left;
            if (uncle != NULL && uncle->col == RED) {
                // 情况1:叔叔节点为红色
                parent->col = BLACK;
                uncle->col = BLACK;
                grandfather->col = RED;
                // 继续向上调整
                cur = grandfather;
                parent = cur->parent;
            } else {
                // 情况2或3:叔叔节点为黑色
                if (cur == parent->left) {
                    // 情况2:需要右旋
                    RotateR(tree, parent);
                    // 交换指针
                    Node* temp = parent;
                    parent = cur;
                    cur = temp;
                }
                // 情况3:左旋
                parent->col = BLACK;
                grandfather->col = RED;
                RotateL(tree, grandfather);
                break;
            }
        }
    }
    
    // 确保根节点为黑色
    tree->root->col = BLACK;
    return true;
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐