news 2026/2/11 0:29:04

红黑树入门指南(C语言版)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
红黑树入门指南(C语言版)

红黑树入门指南(C语言版)

文章目录

  • 红黑树入门指南(C语言版)
    • 前言
    • 一、红黑树的基本概念
      • 1.1 核心定义
      • 1.2 关键特性
    • 二、红黑树的操作
      • 2.1 旋转(Rotation)
        • 左旋(Left Rotation)
        • 右旋(Right Rotation)
      • 2.2 插入操作
        • 插入修复的三种情况(父节点为红色时):
      • 2.3 删除操作
        • 删除修复的四种情况(替代节点`s`为黑色时):
    • 三、红黑树的C语言实现
      • 3.1 数据结构定义
      • 3.2 辅助函数(创建节点、打印树)
      • 3.3 旋转操作实现
      • 3.4 插入与修复操作
      • 3.5 测试代码
    • 四、红黑树的应用场景
    • 五、总结

前言

红黑树是一种自平衡二叉搜索树,它通过额外的颜色规则保证在最坏情况下基本操作(插入、删除、查找)的时间复杂度为O(log n)。相比AVL树,红黑树的平衡条件更宽松,因此旋转操作更少,适合需要频繁修改的场景(如STL的mapset)。

一、红黑树的基本概念

1.1 核心定义

红黑树是每个节点带有颜色属性(红色或黑色)的二叉搜索树,需满足以下5条性质

  1. 节点颜色:每个节点要么是红色,要么是黑色。
  2. 根节点:根节点是黑色。
  3. 叶子节点(NIL):所有叶子节点(空节点,通常用NIL表示)是黑色。
  4. 红色节点的子节点:若一个节点是红色,则它的两个子节点必须是黑色(即不存在连续的红色节点)。
  5. 黑高一致:从任一节点到其所有后代叶子节点的路径中,包含相同数量的黑色节点(称为该节点的黑高)。

1.2 关键特性

  • 近似平衡:红黑树不追求严格的左右子树高度差≤1(如AVL树),但通过性质4和5确保最长路径不超过最短路径的2倍(最短路径全黑,最长路径红黑交替)。
  • NIL节点:实际实现中,为了简化边界条件(如叶子节点无子节点),通常用一个统一的NIL节点代替所有空指针(颜色为黑)。

二、红黑树的操作

红黑树的核心操作包括查找插入删除。其中查找与普通二叉搜索树一致,插入和删除后可能破坏红黑树性质,需通过旋转重新着色修复。

2.1 旋转(Rotation)

旋转是调整树结构的基础操作,分为左旋右旋,用于保持二叉搜索树性质的同时调整节点位置。

左旋(Left Rotation)

以节点x为中心,将x的右子节点y提升为父节点,x成为y的左子节点,y的原左子节点成为x的右子节点。

右旋(Right Rotation)

与左旋对称,以节点y为中心,将y的左子节点x提升为父节点,y成为x的右子节点,x的原右子节点成为y的左子节点。

2.2 插入操作

插入步骤:

  1. 按二叉搜索树规则插入新节点(初始颜色设为红色,避免破坏性质5)。
  2. 若插入后父节点是黑色,无需调整;若父节点是红色(违反性质4),需通过重新着色旋转修复。
插入修复的三种情况(父节点为红色时):

假设新节点为z,父节点p(z),祖父节点g(z),叔节点u(z)p(z)的兄弟节点):

  • 情况1:叔节点u(z)是红色 → 将p(z)u(z)设为黑色,g(z)设为红色,然后递归检查g(z)
  • 情况2:叔节点u(z)是黑色,且zp(z)的右子节点 → 对p(z)左旋,转化为情况3。
  • 情况3:叔节点u(z)是黑色,且zp(z)的左子节点 → 将p(z)设为黑色,g(z)设为红色,对g(z)右旋。

2.3 删除操作

删除步骤:

  1. 按二叉搜索树规则删除节点(找到替代节点s,可能是后继或前驱)。
  2. 若替代节点是黑色,删除后会破坏性质5(黑高减少),需通过重新着色旋转修复。
删除修复的四种情况(替代节点s为黑色时):

假设当前节点为x(替代节点),兄弟节点s

  • 情况1s是红色 → 将s设为黑色,父节点设为红色,对父节点左旋,更新s为新兄弟节点。
  • 情况2s是黑色,且s的两个子节点都是黑色 → 将s设为红色,x上移至父节点。
  • 情况3s是黑色,左子红、右子黑 → 将s设为红色,左子设为黑色,对s右旋,更新s为新兄弟节点。
  • 情况4s是黑色,右子红 → 将s的颜色设为父节点颜色,父节点设为黑色,右子设为黑色,对父节点左旋,x设为根节点结束。

三、红黑树的C语言实现

以下是简化版的红黑树实现(仅包含插入和遍历,删除操作类似但更复杂)

3.1 数据结构定义

// 节点颜色枚举typedefenum{RED,BLACK}Color;// 红黑树节点(使用NIL节点简化边界处理)typedefstructNode{intdata;// 数据域Color color;// 颜色structNode*left;// 左子节点structNode*right;// 右子节点structNode*parent;// 父节点}Node;// 全局NIL节点(所有空指针指向它)Node*NIL;// 初始化NIL节点voidinitNIL(){NIL=(Node*)malloc(sizeof(Node));NIL->color=BLACK;NIL->left=NIL->right=NIL->parent=NULL;}

3.2 辅助函数(创建节点、打印树)

// 创建新节点(初始颜色为RED)Node*createNode(intdata){Node*node=(Node*)malloc(sizeof(Node));node->data=data;node->color=RED;// 新节点默认红色node->left=node->right=node->parent=NIL;returnnode;}// 中序遍历(验证二叉搜索树性质)voidinOrderTraversal(Node*root){if(root==NIL)return;inOrderTraversal(root->left);printf("%d(%s) ",root->data,root->color==RED?"R":"B");inOrderTraversal(root->right);}// 层序遍历(直观查看树结构)voidlevelOrderTraversal(Node*root){if(root==NIL){printf("Tree is empty\n");return;}Node*queue[100];intfront=0,rear=0;queue[rear++]=root;while(front<rear){Node*current=queue[front++];printf("%d(%s) ",current->data,current->color==RED?"R":"B");if(current->left!=NIL)queue[rear++]=current->left;if(current->right!=NIL)queue[rear++]=current->right;}printf("\n");}

3.3 旋转操作实现

// 左旋(x为旋转中心)voidleftRotate(Node**root,Node*x){Node*y=x->right;// y是x的右子节点x->right=y->left;// y的左子节点变为x的右子节点if(y->left!=NIL){y->left->parent=x;}y->parent=x->parent;// y继承x的父节点if(x->parent==NIL){*root=y;// x是根节点,y成为新根}elseif(x==x->parent->left){x->parent->left=y;// x是左子节点,y替换x的位置}else{x->parent->right=y;// x是右子节点,y替换x的位置}y->left=x;// x成为y的左子节点x->parent=y;}// 右旋(与左旋对称)voidrightRotate(Node**root,Node*y){Node*x=y->left;// x是y的左子节点y->left=x->right;// x的右子节点变为y的左子节点if(x->right!=NIL){x->right->parent=y;}x->parent=y->parent;// x继承y的父节点if(y->parent==NIL){*root=x;// y是根节点,x成为新根}elseif(y==y->parent->left){y->parent->left=x;// y是左子节点,x替换y的位置}else{y->parent->right=x;// y是右子节点,x替换y的位置}x->right=y;// y成为x的右子节点y->parent=x;}

3.4 插入与修复操作

// 插入修复(处理插入后可能的红红冲突)voidinsertFixup(Node**root,Node*z){while(z->parent->color==RED){// 父节点为红色时需修复if(z->parent==z->parent->parent->left){// 父节点是祖父的左子Node*y=z->parent->parent->right;// 叔节点if(y->color==RED){// 情况1:叔节点红z->parent->color=BLACK;y->color=BLACK;z->parent->parent->color=RED;z=z->parent->parent;// 检查祖父}else{if(z==z->parent->right){// 情况2:z是右子z=z->parent;leftRotate(root,z);}// 情况3:z是左子z->parent->color=BLACK;z->parent->parent->color=RED;rightRotate(root,z->parent->parent);}}else{// 父节点是祖父的右子(对称处理)Node*y=z->parent->parent->left;// 叔节点if(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;rightRotate(root,z);}z->parent->color=BLACK;z->parent->parent->color=RED;leftRotate(root,z->parent->parent);}}}(*root)->color=BLACK;// 确保根节点为黑}// 插入节点(返回根节点)Node*insert(Node*root,intdata){Node*z=createNode(data);Node*y=NIL;// y记录z的父节点Node*x=root;// 寻找插入位置(类似BST插入)while(x!=NIL){y=x;if(z->data<x->data){x=x->left;}else{x=x->right;}}z->parent=y;if(y==NIL){root=z;// 树为空,z成为根}elseif(z->data<y->data){y->left=z;}else{y->right=z;}// 修复红黑树性质insertFixup(&root,z);returnroot;}

3.5 测试代码

intmain(){initNIL();// 初始化NIL节点Node*root=NIL;// 插入测试数据inttestData[]={10,20,30,15,25,5};intn=sizeof(testData)/sizeof(testData[0]);for(inti=0;i<n;i++){root=insert(root,testData[i]);printf("插入 %d 后层序遍历: ",testData[i]);levelOrderTraversal(root);}// 验证中序遍历(应有序)printf("中序遍历结果: ");inOrderTraversal(root);printf("\n");return0;}

四、红黑树的应用场景

红黑树因其高效的动态操作性能,被广泛应用于需要快速查找、插入、删除的场景:

  • C++ STLmapsetmultimapmultiset的底层实现(GCC使用红黑树)。
  • Java集合TreeMapTreeSet基于红黑树。
  • Linux内核:进程调度(CFS)、内存管理中的区间树。
  • 数据库索引:部分数据库的索引结构(如MySQL的某些存储引擎)。

五、总结

红黑树通过颜色规则和旋转操作,在动态数据维护中实现了高效的平衡。其核心是理解插入/删除后的修复逻辑(尤其是各种情况的处理顺序)。尽管代码实现较复杂,但掌握红黑树能帮助我们理解许多高级数据结构的设计思想。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 7:33:55

代码克隆检测的挑战与AI的机遇

代码克隆检测是软件测试中的重要环节&#xff0c;涉及识别代码库中的相似或重复片段。传统方法如基于文本、令牌或抽象语法树&#xff08;AST&#xff09;的匹配&#xff0c;虽有一定效果&#xff0c;但常面临高误报率、难以检测语义克隆&#xff08;功能相似但结构不同&#x…

作者头像 李华
网站建设 2026/1/29 6:47:04

35、RAID 系统迁移与管理全攻略

RAID 系统迁移与管理全攻略 1. RAID 基础管理 在 RAID 系统中,如果需要更换磁盘,可按以下步骤操作: - 用新磁盘替换旧磁盘,并对新磁盘进行分区。要确保新分区的大小等于或大于 RAID 阵列中其他分区。 - 新分区准备好后,使用 --add 命令将其添加到阵列: $ sudo md…

作者头像 李华
网站建设 2026/2/9 2:48:20

37、构建高可用Linux集群:Heartbeat实战指南

构建高可用Linux集群:Heartbeat实战指南 在服务器运行过程中,即使主机配备了RAID和以太网绑定,仍有许多组件可能出现故障,从CPU到主机上的软件都有可能。若要确保服务在主机故障时仍能正常运行,就需要构建集群。本文将介绍基本Linux集群中常用的工具Heartbeat,并详细说明…

作者头像 李华
网站建设 2026/1/31 7:12:59

38、构建高可用集群:Heartbeat与DRBD实战指南

构建高可用集群:Heartbeat与DRBD实战指南 1. 集群准备与Heartbeat简介 在集群搭建过程中,当完成故障转移(fail back)相关操作后,集群就可以进行剩余的测试,适当调整超时设置,随后便可投入实际使用。之前的示例为搭建自己的集群服务提供了一个良好的开端,但它并未涵盖…

作者头像 李华
网站建设 2026/1/31 5:37:36

46、Linux 实用命令与技巧大揭秘

Linux 实用命令与技巧大揭秘 在 Linux 系统的使用过程中,掌握一些实用的命令和技巧能让我们的工作更加高效。下面将为大家详细介绍一系列实用的 Linux 命令及操作方法。 命令路径快捷查找 有时候,我们想查看二进制路径下的某个 shell 脚本,但却记不清它具体位于 /bin 、…

作者头像 李华
网站建设 2026/2/10 6:08:07

25、Ubuntu 网络应用全攻略

Ubuntu 网络应用全攻略 1. Firefox 浏览器使用技巧 Firefox 支持标签式窗口,提供了多种打开新标签的方式: - 点击“New Tab”按钮(现有标签右侧的“+”符号)。 - 按住“Ctrl”键并点击链接,可在新标签中打开。 - 按下“Ctrl - T”组合键。 - 若鼠标有中键,有时点击…

作者头像 李华