当前位置: 首页 > news >正文

从HashMap到红黑树:手把手带你用C语言实现一个简易版(附OpenHarmony源码分析)

从HashMap到红黑树:手把手带你用C语言实现一个简易版(附OpenHarmony源码分析)

在计算机科学领域,数据结构的选择往往决定了程序的性能边界。当我们使用各种高级语言中的Map或HashMap时,很少思考它们背后的魔法是如何实现的。实际上,这些高效数据结构的核心引擎之一就是红黑树——一种看似复杂却极其优雅的平衡二叉搜索树。

本文将带您深入工程实践,从HashMap的需求出发,逐步揭示红黑树的核心价值。不同于纯理论讲解,我们会结合OpenHarmony操作系统中的真实源码,最终用C语言实现一个支持基本操作的红黑树结构。您将看到算法理论如何转化为实际可用的代码,以及为什么红黑树会成为工业级系统的首选平衡树结构。

1. 为什么HashMap需要树结构

现代编程语言中的HashMap通常被实现为数组加链表的结构,这种设计在理想情况下能提供O(1)时间复杂度的访问性能。然而,当哈希冲突严重时,链表可能退化为线性结构,导致性能急剧下降。这时,将链表转换为平衡树就成为提升性能的关键策略。

以Java 8的HashMap实现为例,当链表长度超过阈值(默认为8)时,链表会自动转换为红黑树:

// 伪代码展示链表转树的逻辑 if (binCount >= TREEIFY_THRESHOLD - 1) { treeifyBin(tab, hash); break; }

这种混合设计结合了哈希表的最佳平均情况和树结构的最差情况保障。红黑树在此场景中的优势主要体现在:

  • 最差情况下仍保持O(log n)操作复杂度
  • 相对AVL树更少的旋转操作,适合频繁插入删除的场景
  • 内存效率高,每个节点只需额外1bit存储颜色信息

在OpenHarmony的hashmap实现中,我们也能看到类似的设计思想。例如在kernel/linux/linux-5.10/lib/rbtree.c中,红黑树被广泛用于管理各种内核数据结构。

2. 红黑树核心原理剖析

红黑树通过五条简单的规则维持平衡,这些规则看似简单却蕴含着精妙的设计:

  1. 节点非红即黑
  2. 根节点必须为黑
  3. 红色节点的子节点必须为黑(无连续红节点)
  4. 从任一节点到其叶子的所有路径包含相同数量的黑节点
  5. NIL节点视为黑色

这些规则确保了红黑树的关键特性:从根到最远叶子的路径不超过最近路径的两倍。下面是一个典型红黑树的结构示例:

B8 / \ R4 R12 / \ / \ B2 B6 B10 B14

与AVL树相比,红黑树的平衡要求更为宽松,这带来了实际性能优势:

特性AVL树红黑树
平衡严格度严格平衡近似平衡
查找效率更高稍低
插入/删除更多旋转操作更少旋转操作
适用场景查找密集型混合操作型

在OpenHarmony的进程调度器中,红黑树被用来管理就绪队列。kernel/liteos_a/libs/libc/src/queue.c中的实现展示了如何利用红黑树高效管理动态任务。

3. 红黑树节点设计与基础操作

让我们从基础结构开始,定义一个红黑树节点:

typedef enum { RED, BLACK } Color; typedef struct RBNode { int key; Color color; struct RBNode *left; struct RBNode *right; struct RBNode *parent; } RBNode;

这个结构体包含了红黑树节点的所有必要信息。值得注意的是,我们显式存储了父节点指针,这在红黑树操作中至关重要。OpenHarmony的third_party/e2fsprogs/lib/ext2fs/rbtree.c中也采用了类似的设计。

初始化新节点的通用模式如下:

RBNode* create_node(int key) { RBNode* node = (RBNode*)malloc(sizeof(RBNode)); node->key = key; node->color = RED; // 新节点默认为红色 node->left = node->right = node->parent = NULL; return node; }

新节点初始为红色,这可能会违反红黑树规则,需要通过后续调整来修复。这种设计减少了需要处理的平衡情况,是红黑树高效的关键之一。

4. 红黑树插入算法实现

红黑树的插入过程分为两个阶段:标准BST插入和平衡修复。让我们通过具体代码实现这一过程。

4.1 标准BST插入

首先实现标准的二叉搜索树插入逻辑:

void rb_insert(RBNode** root, int key) { RBNode* node = create_node(key); RBNode* parent = NULL; RBNode* current = *root; // 查找插入位置 while (current != NULL) { parent = current; if (node->key < current->key) current = current->left; else current = current->right; } node->parent = parent; if (parent == NULL) { *root = node; // 树为空 } else if (node->key < parent->key) { parent->left = node; } else { parent->right = node; } // 调整红黑树平衡 rb_insert_fixup(root, node); }

4.2 红黑树平衡修复

插入后的平衡修复是红黑树最复杂的部分,需要考虑多种情况:

void rb_insert_fixup(RBNode** root, RBNode* node) { while (node != *root && node->parent->color == RED) { if (node->parent == node->parent->parent->left) { RBNode* uncle = node->parent->parent->right; // Case 1: 叔叔节点为红色 if (uncle != NULL && uncle->color == RED) { node->parent->color = BLACK; uncle->color = BLACK; node->parent->parent->color = RED; node = node->parent->parent; } else { // Case 2: 节点是右孩子 if (node == node->parent->right) { node = node->parent; left_rotate(root, node); } // Case 3: 节点是左孩子 node->parent->color = BLACK; node->parent->parent->color = RED; right_rotate(root, node->parent->parent); } } else { // 对称情况处理... } } (*root)->color = BLACK; }

旋转操作是维持平衡的关键,下面是左旋的实现:

void left_rotate(RBNode** root, RBNode* x) { RBNode* y = x->right; x->right = y->left; if (y->left != NULL) y->left->parent = x; y->parent = x->parent; if (x->parent == NULL) *root = y; else if (x == x->parent->left) x->parent->left = y; else x->parent->right = y; y->left = x; x->parent = y; }

OpenHarmony的kernel/linux/linux-5.10/lib/rbtree.c中实现了类似的旋转逻辑,但更加优化,考虑了各种边界条件。

5. 红黑树删除操作详解

删除操作比插入更为复杂,因为它可能同时影响树的平衡和颜色属性。我们分步骤实现这一过程。

5.1 标准BST删除

首先实现替换节点的辅助函数:

void rb_transplant(RBNode** root, RBNode* u, RBNode* v) { if (u->parent == NULL) *root = v; else if (u == u->parent->left) u->parent->left = v; else u->parent->right = v; if (v != NULL) v->parent = u->parent; }

然后是主要的删除逻辑:

void rb_delete(RBNode** root, int key) { RBNode* z = rb_search(*root, key); if (z == NULL) return; RBNode* y = z; Color y_original_color = y->color; RBNode* x; if (z->left == NULL) { x = z->right; rb_transplant(root, z, z->right); } else if (z->right == NULL) { x = z->left; rb_transplant(root, z, z->left); } else { y = tree_minimum(z->right); y_original_color = y->color; x = y->right; if (y->parent == z) { if (x != NULL) x->parent = y; } else { rb_transplant(root, y, y->right); y->right = z->right; y->right->parent = y; } rb_transplant(root, z, y); y->left = z->left; y->left->parent = y; y->color = z->color; } if (y_original_color == BLACK) rb_delete_fixup(root, x); free(z); }

5.2 删除后的平衡修复

删除黑色节点后,需要通过复杂的修复过程恢复红黑树性质:

void rb_delete_fixup(RBNode** root, RBNode* x) { while (x != *root && (x == NULL || x->color == BLACK)) { if (x == x->parent->left) { RBNode* w = x->parent->right; // Case 1: 兄弟节点为红色 if (w->color == RED) { w->color = BLACK; x->parent->color = RED; left_rotate(root, x->parent); w = x->parent->right; } // Case 2: 兄弟节点的两个子节点都是黑色 if ((w->left == NULL || w->left->color == BLACK) && (w->right == NULL || w->right->color == BLACK)) { w->color = RED; x = x->parent; } else { // Case 3: 兄弟节点的右子节点为黑色 if (w->right == NULL || w->right->color == BLACK) { if (w->left != NULL) w->left->color = BLACK; w->color = RED; right_rotate(root, w); w = x->parent->right; } // Case 4 w->color = x->parent->color; x->parent->color = BLACK; if (w->right != NULL) w->right->color = BLACK; left_rotate(root, x->parent); x = *root; } } else { // 对称情况处理... } } if (x != NULL) x->color = BLACK; }

OpenHarmony中的实现(如third_party/mtd-utils/jffsX-utils/rbtree.c)处理了更多边界情况,值得参考。

6. 红黑树在OpenHarmony中的实际应用

OpenHarmony作为现代操作系统,大量使用了红黑树来管理各种内核资源。让我们分析几个典型应用场景:

6.1 进程调度

kernel/liteos_a/libs/libc/src/queue.c中,红黑树被用来管理就绪队列:

struct ProcessControlBlock { int pid; int priority; RBNode rb_node; // 嵌入红黑树节点 // 其他字段... };

调度器可以根据优先级快速找到最高优先级的就绪进程,时间复杂度仅为O(log n)。

6.2 内存管理

OpenHarmony的内存管理系统使用红黑树跟踪内存块:

struct MemoryChunk { size_t size; void* start_addr; RBNode size_node; // 按大小组织的红黑树 RBNode addr_node; // 按地址组织的红黑树 };

这种双重索引结构使得系统可以:

  • 快速找到足够大的空闲块(最佳适配)
  • 快速合并相邻空闲块

6.3 文件系统

third_party/e2fsprogs/lib/ext2fs/rbtree.c中,红黑树被用于:

  • 目录项缓存
  • 文件块映射管理
  • 磁盘配额跟踪

以下是一个简化的文件块管理示例:

struct FileBlock { unsigned long block_no; RBNode node; // 其他元数据... }; // 按块号查找 struct FileBlock* find_block(RBNode* root, unsigned long block_no) { RBNode* current = root; while (current != NULL) { struct FileBlock* fb = container_of(current, struct FileBlock, node); if (block_no < fb->block_no) current = current->left; else if (block_no > fb->block_no) current = current->right; else return fb; } return NULL; }

7. 性能优化与实践建议

在实际工程中实现红黑树时,有几个关键优化点值得注意:

7.1 内存布局优化

现代CPU对缓存命中率极为敏感。我们可以优化节点结构:

// 优化后的节点结构 typedef struct RBNode { int key; RBNode* left; RBNode* right; RBNode* parent; uintptr_t color; // 利用指针最低位存储颜色 } RBNode;

这种设计:

  • 将颜色信息嵌入父指针的最低有效位(LSB)
  • 减少内存占用(从至少12字节降到8字节)
  • 提高缓存局部性

OpenHarmony的kernel/linux/linux-5.10/lib/rbtree.c采用了类似的技巧。

7.2 迭代器实现

为红黑树实现迭代器可以大大简化遍历操作:

typedef struct { RBNode* current; RBNode* root; } RBIterator; RBNode* rb_iter_next(RBIterator* iter) { RBNode* current = iter->current; if (current == NULL) return NULL; // 中序遍历 if (current->right != NULL) { current = current->right; while (current->left != NULL) current = current->left; iter->current = current; return current; } RBNode* parent = current->parent; while (parent != NULL && current == parent->right) { current = parent; parent = parent->parent; } iter->current = parent; return parent; }

7.3 无父指针实现

在某些受限环境中,可以牺牲部分性能实现无父指针的红黑树:

void rb_insert_no_parent(RBNode** root, int key) { RBNode** current = root; RBNode* parent = NULL; while (*current != NULL) { parent = *current; if (key < parent->key) current = &parent->left; else current = &parent->right; } *current = create_node(key); rb_insert_fixup_no_parent(root, *current, parent); }

这种实现虽然节省了内存,但增加了算法复杂度,适合内存极度受限的嵌入式场景。

http://www.jsqmd.com/news/966364/

相关文章:

  • AI遗忘学习:实现数据可撤销的机器学习新范式
  • 名庄红酒回收靠谱解析:天津五粮液回收、天津人头马回收、天津剑南春回收、天津名庄红酒回收、天津名庄红酒回收、天津名酒回收选择指南 - 优质品牌商家
  • 2026年上海钢材批发厂家专业度排行:江苏钢材批发厂家/镀锌方管生产厂家/上海天津友发代理/上海钢材加工定制厂家/选择指南 - 优质品牌商家
  • 保定黄金回收上门变现黄金高位运行六家持证门店全城响应 - 余生黄金回收
  • ISE14.7搭配黑金S6开发板:从Verilog代码到LED闪烁的保姆级实战(含UCF约束文件避坑)
  • 【CSDN AI数字营销实战指南】:支持行业关键词自定义的5大底层能力验证与3类企业避坑清单
  • SAP ABAP锁参数SCOPE的坑,我踩了!记一次生产环境重复投料的排查与修复
  • AI中间层归零:Claude-3.5如何用Prompt折叠系统栈
  • RAG系统性能优化与故障诊断的视觉分析方法
  • 别再折腾虚拟机了!用WSL2在Windows上搞定MicroPython固件编译(STM32F407实战)
  • 开发提效新思路:基于快马平台与mcp协议构建标准化ai工具链
  • 从热释电传感器到开关电源:搞懂NMOS管G、S、D接法,让你的电路不再‘发烧’
  • 别再让MinIO图片变下载了!手把手教你用S3 Browser配置预览(附Java代码)
  • 别再手动清理Docker垃圾了!教你用Cron定时任务自动释放磁盘空间(附完整脚本)
  • 宝鸡2026贵金属回收 黄金白银铂金彩金靠谱门店榜单 - 余生黄金回收
  • React Web项目秒变App?试试HBuilderX的“5+App”云打包方案
  • 2026成都外墙瓷砖脱落修复技术解析与合规服务商参考:成都,成都外墙防水补漏/老旧小区外墙防水/蜘蛛人外墙防水施工/选择指南 - 优质品牌商家
  • CANoe自动化配置避坑指南:用Python脚本搞定CommunicationSetup接口(附完整代码)
  • STM32L496 STOP模式低功耗工程:WKUP按键+RTC定时唤醒,HAL库Keil开箱实测
  • CentOS 7上Python 3连接达梦数据库:保姆级dmPython驱动编译安装与避坑指南
  • 2026年q2茅台五十年回收解析:茅台五十年回收回收/茅台十五年回收/陈年白酒回收/渠道与实操技术要点 - 优质品牌商家
  • 宜善园养老院:天津国寿嘉园/天津市养老院/天津西青区养老院/天津高端养老院/宜善园养老院/老人院养老院/老年养老公寓/选择指南 - 优质品牌商家
  • 2026宝鸡卖金指南 全市合规黄金铂金彩银上门商家精选 - 余生黄金回收
  • Ubuntu触摸屏下阻止Caribou软键盘误触发的GNOME扩展包
  • 告别C99编译报错!e2 studio项目C语言标准配置保姆级指南
  • 2026宝鸡实测 黄金铂金白银回收正规商家榜单 - 余生黄金回收
  • AI工程周度技术脉搏:从筛选到决策的结构化实践
  • 周志华《Machine Learning》学习笔记(1)--绪论
  • LLM多智能体框架如何提升科学文献分析效率
  • 告别FlexTimer!S32K3的eMIOS实战:手把手教你配置PWM与输入捕获(MCAL配置避坑指南)