C++封装红黑树实现mymap和myset和模拟实现详解

ops/2025/1/31 23:30:09/

文章目录

map和set的封装

介绍map和set的底层实现

map和set的底层

一份模版实例化出key的rb_tree和pair<k,v>的rb_tree
rb_tree的Key和Value不是我们之前传统意义上的key/value
在这里插入图片描述
迭代器也是一份模版实例化出两个不同的迭代器
所以说底层上红黑树是有两份的,一份是key的,一份是key/value的
在这里插入图片描述

  1. 通过泛型的思想实现出的模版,第二个参数不是写死的,第二个模版参数是Value决定的,Value可以是key或者是pair<const key, T>,这样既可以实现key的搜索场景,也可以实现key/value的搜索场景
  2. 要注意一下,源码里面模板参数是用T代表value,而内部写的value_type不是我们我们日常key/value场景中说的value,源码中的value_type反而是红黑树结点中存储的真实的数据的类型
  3. rb_tree的第二个模版参数已经控制了红黑树节点的存储的数据类型,为什么还要写第二个模版参数?
    set的两个模版参数都是一样的,都是key,insert的都是key,find和erase的也是key。但是对于map来说,insert的是pair对象,find/erase的是key。所以set为了兼容map就传了两个模版参数

map和set的模拟实现

pair的默认比较的是first,first小就小,first不小,比较second,second小就小。

insert

insert关键是要解决插入的值是Key还pair的问题
上层用仿函数实现一个类来解决下层比较的是key还是pair的问题,下层不知道T是什么,但是上层知道,如果比较的是key上层就传key,是pair就传pair

namespace wbc
{template<class K>class set{// 为了解决不知道下层T是key还是pair的逻辑struct SetKeyOfT{// 仿函数可以解决const K& operator()(const K& key){return key;}};public:bool insert(const K& key){return _t.Insert(key);}private:RBTree<K,K,SetKeyOfT> _t;};
}namespace wbc
{template<class K, class V>class map{// 为了解决不知道下层T是key还是pair的逻辑struct MapKeyOfT{// 仿函数可以解决const pair<K, V>& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<K, V>,MapKeyOfT> _t;};
}

iterator_67">iterator实现的思路

  1. map和set的迭代器走的是中序遍历,begin()返回的是中序的第一个节点
  2. operator++核心是不看全局,只看局部,只关心当前局部的下一个节点是什么
  3. 中序访问的顺序:左子树,根,右子树。++,it当前节点已经访问完了,如果右不为空,访问右子树的最左节点。如果右为空,说明当前节点已经访问完了,子树也访问完了,就要访问该节点的祖先,并且要往上找。要找的是孩子是祖先的左边的那个祖先。
    如果孩子在父亲的右,说明父亲访问完了,父亲在爷爷的左,下一个就访问爷爷。
  4. set的iterator也不支持修改,我们把set的第二个模板参数改成const K即可, RBTree<K,const K, SetKeyOfT> _t;
  5. map的iterator不支持修改key但是可以修改value,我们把map的第二个模板参数pair的第一个参
    数改成const K即可, RBTree<K, pair<const K, V>,MapKeyOfT> _t;
    在这里插入图片描述

operator++

  1. 右不为空,中序的下一个节点就是右子树中的最左节点
  2. 右为空,分为两种情况:
    情况1:一直往上找直到找到父亲的左是当前节点,那么下一个节点就是这个父亲节点
    情况2:就是一直找,找不到父亲的左是当前节点,也就是当前节点一直是父亲的右,直到找到父亲是空都没有找到,那么这棵树就走完了
    在这里插入图片描述

在这里插入图片描述

Self operator++()
{if (_node->_right){// 如果右不为空,中序下一个要访问的节点就是最左(最小)节点Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{// 如果右为空,祖先里面的孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}

operator- -

访问节点

  1. 右子树,根,左子树。如果左树不为空,访问左树的最右节点(最大节点)。如果左树为空,说明这棵子树访问完了,如果该节点还是父亲的左,说明也访问完了,要找到节点是父亲的右,下一个访问的节点就是父亲。如果都不存在,下一个就要访问空节点了,说明这棵树访问完了。
  2. operator- - 和operator++正好反过来了
    在这里插入图片描述
// RBTree.h
Self operator--()
{if (_node == nullptr) // --end(){// --end(),找到整棵树的最右节点,中序的最后一个节点// 找最右节点Node* mostright = _root;// 空树(无节点)和找最右节点while (mostright && mostright->_right){mostright = mostright->_right;}_node = mostright;}else if (_node->_left){// 如果左不为空,下一访问的是左树中的最右节点Node* most = _node->_left;while (most->_right){most = most->_right;}_node = most;}else{// 如果左为空,下一个访问的是孩子是父亲右的,那个祖先Node* cur = _node;Node* parent = _node->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}// Myset.h
void Print(const set<int>& s)
{set<int>::const_iterator it = s.end();while (it != s.begin()){--it;cout << *it << " ";}cout << endl;
}// test.cpp
int main()
{wbc::set<int> s;s.insert(5);s.insert(1);s.insert(2);s.insert(3);s.insert(4);s.insert(6);s.Print(s);
}

在这里插入图片描述
库里面实现的是有哨兵位的头节点,我们实现是end()是nullptr,但是也可以实现–end()的功能,无非就是要_root,去找整棵树的最右节点(最后一个节点),end()是最后一个节点的下一个节点
在这里插入图片描述

在这里插入图片描述

operator[ ]

operator[ ]直接调用insert即可
map实现operator[ ],修改insert的返回值为pair< Iterator,bool>

V& operator[](const K& key)
{pair<iterator, bool> ret = insert({ key,V() });return ret.first->second;// 修改value
}pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;// return pair<Iterator,bool>(Iterator(_root,_root),true);return { Iterator(_root,_root),true }; }KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else{// 不冗余,插入失败return { Iterator(cur,_root),false }; ;}}cur = new Node(data);Node* newnode = cur;// 如果连续变色,cur不一定是新节点// 如果是非空树,插入红色节点cur->_col = RED;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else if (kot(parent->_data) > kot(data)){parent->_left = cur;}// 链接父亲节点cur->_parent = parent;// parent是红色,出现了连续的红色节点,需要向上调整// 调整之后cur是根,cur的parent是nullptrwhile (parent && parent->_col == RED){Node* grandfather = parent->_parent;if (grandfather->_left == parent){//   g// p     uNode* uncle = grandfather->_right;if (uncle && uncle->_col == RED){// 变色是为了处理连续的红节点,保证黑节点的数量不变,// 向上继续调整是因为grandfather的节点可能是黑节点就结束,// 可能是红节点就继续向上处理parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上处理cur = grandfather;parent = cur->_parent;}else{// uncle不存在或uncle存在且为黑//   g// p     u// c// 右单旋if (cur == parent->_left){RotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 双旋//   g// p   u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{//   g// u     pNode* uncle = grandfather->_left;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 继续向上更新cur = grandfather;parent = cur->_parent;}else{// uncle不存在或者存在且是黑//    g// u     p//          c// 左单旋if (parent->_right == cur){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{//     g//      u     p//          c// 双旋RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 无论如何结束之后根都是黑色的_root->_col = BLACK;return {Iterator(newnode,_root),true};
}

http://www.ppmy.cn/ops/154604.html

相关文章

JavaScript系列(43)--依赖注入系统实现详解

JavaScript依赖注入系统实现详解 &#x1f489; 今天&#xff0c;让我们深入探讨JavaScript的依赖注入系统实现。依赖注入是一种设计模式&#xff0c;它通过将依赖关系的创建和管理从代码中分离出来&#xff0c;提高了代码的可维护性和可测试性。 依赖注入基础概念 &#x1f…

【Elasticsearch 】自定义分词器

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

力扣-链表-206 反转链表

思路 把每一个元素都头插法插入到一个虚拟节点即可 代码 class Solution { public:ListNode* reverseList(ListNode* head) {ListNode* dummy_head new ListNode();dummy_head->next nullptr;while(head ! nullptr){ListNode* cur head;head head->next;cur->n…

守护资产,防范风险 | 2025年Web3.0资产安全指南

2024年&#xff0c;Web3.0世界迎来了一波新的机遇&#xff0c;但诸多潜藏的危机也随之浮现。CertiK最新发布的《Hack3d&#xff1a;2024年度安全报告》数据显示&#xff0c;全年共发生了760起链上安全事件&#xff0c;造成总计23.6亿美元的损失。其中&#xff0c;钓鱼攻击引发的…

vim多文件操作如何同屏开多个文件

[rootxxx ~]# vimdiff aa.txt bb.txt cc.txt #带颜色比较的纵向排列打开的同屏多文件操作 示例&#xff1a; [rootxxx ~]# vimdiff -o aa.txt bb.txt cc.txt #带颜色比较的横向排列打开的同屏多文件操作 示例&#xff1a; [rootxxx ~]# vim -O aa.txt bb.txt c…

【Node.js】Koa2 整合接口文档

部分学习来源&#xff1a;https://blog.csdn.net/qq_38734862/article/details/107715579 依赖 // koa2-swagger-ui UI视图组件 swagger-jsdoc 识别写的 /***/ 转 json npm install koa2-swagger-ui swagger-jsdoc --save配置 config\swaggerConfig.js const Router requir…

Vim安装与配置教程(解决软件包Vim没有安装可候选)

Vim安装与配置教程&#xff08;解决软件包Vim没有安装可候选&#xff09;_软件包 vim 没有可安装候选-CSDN博客文章浏览阅读4.4k次&#xff0c;点赞70次&#xff0c;收藏47次。在Linux系统中&#xff0c;当我们使用apt-get install vim命令安装Vim 编辑器时&#xff0c;如果系统…

FFmpeg 自定义IO和格式转换

文章目录 自定义IO音频重采样图像格式转换 自定义IO 通常解封装时&#xff0c;当调用avformat_open_input和avformat_find_stream_info时&#xff0c;FFmpeg内部会自动读取文件内容来查找信息。除此之外&#xff0c;我们也可以自定义IO&#xff0c;我们只要提供一个自定义的读…