3.3 红黑树
3.3.1 定义和性质
-
为什么发明红黑树?
平衡二叉树和红黑树的时间复杂度相同,但是平衡二叉树的平衡特性容易被破坏,需要频繁调整树的形态。
红黑树RBT:插入/删除很多时候不会破坏红黑特性,无需频繁调整树的形态,即需要调整,也可在常数级时间内完成。
-
平衡二叉树:适用于以查为主,少插入删除的场景;
红黑树,适用于频繁插入、删除的场景,实用性更强。
-
-
定义
-
红黑树是二叉排序树 ==》左子树结点<=根结点<= 右子树结点
-
与普通BST相比,有什么要求? ==》
1.每个结点不是红色就是黑色;
2.根结点是黑色;
3.叶结点(外部结点、NULL结点、失败结点)均是黑色;
4.不存在连续两个红色结点(父子不能同为红色);
5.对每个结点,从该结点到任一叶结点的简单路径上,所含黑结点的数目相同。
-
黑高
结点的黑高
bh
——从某结点出发(不含该结点),到达任一空叶结点的路径上黑结点总数。
-
-
性质
1.从根结点到叶结点的最长路径不大于最短路径的2倍;
证明:任何一条查找失败路径上黑结点数都相同,而路径上不能连续出现两个红结点,即红结点只能穿插在各个黑结点中间。
则最长路径为红结点穿插在黑结点间,最短路径是没有红结点。
2.有n个内部节点的红黑树高度 h < = 2 l o g 2 ( n + 1 ) h<=2log_2(n+1) h<=2log2(n+1)
==》 红黑树查找操作时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)
证明:若红黑树总高度=h,则根结点黑高>=h/2,因此内部结点数 n > = 2 h / 2 − 1 n>=2^{h/2}-1 n>=2h/2−1,由此推出 h < = 2 l o g 2 ( n + 1 ) h<=2log_2(n+1) h<=2log2(n+1)
-
与黑高相关的推论
-
根结点黑高为h的红黑树,内部结点数(关键字)至少有多少个?
内部结点数最少的情况——总共h层黑结点的满树形态。
结论:若根结点黑高为h,内部结点数(包含关键字的结点)最少有 2 h − 1 2^h-1 2h−1个
-
3.3.2 查找
- 与BST、AVL相同,从根出发,左小右大,若查找到一个空叶结点,则查找失败。
3.3.3 插入
-
方法
-
先查找,确定插入位置(原理同二叉排序树),插入新结点;
-
若新结点是根——染黑色;
-
若新结点非根——染为红色;
- 若插入新结点后依然满足红黑树定义,则插入结束
- 若插入新结点后不满足红黑树定义,需要调整,使其重新满足红黑树定义
- 黑叔:旋转+染色
- LL型:右单旋,父换爷+染色
- RR型:左单旋,父换爷+染色
- LR型:左右双旋,儿换爷+染色
- RL型:右左双旋,儿换爷+染色
- 红叔:染色+变新
- 黑叔:旋转+染色
-
-
口诀
左根右——RBT是一种BST,需满足左<根<右
根叶黑——根结点和叶结点一定是黑色
不红红——任何一条查找路径上不能连续出现两个红结点
黑路同——从任一结点出发,达到任一空叶结点的路径上经过的黑结点数量相同
-
例题
-
插入20
把20作为根结点插入,并且满足根结点为黑的特性,把该结点染黑。
-
插入10
因为新结点是非根结点,为了满足任一路径上黑结点数相同,所以染为红色插入。
-
插入5
新结点是非根结点,染成红色插入,
但是违反了父子不能同为红的特性,所以要看叔叔结点是什么颜色;
叔叔结点是黑色(黑叔),则要进行旋转+染色;
因为新结点是LL型,所以旋转时遵循右单旋,父换爷;
旋转后,根据红黑树特性染色。
-
插入30
新结点插入后违反父子不能同为红的特性,
叔叔是红色(红叔),则遵循染色+变新,即叔父爷染色(改变颜色),爷变成新结点。
-
插入40
插入后违反父子不能同为红的特性;
叔叔是黑色,新结点是RR型,
则要左单旋,父换爷+染色
-
插入57
-
插入3
不会破坏特性,所以不需要变
-
插入2
-
最后结果
-
3.3.4 删除
-
重点
1.红黑树删除操作的时间复杂度= O ( l o g 2 n ) O(log_2n) O(log2n);
2.在红黑树中删除结点的处理方式和二叉排序树的一样;
3.按第2步删除结点后,可能破坏红黑树特性,此时需要调整结点颜色、位置,使其再次满足红黑树特性。
*完整代码 红黑树
#include <stdio.h>
#include <stdlib.h>#define RED 0
#define BLACK 1// 定义红黑树节点的结构
struct Node {int data; // 节点存储的数据int color; // 节点的颜色,红色为0,黑色为1struct Node *left, *right, *parent; // 左子节点、右子节点、父节点指针
};typedef struct Node Node; // 将结构体 Node 重命名为 Node// 创建一个新节点,并初始化数据
Node* createNode(int data) {// 分配内存空间给新节点Node* newNode = (Node*)malloc(sizeof(Node));// 初始化新节点的数据和颜色(红色)newNode->data = data;newNode->color = RED;// 将左右子节点和父节点指针都设置为 NULLnewNode->left = newNode->right = newNode->parent = NULL;return newNode;
}// 在给定节点处执行左旋转
void rotateLeft(Node **root, Node *x) {// 将 x 的右子节点保存在 y 中Node *y = x->right;// 将 x 的右子节点设置为 y 的左子节点x->right = y->left;// 如果 y 的左子节点非空,则更新其父节点指针指向 xif (y->left != NULL)y->left->parent = x;// 将 y 的父节点指针指向 x 的父节点y->parent = x->parent;// 如果 x 是根节点,则将根节点更新为 yif (x->parent == NULL)(*root) = y;// 如果 x 是其父节点的左子节点,则将 y 设为 x 的父节点的左子节点else if (x == x->parent->left)x->parent->left = y;// 如果 x 是其父节点的右子节点,则将 y 设为 x 的父节点的右子节点elsex->parent->right = y;// 将 x 设为 y 的左子节点y->left = x;// 将 x 的父节点设为 yx->parent = y;
}// 在给定节点处执行右旋转
void rotateRight(Node **root, Node *y) {// 将 y 的左子节点保存在 x 中Node *x = y->left;// 将 y 的左子节点设置为 x 的右子节点y->left = x->right;// 如果 x 的右子节点非空,则更新其父节点指针指向 yif (x->right != NULL)x->right->parent = y;// 将 x 的父节点指针指向 y 的父节点x->parent = y->parent;// 如果 y 是根节点,则将根节点更新为 xif (y->parent == NULL)(*root) = x;// 如果 y 是其父节点的左子节点,则将 x 设为 y 的父节点的左子节点else if (y == y->parent->left)y->parent->left = x;// 如果 y 是其父节点的右子节点,则将 x 设为 y 的父节点的右子节点elsey->parent->right = x;// 将 y 设为 x 的右子节点x->right = y;// 将 y 的父节点设为 xy->parent = x;
}// 修正插入操作可能导致的红黑树性质违反
void fixViolation(Node **root, Node *z) {// 当插入节点不是根节点且父节点为红色时,需要进行修正while (z != *root && z->parent->color == RED) {// 当父节点是祖父节点的左子节点时if (z->parent == z->parent->parent->left) {Node *y = z->parent->parent->right; // 获取叔父节点// 当叔父节点存在且为红色时,进行情况1的处理if (y != NULL && y->color == RED) {z->parent->color = BLACK; // 将父节点设为黑色y->color = BLACK; // 将叔父节点设为黑色z->parent->parent->color = RED; // 将祖父节点设为红色z = z->parent->parent; // 将 z 移动到祖父节点处} else {// 当叔父节点不存在或为黑色时,进行情况2的处理if (z == z->parent->right) { // 如果 z 是父节点的右子节点z = z->parent; // 将 z 移动到父节点处rotateLeft(root, z); // 左旋转}z->parent->color = BLACK; // 将父节点设为黑色z->parent->parent->color = RED; // 将祖父节点设为红色rotateRight(root, z->parent->parent); // 右旋转}} else { // 当父节点是祖父节点的右子节点时,与上述情况对称Node *y = z->parent->parent->left;if (y != NULL && y->color == RED) {z->parent->color = BLACK;y->color = BLACK;z->parent->parent->color = RED;z = z->parent->parent;} else {if (z == z->parent->left) {z = z->parent;rotateRight(root, z);}z->parent->color = BLACK;z->parent->parent->color = RED;rotateLeft(root, z->parent->parent);}}}(*root)->color = BLACK; // 将根节点设为黑色
}// 插入新节点到红黑树中
void insert(Node **root, int data) {Node *newNode = createNode(data); // 创建新节点Node *parent = NULL;Node *current = *root;while (current != NULL) { // 寻找插入位置parent = current;if (newNode->data < current->data)current = current->left;elsecurrent = current->right;}newNode->parent = parent; // 设置新节点的父节点if (parent == NULL)*root = newNode; // 如果树为空,则将新节点设为根节点else if (newNode->data < parent->data)parent->left = newNode; // 如果新节点值小于父节点值,则设为左子节点elseparent->right = newNode; // 否则设为右子节点fixViolation(root, newNode); // 修正插入可能导致的红黑树性质违反
}// 在红黑树中查找指定值的节点
Node* search(Node *root, int data) {while (root != NULL) {if (data < root->data)root = root->left; // 在左子树中查找else if (data > root->data)root = root->right; // 在右子树中查找elsereturn root; // 找到节点}return NULL; // 未找到节点
}// 寻找以给定节点为根的子树中的最小值节点
Node* minValueNode(Node* node) {Node* current = node;while (current->left != NULL)current = current->left; // 不断向左遍历直到最左叶节点return current; // 返回最小值节点
}// 修正双黑节点情况
void fixDoubleBlack(Node **root, Node *x) {if (x == *root) // 如果 x 是根节点,直接返回return;Node *sibling = NULL; // 声明一个指向兄弟节点的指针while (x != *root && x->color == BLACK) { // 当 x 不是根节点且颜色为黑色时执行循环if (x == x->parent->left) { // 如果 x 是父节点的左子节点sibling = x->parent->right; // 获取兄弟节点if (sibling->color == RED) { // 如果兄弟节点为红色sibling->color = BLACK; // 将兄弟节点设为黑色x->parent->color = RED; // 将父节点设为红色rotateLeft(root, x->parent); // 左旋转sibling = x->parent->right; // 更新兄弟节点}if (sibling->left->color == BLACK && sibling->right->color == BLACK) { // 如果兄弟节点的两个子节点都为黑色sibling->color = RED; // 将兄弟节点设为红色x = x->parent; // 将 x 移动到父节点处} else {if (sibling->right->color == BLACK) { // 如果兄弟节点的右子节点为黑色sibling->left->color = BLACK; // 将兄弟节点的左子节点设为黑色sibling->color = RED; // 将兄弟节点设为红色rotateRight(root, sibling); // 右旋转sibling = x->parent->right; // 更新兄弟节点}sibling->color = x->parent->color; // 将兄弟节点的颜色设为父节点的颜色x->parent->color = BLACK; // 将父节点设为黑色sibling->right->color = BLACK; // 将兄弟节点的右子节点设为黑色rotateLeft(root, x->parent); // 左旋转x = *root; // 将 x 设为根节点}} else { // 如果 x 是父节点的右子节点,与上述情况对称sibling = x->parent->left;if (sibling->color == RED) {sibling->color = BLACK;x->parent->color = RED;rotateRight(root, x->parent);sibling = x->parent->left;}if (sibling->right->color == BLACK && sibling->left->color == BLACK) {sibling->color = RED;x = x->parent;} else {if (sibling->left->color == BLACK) {sibling->right->color = BLACK;sibling->color = RED;rotateLeft(root, sibling);sibling = x->parent->left;}sibling->color = x->parent->color;x->parent->color = BLACK;sibling->left->color = BLACK;rotateRight(root, x->parent);x = *root;}}}x->color = BLACK; // 将最终 x 设为黑色
}// 替换节点
void transplant(Node **root, Node *u, Node *v) {if (u->parent == NULL) // 如果 u 是根节点*root = v; // 将根节点设为 velse if (u == u->parent->left) // 如果 u 是其父节点的左子节点u->parent->left = v; // 将 v 设为其父节点的左子节点else // 如果 u 是其父节点的右子节点u->parent->right = v; // 将 v 设为其父节点的右子节点if (v != NULL) // 如果 v 不为空v->parent = u->parent; // 将 v 的父节点设为 u 的父节点
}// 删除节点函数
void deleteNode(Node **root, int data) {Node *z = search(*root, data); // 在树中查找值为 data 的节点if (z == NULL) { // 如果未找到节点printf("Node with value %d not found\n", data); // 输出未找到节点的信息return; // 返回}Node *y = z; // 将 y 设为 zNode *x; // 声明一个指向后继节点的指针 xint yOriginalColor = y->color; // 保存 y 的颜色if (z->left == NULL) { // 如果 z 的左子节点为空x = z->right; // 将 x 设为 z 的右子节点transplant(root, z, z->right); // 将 z 替换为其右子节点} else if (z->right == NULL) { // 如果 z 的右子节点为空x = z->left; // 将 x 设为 z 的左子节点transplant(root, z, z->left); // 将 z 替换为其左子节点} else { // 如果 z 既有左子节点又有右子节点y = minValueNode(z->right); // 找到 z 的右子树中的最小值节点 yyOriginalColor = y->color; // 保存 y 的颜色x = y->right; // 将 x 设为 y 的右子节点if (y->parent == z) // 如果 y 是 z 的直接子节点x->parent = y; // 将 x 的父节点设为 yelse {transplant(root, y, y->right); // 将 y 替换为其右子节点y->right = z->right; // 将 y 的右子节点设为 z 的右子节点y->right->parent = y; // 更新 y 的右子节点的父节点}transplant(root, z, y); // 将 z 替换为 yy->left = z->left; // 将 y 的左子节点设为 z 的左子节点y->left->parent = y; // 更新 y 的左子节点的父节点y->color = z->color; // 将 y 的颜色设为 z 的颜色}if (yOriginalColor == BLACK) // 如果 y 的原始颜色为黑色fixDoubleBlack(root, x); // 修正双黑节点情况free(z); // 释放删除的节点的内存
}// 中序遍历函数
void inorder(Node *root) {if (root == NULL) // 如果根节点为空return; // 返回inorder(root->left); // 递归遍历左子树printf("%d ", root->data); // 输出当前节点的值inorder(root->right); // 递归遍历右子树
}int main() {Node *root = NULL;insert(&root, 7);insert(&root, 3);insert(&root, 18);insert(&root, 10);insert(&root, 22);insert(&root, 8);insert(&root, 11);insert(&root, 26);insert(&root, 2);insert(&root, 6);insert(&root, 13);printf("Inorder traversal of the tree: ");inorder(root);printf("\n");deleteNode(&root, 18);printf("Inorder traversal after deletion of 18: ");inorder(root);printf("\n");int searchData = 10;Node *searchResult = search(root, searchData);if (searchResult != NULL)printf("%d found in the tree.\n", searchData);elseprintf("%d not found in the tree.\n", searchData);int searchData2 = 18;Node *searchResult2 = search(root, searchData2);if (searchResult2 != NULL)printf("%d found in the tree.\n", searchData2);elseprintf("%d not found in the tree.\n", searchData2);return 0;
}