改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现

改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现

容器map/set的底层是红黑树,这一篇详解红黑树如何封装实现map/set。

1.map/set设计的巧妙之处

map是key/value类型,set是key类型,两个冲突的参数类型,是如何由红黑树封装而成?



暴力思路:两个红黑树,一个kv,一个k。可是这样代码复用率极低,维护成本高



源码思路:利用 键提取器——仿函数 提取kv、k的key,用一颗红黑树实现map,set



C语言一般用函数指针,但是它十分麻烦,C++有了仿函数就很方便

接下来在红黑树基础上封装map和set

2.map和set的实现

2.1map和set的基本框架 + 原红黑树结构变化

map是key、value结构,set是key结构:

 既然我们要用一个红黑树封装实现map和set,那传的参数就得通用:







原本是K,V结构,现在,要改成通用的,就用T吧







T根据需要,可选择传pair<K,V>,也可以是K:(原来是K,V,参数是kv)

然后根据上面,各自写一个仿函数,用于控制红黑树底层获取Key的行为

上层传仿函数,下层需要新增一个模板参数来接收:

2.2Insert

根据上面,原本是KV结构的红黑树,那Insert自然传的是pair

现在传data就行:data类型可能是pair<K,V>,也可能是Key,根据传的不同,创建仿函数对象,去获取它的Key:

Insert后面的部分,反正涉及到获取Key比较大小,就用仿函数

这样就形成一个回调。

2.3Find

Find是用Key比较,那_data类型可能是Key,也可能是pair<K,V>,那同样需要仿函数获取Key

map复用红黑树的Insert,Find



set复用红黑树的Insert,Find

2.4map和set的迭代器Iterator

map和set的迭代器都是由红黑树的迭代器封装而成,源码中有体现。源码写的迭代器十分复杂,包含了继承关系,我们就不写那么复杂。

了解到前面容器的迭代器实现,不难猜红黑树的迭代器也是用一个类封装,再通过重载运算符,使迭代器能像指针一样访问。

这里难点是++和--的实现。红黑树是一种二叉搜索树,走中序遍历输出结果有序。之前使用map和set也提到是这样,得出结论:红黑树迭代器也是走中序遍历:左根右

那begin返回中序遍历第一个结点(最左结点),end返回空结点(最右结点的一个是空,下面)

迭代器实现++ 和 -- ,只需要注意局部。++返回中序下一个节点,--返回上一个,局部解决了,全局就解决了。但是++和--的情况很多,这也就是它的难点,不是逻辑难点,是代码难点。举例子:

根据中序:左根右,可以推断,begin的下一个就是15节点

因为走到10节点,说明当前子树 左,根都遍历完了,就剩右子树,中序访问右子树,就必须先访问其最左节点,15没有最左节点(本身就是右子树的最左节点),那++it的结果就是15节点

这里呢? it遍历到18节点,说明 左与根 都遍历完了,++it就是遍历右子树的最左节点(中序第一个),也就是25节点

那如果右子树为空呢?中序遍历升序,可以知道15的下一个是18,怎么得来?

15的右子树为空,15也已经遍历过,说明当前15的子树遍历完,那15要往上找祖先。15作为10的右子树,说明10也已经遍历完(右子树最后遍历),那10也要往上找祖先。

若10还是祖先的右子树,就还得继续找,找到根的父亲还没找到,或者找到,右父亲,停止。

若10是祖先的左子树,说明才刚遍历完祖先的左子树,那下一个就是该祖先,根

这里同理,++若右子树为空,就得向上找右父亲,25的右父亲直接就是30,得出答案。

2.4.1实现迭代器(重点是++与--)

红黑树内部使用迭代器: 

上层(map和set)复用红黑树的迭代器

模板还未实例化,取其中成员类型时,需要typename:告诉是个类型,后面实例化再去找。

迭代器的 --  和 ++ 完全对称,找左父亲,end前一个为最右结点。

2.5 Key不能被修改的问题

对于set,它的key是不能被修改的,但是这里的迭代器还是能修改它。

可以修改后,这棵树就失去意义了, 所以我们要解决这个Key被修改的问题。

为了让它不被修改,最简单的就是这样:

上层传递的const Key给红黑树,因为被const修饰,所以K将不会被修改,从源头解决问题。

typedef这里的声明也需要加上const,不然会出现很多模板特有的意义不明确的报错。

对于map,pair里的Key也不容被修改,那就同理:

2.6修改Find返回值为Iterator,实现统计次数功能

2.7修改Insert返回值为pair<Iterator,bool>,实现operator[ ]

map和set那节,讲过这个[ ]的使用和底层

newnode保存新增结点cur(因为插入过程设计旋转加变色,向上更新,最后的cur不一定是新增)

解决完Insert,接下来就实现operator[ ]:

首先插入Key,如果插入失败,说明存在,flag为false,it是已存在节点,最后返回value

                        如果插入成功,说明不存在,flag为true,it是新增结点,最后返回value

外层对value([ ]的返回值)++,即可统计次数,不用那么麻烦。

j

2.8红黑树的析构函数

map和set的析构函数会调用红黑树的析构,所以不用写他们的。注意后序遍历析构

Read more

前端高频面试题:TypeScript 篇(2026 最新版)

前端高频面试题:TypeScript 篇(2026 最新版) TypeScript(TS)已成为现代前端开发的标配,尤其在 React、Vue、Angular 等框架中,几乎是大厂必考点。2026 年面试趋势:更注重类型安全、高级类型工具、实际项目应用和tsconfig 配置。以下精选 20+ 高频题(基于最新大厂真题汇总),分为基础、中级、高级,并附详细解答和代码示例。建议结合项目实战记忆! 基础篇(必背,考察理解 TS 核心价值) 1. 什么是 TypeScript?它与 JavaScript 的区别是什么? TypeScript 是 JavaScript 的超集(superset),由 Microsoft 开发,最终编译成纯 JS

By Ne0inhk

动态规划DP入门详解(从原理到实战,新手必看)

动态规划入门详解(从原理到实战,新手必看) 动态规划问题(Dynamic Programming,简称DP)应该是很多读者头疼的,但这类问题也是最具技巧性、最有意思的。动态规划作为运筹学的一种最优化方法,在计算机算法中应用广泛,比如最长递增子序列、最小编辑距离等经典问题,都离不开动态规划的思想。 本文将彻底解决三个核心问题,帮你打通动态规划的任督二脉: * 动态规划到底是什么? * 解决动态规划问题有什么固定技巧? * 新手该如何高效学习动态规划? 刷题刷多了就会发现,算法技巧就那几个套路。后续的动态规划系列文章,都会围绕本文的解题框架展开,掌握了这套思路,再难的DP问题也能迎刃而解。所以本文作为DP入门第一章,希望能成为你解决动态规划问题的指导方针,下面直接上干货! 一、动态规划的核心本质 首先明确:动态规划问题的一般形式就是求最值。无论是最长、最小、最多、最少,只要题目要求“最值”,大概率就是动态规划问题。 既然是求最值,核心问题是什么?答案很简单:穷举。因为要找最值,必须先把所有可行的答案穷举出来,再从里面筛选出最值。 看到这里你可能会疑惑:动态规划不就是

By Ne0inhk
栈和队列--数据结构初阶(2)(C/C++)

栈和队列--数据结构初阶(2)(C/C++)

文章目录 * 前言 * 理论部分 * 栈的模拟实现 * STL中的栈容器 * 队列的模拟实现 * STL中的队列容器 * 作业部分 前言 这期的话会给大家讲解栈和队列的模拟实现和在STL中栈和队列怎么用的一些知识和习题部分(这部分侧重于理论知识,习题倒还是不难) 理论部分 栈的模拟实现 typedef int STDataType; typedef struct Stack { STDataType* a;//这里的a想表示的是数组 int top;//表示数组a当前的容量 int capacity; }ST; void STInit(ST* ps) { assert(ps); ps->a = (STDataType*)malloc(sizeof(STDataType) * 4); if (ps->a == NULL) { perror("

By Ne0inhk
【图论】迪杰特斯拉算法

【图论】迪杰特斯拉算法

文章目录 * 迪杰特斯拉算法 * 主要特点 * 基本思想 * 算法步骤 * 示例 * 实现迪杰斯特拉算法 * 基本步骤 * 算法思路 * 总结 迪杰特斯拉算法 迪杰特斯拉算法是由荷兰计算机科学家艾兹赫尔·迪杰特斯拉(Edsger W. Dijkstra)在1956年提出的,用于解决单源最短路径问题的经典算法。该算法的目标是从一个起始顶点找到到图中其他顶点的最短路径。 主要特点 * 适用于带权图,其中权重为非负数。(为什么只适用于非负数,因为迪杰斯特拉的思想是贪心测量,当有负权引入的时候,贪心策略将不再适用) * 解决从单个源点到其他所有顶点的最短路径问题。 * 时间复杂度:当使用优先队列(例如堆)时,复杂度为 O ( E log ⁡ V ) O(E \log V) O(ElogV),其中 V V V 为顶点数量, E E E

By Ne0inhk