【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;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/756111.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:Toggle)

组件提供勾选框样式、状态按钮样式及开关样式。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 仅当ToggleType为Button时可包含子组件。 接口 Toggle(options: { type: ToggleType, is…

一台电脑安装多个版本node,如何切换使用

直接上答案&#xff0c;请安装nvm——nodejs的版本管理工具 官网地址在此&#xff1a;nvm文档手册 - nvm是一个nodejs版本管理工具 - nvm中文网 (uihtm.com) 1.由于我电脑本来就有node14&#xff0c;所以需要先卸载 原来的node&#xff0c;建议在软件目录自带的node文件夹中点…

Java学习笔记NO.20

Java流程控制 1. 用户交互 Scanner Java中的Scanner类用于获取用户输入&#xff0c;可以从标准输入&#xff08;键盘&#xff09;读取各种类型的数据。 import java.util.Scanner; public class UserInputExample { public static void main(String[] args) { Scanner sc…

weaviate向量库安装

简介 Weaviate 是一个开源的向量数据库和搜索引擎&#xff0c;专门用于存储、搜索和分析向量数据。它提供了一个简单易用的 REST API&#xff0c;使得用户可以轻松地将向量数据存储到数据库中&#xff0c;并且能够以高效的方式进行相似向量的搜索。 以下是一些 Weaviate 向量…

面向切面的编程实战

面向切面的编程&#xff08;AOP&#xff09;就是让哥们写代码的时候更加关注核心业务的实现&#xff0c;将核心业务代码前后的日志等不重要的通过注解的方式&#xff0c;交给其他部分完成&#xff0c;使得代码有更高的可维护性。 具体使用 先定义一个注解 Target(ElementTyp…

[Java、Android面试]_08_强软弱虚四种引用及应用场景

本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天整理一点&#xff0c;后续会陆续分享出来&#xff0c;感兴趣的朋友可关注收…

SpringBoot3框架,基础特性

MyBatis整合 导入MyBatis整合的依赖&#xff1a; <!-- <https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter> --> <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-…

电机驱动器不确定性建模

跟踪误差信号和执行器驱动信号在控制系统的设计中也很重要&#xff01;&#xff01;&#xff01; 不确定度可分为扰动信号和动态扰动两类。前者包括输入和输出干扰&#xff08;如飞机上的阵风&#xff09;、传感器噪声和执行器噪声等。后者表示数学模型与系统在运行中的实际动…

Geostationary IR Channel Brightness Temperature - GridSat B1 -- shell下载

进入网页 https://www.ncei.noaa.gov/products/gridded-geostationary-brightness-temperature 然后进入数据目录&#xff0c;通过https的方式进行下载&#xff1a; 点击后进入如下界面&#xff1a; 点击任意年份进行下载 这里以2004年为例&#xff0c;如下所示&#xff1…

【CSS练习】万年历 html+css+js

效果图 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title><style>bod…

实现: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版本…

软件设计师

计算机系统知识 浮点数 运算&#xff1a;小阶对齐大阶 海明码 数据位n校验位k&#xff0c;必须满足 2k-1≥nk 吞吐率 p为最长子过程的倒数 数据结构 图 深度遍历时间复杂度&#xff1a; 邻接矩阵n2 邻接表ne 操作系统 进程 信号量P、V、S P申请资源&#xff0c;V释…

Linux 进程管理工具top ps

概述 top 和 ps 是 Linux 系统中两个非常重要的用于管理和监控进程的命令工具。以下是它们的主要功能和区别&#xff1a; top&#xff1a; 动态视图&#xff1a;top 提供了一个实时动态更新的视图&#xff0c;能够持续显示系统中当前正在运行的进程信息及其资源占用情况。 系统…

安卓面试题多线程11-15

11. 如何使用thread dump?你将如何分析Thread dump?在UNIX中你可以使用kill -3,然后thread dump将会打印日志,在windows中你可以使用”CTRL+Break”。非常简单和专业的线程面试问题,但是如果他问你怎样分析它,就会很棘手。🚀🚀🚀🚀🚀🚀12. Java中你怎样唤醒一…

注意力机制 self-attention 的原理探究

一、点积的认识 向量的点积可以表示相似性的原因在于它衡量了两个向量之间的方向是否相似。当两个向量的方向趋于一致时&#xff0c;它们的点积会更大&#xff1b;当两个向量的方向趋于相互垂直时&#xff0c;它们的点积会接近于0。这种性质使得点积在衡量向量之间的相似性和相…

聚合函数和GROUP BY

1、聚合函数 1.1 聚合函数概念 聚合函数是用于对一组数值进行计算并返回单一数值作为结果的函数。在数据库查询中&#xff0c;它们通常用于对数据进行汇总和统计分析。常见的聚合函数包括 SUM、AVG、COUNT、MAX 和 MIN 等。 1.2 函数介绍 1.2.1 SUM&#xff08;求和&#x…

Devops-02-Jpom 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件

拓展阅读 Devops-01-devops 是什么&#xff1f; Devops-02-Jpom 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件 代码质量管理 SonarQube-01-入门介绍 项目管理平台-01-jira 入门介绍 缺陷跟踪管理系统&#xff0c;为针对缺陷管理、任务追踪和项目管理的商业…

ins中扰动分析举例

扰动分析很重要&#xff0c;搞明白扰动分析&#xff0c;基本上就可以清楚了误差模型。 什么是扰动分析&#xff1a; 简单理解 测量值 真值 误差 这里的误差就是与测量直接对应的误差&#xff0c;例如 把误差分离出来 误差 测量值 - 真值 &#xff…

如何在海外服务器上配置静态路由?

在海外服务器上配置静态路由可以通过操作系统提供的网络配置工具来实现。下面是在常见操作系统上配置静态路由的一般步骤&#xff1a; 在 Windows 上&#xff1a; 打开命令提示符&#xff1a;在开始菜单中搜索并打开“命令提示符”(或者以管理员身份运行)。 查看当前路由表&…

基于arm的ubuntu上运行qgc

个需要在基于arm的ubuntu上运行qgc的人&#xff1a; 总结如下 &#xff1a; 必须安装 flatpak 1.12.4 。 要安装 1.12.4&#xff0c;请在终端中运行以下命令&#xff1a; sudo add-apt-repository ppa:alexlarsson/flatpak sudo apt update sudo apt install flatpak flatpak …