【数据结构】红黑树(定义性质、插入、查找、删除)解析+完整代码

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/21,由此推出 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 2h1

      在这里插入图片描述

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,黑色为1
    struct 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;
    // 将左右子节点和父节点指针都设置为 NULL
    newNode->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 的左子节点非空,则更新其父节点指针指向 x
    if (y->left != NULL)
        y->left->parent = x;
    // 将 y 的父节点指针指向 x 的父节点
    y->parent = x->parent;
    // 如果 x 是根节点,则将根节点更新为 y
    if (x->parent == NULL)
        (*root) = y;
        // 如果 x 是其父节点的左子节点,则将 y 设为 x 的父节点的左子节点
    else if (x == x->parent->left)
        x->parent->left = y;
        // 如果 x 是其父节点的右子节点,则将 y 设为 x 的父节点的右子节点
    else
        x->parent->right = y;
    // 将 x 设为 y 的左子节点
    y->left = x;
    // 将 x 的父节点设为 y
    x->parent = y;
}

// 在给定节点处执行右旋转
void rotateRight(Node **root, Node *y) {
    // 将 y 的左子节点保存在 x 中
    Node *x = y->left;
    // 将 y 的左子节点设置为 x 的右子节点
    y->left = x->right;
    // 如果 x 的右子节点非空,则更新其父节点指针指向 y
    if (x->right != NULL)
        x->right->parent = y;
    // 将 x 的父节点指针指向 y 的父节点
    x->parent = y->parent;
    // 如果 y 是根节点,则将根节点更新为 x
    if (y->parent == NULL)
        (*root) = x;
        // 如果 y 是其父节点的左子节点,则将 x 设为 y 的父节点的左子节点
    else if (y == y->parent->left)
        y->parent->left = x;
        // 如果 y 是其父节点的右子节点,则将 x 设为 y 的父节点的右子节点
    else
        y->parent->right = x;
    // 将 y 设为 x 的右子节点
    x->right = y;
    // 将 y 的父节点设为 x
    y->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;
        else
            current = current->right;
    }
    newNode->parent = parent; // 设置新节点的父节点
    if (parent == NULL)
        *root = newNode; // 如果树为空,则将新节点设为根节点
    else if (newNode->data < parent->data)
        parent->left = newNode; // 如果新节点值小于父节点值,则设为左子节点
    else
        parent->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; // 在右子树中查找
        else
            return 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; // 将根节点设为 v
    else 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 设为 z
    Node *x; // 声明一个指向后继节点的指针 x
    int 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 的右子树中的最小值节点 y
        yOriginalColor = y->color; // 保存 y 的颜色
        x = y->right; // 将 x 设为 y 的右子节点
        if (y->parent == z) // 如果 y 是 z 的直接子节点
            x->parent = y; // 将 x 的父节点设为 y
        else {
            transplant(root, y, y->right); // 将 y 替换为其右子节点
            y->right = z->right; // 将 y 的右子节点设为 z 的右子节点
            y->right->parent = y; // 更新 y 的右子节点的父节点
        }
        transplant(root, z, y); // 将 z 替换为 y
        y->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);
    else
        printf("%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);
    else
        printf("%d not found in the tree.\n", searchData2);

    return 0;
}
  • 34
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值