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

【C++】用红黑树模拟实现set、map

目录

  • 前言及准备:
  • 一、红黑树接口
    • 1.1 begin
    • 1.2 end
    • 1.3 查找
    • 1.4 插入
    • 1.5 左单旋和右单旋
  • 二、树形迭代器(正向)
    • 2.1 前置++
  • 三、模拟实现set
  • 四、模拟实现map

前言及准备:

set、map的底层结构是红黑树,它们的函数通过调用红黑树的接口来实现,红黑树一些接口需要通过树形迭代器来实现。set是k模型,map是kv模型,红黑树要不要写两份呢?答案是不需要,只用一份即可,通过仿函数来控制。

定义树的节点:

template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Colour _col;RBTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){}
};

红黑树有3个指针域,数据域用T来表示,如果是set,那么传过来的是k模型;如果是map,是kv模型。新增的节点的颜色默认是红色(根节点除外)。

一、红黑树接口

1.1 begin

返回的是红黑树的第一个节点,注意,这里的第一个的顺序是按中序遍历来的,所以,第一个节点的位置是树的最左节点。

//返回的迭代器指向的数据可修改
iterator begin()
{Node* subLeft = _root;while (subLeft->_left){subLeft = subLeft->_left;}return iterator(subLeft);
}
//返回的迭代器指向的数据不可修改
const_iterator begin() const
{Node* subLeft = _root;while (subLeft->_left){subLeft = subLeft->_left;}return const_iterator(subLeft);
}

1.2 end

返回的是最后一个节点(最右侧节点)的下一个位置。由于这里实现的红黑树没有头节点,所以只能给nullptr来勉强实现这个迭代器。但是这样其实是不行的,因为对end()位置的迭代器进行 - - 操作,必须要能找最后一个元素,给nullptr就找不到了。如果有头节点,那么end()的位置应该是在头节点的位置。
在这里插入图片描述

iterator end()
{return iterator(nullptr);
}
const_iterator end() const
{return const_iterator(nullptr);
}

1.3 查找

查找的过程与之前写的二叉搜索树没有多大区别,要注意的是返回类型,找到了,返回的是该节点的迭代器,找不到就返回end()。

iterator Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return iterator(cur);}}return end();
}

咋知道是set还是map的数据进行比较,看传过来的类模板参数中的仿函数是set的还是map的。当然,这里只需写好就行,不用关心传过来的是什么,set和map的仿函数内部已经确定好了。

说明一下:

template<class K, class T, class KeyOfT>

这是该红黑树的类模板,K是Find()函数中要对比的数据类型,T是节点的数据域,可能是k模型,也有可能是kv模型。怎么确定呢?通过第三个类模板参数——仿函数来确定。仿函数传的是set的,就是k模型;仿函数传的是map的,就是kv模型。仿函数内部具体实现下面再说。

1.4 插入

为了接近STL库中的insert函数,返回类型是pair,即插入成功,返回该节点的迭代器和true;插入失败,说明该节点已经存在,返回该节点的迭代器和false。

pair<iterator, bool> Insert(const T& data)
{//为空if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点都是黑色的,特殊处理return make_pair(iterator(_root), true);}//非空KeyOfT kot;Node* cur = _root;Node* parent = nullptr;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 make_pair(iterator(cur), false);}}//插入新节点cur = new Node(data);//红色的if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;Node* newnode = cur;//调整颜色while (parent && parent->_col == RED){Node* grandfather = parent->_parent;//爷爷节点//父节点在爷爷节点的左边,那么叔叔节点在右边if (parent == grandfather->_left){Node* uncle = grandfather->_right;//情况一:叔叔存在且为红if (uncle && uncle->_col == RED){grandfather->_col = RED;uncle->_col = parent->_col = BLACK;cur = grandfather;//爷爷不是根,向上更新parent = cur->_parent;}//情况二:叔叔不存在/存在且为黑else{//单旋if (cur == parent->_left){RotateR(grandfather);//右单旋parent->_col = BLACK;//变色grandfather->_col = RED;}//左右双旋 // cur == parent->_rightelse{RotateL(parent);//先左单旋RotateR(grandfather);//再右单旋grandfather->_col = RED;//变色cur->_col = BLACK;}}}else//父节点在右边,叔叔在左边{Node* uncle = grandfather->_left;//情况一:叔叔存在且为红if (uncle && uncle->_col == RED){grandfather->_col = RED;uncle->_col = parent->_col = BLACK;cur = grandfather;//爷爷不是根,向上更新parent = cur->_parent;}//情况二:叔叔不存在/存在且为黑else{//单旋if (cur == parent->_right){RotateL(grandfather);//左单旋parent->_col = BLACK;//变色grandfather->_col = RED;}//右左双旋 // cur == parent->_leftelse{RotateR(parent);//先右单旋RotateL(grandfather);//再左单旋grandfather->_col = RED;//变色cur->_col = BLACK;}break;//经过情况二后跳出}}}_root->_col = BLACK;//统一处理,根必须是黑的return make_pair(iterator(newnode), true);
}

1.5 左单旋和右单旋

这两个就是之前的,这里不作重复叙述了

//左单旋
void RotateL(Node* parent)
{Node* subR = parent->_right;Node* subRL = subR->_left;parent->_right = subRL;//不为空if (subRL){subRL->_parent = parent;}subR->_left = parent;Node* ppnode = parent->_parent;parent->_parent = subR;//处理parent如果为根if (parent == _root){_root = subR;subR->_parent = nullptr;}//不为根,处理与ppnode的连接else{if (ppnode->_left == parent){ppnode->_left = subR;}else{ppnode->_right = subR;}subR->_parent = ppnode;}
}//右单旋
void RotateR(Node* parent)
{Node* subL = parent->_left;Node* subLR = subL->_right;parent->_left = subLR;//不为空if (subLR){subLR->_parent = parent;}subL->_right = parent;Node* ppnode = parent->_parent;parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}else{if (ppnode->_left == parent){ppnode->_left = subL;}else{ppnode->_right = subL;}subL->_parent = ppnode;}
}

二、树形迭代器(正向)

2.1 前置++

首先要清楚的是++到下一个节点位置是按中序遍历走的,主要有两种情况:

  1. 该节点有右子树
  2. 该节点没有右子树

1️⃣有右子树
在这里插入图片描述
总结:有右子树++后的下一个节点是右子树的最左节点

2️⃣没有右子树
在这里插入图片描述
总结:没有右子树++后下一个节点是祖先节点中左孩子是当前节点(原来节点的位置)或者左孩子是当前节点的父亲的那个祖先

有点弯,再来图捋一捋:
在这里插入图片描述

前置- -的逻辑与前置++刚好相反

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}//前置++Self& operator++(){//右子树存在if (_node->_right){//下一个节点在右子树的最左边Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}//右子树不存在else{Node* cur = _node;Node* parent = cur->_parent;//cur是parent的左子树,parent就是下一个while (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}//前置--Self& operator--()//与前置++的逻辑相反{//左子树存在if (_node->_left){//下一个节点是左子树的最右一个Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}//左子树不存在else{Node* cur = _node;Node* parent = cur->_parent;//cur是parent的右子树时parent就是下一个while (parent && parent->_left == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator!=(const Self& s){  return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};

三、模拟实现set

set是k模型,仿函数返回的只有key值。其他接口调用红黑树的

template<class K>
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::const_iterator const_iterator;//迭代器iterator begin(){return _t.begin();}const_iterator begin() const{return _t.begin();}iterator end(){return _t.end();}const_iterator end() const{return _t.end();}//插入pair<iterator, bool> Insert(const K& key){return _t.Insert(key);}//查找iterator Find(const K& key){_t.Find(key);}
private:RBTree<K, const K, SetKeyOfT> _t;
};

四、模拟实现map

map是kv模型,仿函数返回的取kv中的key值。其他接口调用红黑树的,除此之外,多了一个operator[]

template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;//迭代器iterator begin(){return _t.begin();}const_iterator begin() const{return _t.begin();}iterator end(){return _t.end();}const_iterator end() const{return _t.end();}//插入pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}//查找iterator Find(const K& key){_t.Find(key);}//operator[]V& operator[](const K& key){pair<iterator, bool> ret = Insert(make_pair(key, V()));return ret.first->second;}
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

相关文章:

【C++】用红黑树模拟实现set、map

目录 前言及准备&#xff1a;一、红黑树接口1.1 begin1.2 end1.3 查找1.4 插入1.5 左单旋和右单旋 二、树形迭代器&#xff08;正向&#xff09;2.1 前置 三、模拟实现set四、模拟实现map 前言及准备&#xff1a; set、map的底层结构是红黑树&#xff0c;它们的函数通过调用红…...

实现:mysql-5.7.42 到 mysql-8.2.0 的升级(二进制方式)

实现&#xff1a;mysql-5.7.42 到 mysql-8.2.0 的升级&#xff08;二进制方式&#xff09; 1、操作环境1、查看当前数据库版本2、操作系统版本3、查看 Linux 系统上的 glibc&#xff08;GNU C 库&#xff09;版本&#xff08;**这里很重要&#xff0c;要下载对应的内核mysql版本…...

深入探讨医保购药APP的技术架构与设计思路

随着移动互联网的发展&#xff0c;医疗保健行业也迎来了数字化转型的浪潮。医保购药APP作为医保体系数字化的一部分&#xff0c;其技术架构和设计思路至关重要。接下来&#xff0c;小编将为您讲解医保购药APP的技术架构与设计思路&#xff0c;为相关从业者提供参考和启发。 一、…...

react中点击按钮不能获取最新的state时候

在这个问题中&#xff0c;用户希望在点击确认按钮时触发handleChange函数&#xff0c;并且能够正确获取到最新的bzText值。最初的代码中&#xff0c;在handleOpen函数中弹出一个确认框&#xff0c;并在确认框的onOk回调函数中调用handleChange函数。然而&#xff0c;由于组件传…...

2、鸿蒙学习-申请调试证书和调试Profile文件

申请发布证书 发布证书由AGC颁发的、为HarmonyOS应用配置签名信息的数字证书&#xff0c;可保障软件代码完整性和发布者身份真实性。证书格式为.cer&#xff0c;包含公钥、证书指纹等信息。 说明 请确保您的开发者帐号已实名认证。每个帐号最多申请1个发布证书。 1、登录AppGa…...

蓝桥杯算法基础(13):十大排序算法(希尔排序) (快速排序)c语言版

希尔排序 优化版的插入排序&#xff0c;优化的地方就是步长&#xff08;增量&#xff09;增大了&#xff0c;原来的插入排序的步长&#xff08;增量&#xff09;是1&#xff0c;而希尔排序的步长&#xff08;增量&#xff09;可以很大&#xff0c;然后逐渐减小直到1形成插入排…...

web学习笔记(三十二)

目录 1.函数的call、apply、bind方法 1.1call、apply、bind的相同点 1.2call、apply、bind的不同点 1.3call、apply、bind的使用场景 2. 对象的深拷贝 2.1对象的浅拷贝 2.1对象的深拷贝 1.函数的call、apply、bind方法 1.1call、apply、bind的相同点 在没有传参数时&…...

Android 地图SDK 绘制点 删除 指定

问题 Android 地图SDK 删除指定绘制点 详细问题 笔者进行Android 项目开发&#xff0c;对于已标记的绘制点&#xff0c;提供撤回按钮&#xff0c;即删除绘制点&#xff0c;如何实现。 解决方案 新增绘制点 private List<Marker> markerList new ArrayList<>…...

Nodejs 第五十八章(大文件上传)

在现代网站中&#xff0c;越来越多的个性化图片&#xff0c;视频&#xff0c;去展示&#xff0c;因此我们的网站一般都会支持文件上传。 文件上传的方案 大文件上传&#xff1a;将大文件切分成较小的片段&#xff08;通常称为分片或块&#xff09;&#xff0c;然后逐个上传这…...

Linux编译器--gcc/g++的使用

1. gcc与g gcc与g分别是c语言与c代码的编译器&#xff0c;但同时g也兼容c语言。 我们知道在Linux中&#xff0c;系统并不以文件后缀来区分文件类别。但对于gcc与g等编译器而言却是需要的。Linux中c代码文件的后缀是.c&#xff0c;c代码文件的后缀是.cpp(.cc)(.cxx)。 在Linu…...

苍穹外卖-day13:vue基础回顾+进阶

vue基础回顾进阶 课程内容 VUE 基础回顾路由 Vue-Router状态管理 vuexTypeScript 1. VUE 基础回顾 1.1 基于脚手架创建前端工程 1.1.1 环境要求 要想基于脚手架创建前端工程&#xff0c;需要具备如下环境要求&#xff1a; ​ node.js 前端项目的运行环境 学习web阶段已安…...

蓝桥杯/慈善晚会/c\c++

问题描述 热心公益的G哥哥又来举办慈善晚会了&#xff0c;这次他邀请到了巴菲特、马云等巨富&#xff0c;还邀请到了大V、小C等算法界泰斗。晚会一共邀请了n位尊贵的客人&#xff0c;每位客人都位于不同的城市&#xff0c;也就是说每座城市都有且仅有一位客人。这些城市的编号为…...

2024.3.19

思维导图...

【Python】新手入门学习:详细介绍单一职责原则(SRP)及其作用、代码示例

【Python】新手入门学习&#xff1a;详细介绍单一职责原则&#xff08;SRP&#xff09;及其作用、代码示例 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyT…...

【DataWhale学习笔记】使用AgentScope调用qwen大模型

AgentScope AgentScope介绍 AgentScope是一款全新的Multi-Agent框架&#xff0c;专为应用开发者打造&#xff0c;旨在提供高易用、高可靠的编程体验&#xff01; 高易用&#xff1a;AgentScope支持纯Python编程&#xff0c;提供多种语法工具实现灵活的应用流程编排&#xff…...

【C++】手撕AVL树

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;能直接手撕AVL树。 > 毒鸡汤&#xff1a;放弃自…...

探索 TorchRe-ID--基于 Python 的人员再识别库

导言 人员再识别&#xff08;re-ID&#xff09;是计算机视觉中的一项重要任务&#xff0c;在监控系统、零售分析和人机交互中有着广泛的应用。TorchRe-ID 是一个功能强大、用户友好的 Python 库&#xff0c;它为人员再识别任务提供了一套全面的工具和模型。在本文中&#xff0…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Flex)

以弹性方式布局子组件的容器组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。Flex组件在渲染时存在二次布局过程&#xff0c;因此在对性能有严格要求的场景下建议使用Column、Row代替。Flex组…...

tmux最基础的一点应用-不用一直挂着ssh,可以干点别的事情

文章目录 使用原因基础命令创建一个窗口退出当前窗口重新进入万一忘记了环境名字想要删除环境 使用原因 跑程序要很久&#xff0c;需要干别的事情&#xff0c;电脑不能一直开&#xff0c;可以使用tmux来管理。 基础命令 创建一个窗口 tmux new -s <你自己起的环境名字&g…...

Java推荐算法——特征加权推荐算法(以申请学校为例)

加权推荐算法 文章目录 加权推荐算法1.推荐算法的简单介绍2.加权推荐算法详细介绍3.代码实现4.总结 1.推荐算法的简单介绍 众所周知&#xff0c;推荐算法有很多种&#xff0c;例如&#xff1a; 1.加权推荐&#xff1a;分为简单的特征加权&#xff0c;以及复杂的混合加权。主要…...

嵌入式Linux无线服务器搭建指南

1. 项目概述在嵌入式Linux开发中&#xff0c;传统的有线网络连接方式往往限制了设备的灵活性和部署便捷性。作为一名嵌入式开发者&#xff0c;我最近成功在S3C2410开发板上实现了基于WiFi模块的无线服务器搭建&#xff0c;彻底摆脱了网线的束缚。这套方案不仅适用于智能家居控制…...

Guardrails多区域部署终极指南:构建全球LLM安全服务架构

Guardrails多区域部署终极指南&#xff1a;构建全球LLM安全服务架构 【免费下载链接】guardrails Adding guardrails to large language models. 项目地址: https://gitcode.com/gh_mirrors/gu/guardrails 在当今AI应用全球化的浪潮中&#xff0c;如何为大型语言模型&am…...

Qt项目实战:借助Valgrind精准定位与修复内存泄漏

1. 为什么Qt开发者需要Valgrind 刚接触Qt开发时&#xff0c;我总以为用了智能指针和Qt自带的内存管理机制就能高枕无忧。直到某个深夜&#xff0c;项目上线前突然崩溃&#xff0c;日志里只有一句"segmentation fault"&#xff0c;我才意识到内存问题有多可怕。那次经…...

VCS编译优化全攻略:从-pcmakeprof时间分析到partition配置技巧

VCS编译优化全攻略&#xff1a;从-pcmakeprof时间分析到partition配置技巧 在芯片验证领域&#xff0c;编译时间直接影响着工程师的迭代效率。当RTL代码规模突破千万行时&#xff0c;一次完整编译可能消耗数小时&#xff0c;而传统增量编译往往因为细粒度不足导致不必要的重复工…...

大中华区21个主要城市甲级写字楼市场报告发布;DHL集团与中国外运将进一步深化全球业务协同 | 美通社一周热点简体中文稿

美通社每周发布数百上千篇中文企业资讯&#xff0c;想看完所有稿件可能很困难。以下是我们对过去一周不容错过的主要企业稿件进行的归纳&#xff0c;帮助记者和读者们及时了解一周发布的热门企业资讯。中国在欧洲的专利申请量超越日本首次跃升至前三欧洲专利局(European Patent…...

SAMD平台轻量级事件驱动按钮库slight_ButtonInput

1. 项目概述 slight_ButtonInput 是一个面向嵌入式平台&#xff08; 仅限 SAMD 系列微控制器 &#xff0c;如 ATSAMD21G18、ATSAMD51J19 等&#xff09;的轻量级 Arduino 库&#xff0c;专为 事件驱动型按钮输入处理 而设计。其核心目标并非简单轮询引脚电平&#xff0c;…...

XUnity.AutoTranslator:为Unity游戏开启多语言世界的智能钥匙

XUnity.AutoTranslator&#xff1a;为Unity游戏开启多语言世界的智能钥匙 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 当游戏语言成为障碍&#xff1a;一个翻译插件的诞生背景 你是否曾经遇到过这样的…...

一维光子晶体Zak相位计算详解:包含COMSOL与MATLAB应用方法和步骤

一维光子晶体的zak相位计算 &#xff08;内含comsol文件和matlab程序&#xff09; 注意&#xff1a;这个是重复别人文章的结果&#xff0c;方法是论文中所提到的今天咱们来唠唠一维光子晶体Zak相位的计算实操。这玩意儿听起来挺玄乎&#xff0c;其实就是个描述拓扑特性的数学量…...

从插件到工作流:在Coze平台实战快商通AI语音防伪接口(避坑指南+节点连接技巧)

从插件到工作流&#xff1a;在Coze平台实战快商通AI语音防伪接口&#xff08;避坑指南节点连接技巧&#xff09; 当你已经按照教程在Coze开放平台创建了快商通AI的语音防伪检测插件&#xff0c;接下来面临的挑战是如何将这些独立的组件串联成一个自动化工作流。本文将带你深入实…...

从面包板到开发板:51单片机(STC89C52)点灯避坑指南与硬件连接实战

从面包板到开发板&#xff1a;51单片机(STC89C52)点灯避坑指南与硬件连接实战 当你第一次拿到STC89C52单片机芯片和一堆零散的元器件时&#xff0c;那种既兴奋又迷茫的感觉我至今记忆犹新。与直接使用现成的开发板不同&#xff0c;从零开始搭建最小系统并点亮第一个LED&#xf…...