【C++练级之路】【Lv.17】【STL】set类和map类的模拟实现



快乐的流畅:个人主页


个人专栏:《C语言》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、红黑树(改造版)
    • 1.1 结点
    • 1.2 迭代器
      • 1.2.1 operator++
      • 1.2.2 operator- -
    • 1.3 本体
      • 1.3.1 begin和end
      • 1.3.2 Find
      • 1.3.3 Insert
  • 二、set
    • 2.1 成员变量与仿函数
    • 2.2 begin和end
    • 2.3 find
    • 2.4 insert
  • 三、map
    • 3.1 成员变量与仿函数
    • 3.2 begin和end
    • 3.3 find
    • 3.4 insert
    • 3.5 operator[ ]

引言

STL库中的set类和map类,其底层原理都是通过红黑树来实现的。尽管set和map可以各自实现一棵红黑树,但是为了提高代码的复用率,STL库中将红黑树进行了一定的改造,实现以相同的底层实现不同的容器

一、红黑树(改造版)

1.1 结点

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

细节:

  • 数据类型改为T,因为要同时适用set(存储键值)和map(存储键值对)

1.2 迭代器

改造后的红黑树,最重要的功能之一就是支持双向迭代器,以最左结点为首,以最右结点为尾。

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node): _node(node){}RBTreeIterator(const Iterator& it): _node(it._node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};

细节:

  1. 一些基本的迭代器范式操作已经给出,重点的++与- -操作后面详细实现
  2. 迭代器的拷贝构造函数有两个用途:
    • 以普通迭代器拷贝出普通迭代器(普通迭代器调用时)
    • 以普通迭代器拷贝出const迭代器(const迭代器调用时)

1.2.1 operator++

Self& operator++()
{if (_node->_right)//右不为空,找右子树的最左结点{Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}else//右为空,向上找孩子是父亲左的那个父亲{Node* parent = _node->_parent;Node* cur = _node;while (parent && parent->_right == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}Self operator++(int)
{Self tmp = *this;++*this;return tmp;
}

细节:

  1. 前置++的思路:
    • 右不为空,找右子树的最左结点
    • 右为空,向上找孩子是父亲左的那个父亲
  2. 后置++:复用前置++,返回临时对象

1.2.2 operator- -

Self& operator--()
{if (_node->_left)//左不为空,找左子树的最右结点{Node* subRight = _node->_left;while (subRight->_right){subRight = subRight->_right;}_node = subRight;}else//左为空,向上找孩子是父亲右的那个父亲{Node* parent = _node->_parent;Node* cur = _node;while (parent && parent->_left == cur){cur = parent;parent = cur->_parent;}_node = parent;}return *this;
}Self operator--(int)
{Self tmp = *this;--*this;return tmp;
}

细节:

  1. 前置- -的思路:
    • 左不为空,找左子树的最右结点
    • 左为空,向上找孩子是父亲右的那个父亲
  2. 后置- -:复用前置- -,返回临时对象

1.3 本体

template<class K, class T, class KeyOfT>
class RBTree
{
protected:typedef RBTreeNode<T> Node;
public:
protected:Node* _root = nullptr;
};

细节:

  1. 模板参数第一个为K,键值类型(比较时会用到)
  2. 模板参数第二个为T,同时适用set(存储键值)和map(存储键值对)
  3. 模板参数第三个为KeyOfT(仿函数类型),用于获取不同数据T的键值key来进行比较

1.3.1 begin和end

typedef RBTreeIterator<T, T&, T*> iterator;
typedef RBTreeIterator<T, const T&, const T*> const_iterator;iterator begin()
{Node* cur = _root;while (cur->_left){cur = cur->_left;}return iterator(cur);
}const_iterator begin() const
{Node* cur = _root;while (cur->_left){cur = cur->_left;}return const_iterator(cur);
}iterator end()
{return iterator(nullptr);
}const_iterator end() const
{return const_iterator(nullptr);
}

细节:begin返回最左节点的迭代器,end返回空迭代器

1.3.2 Find

iterator Find(const K& key)
{if (_root == nullptr){return iterator(nullptr);}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 iterator(nullptr);
}

细节:

  1. 返回迭代器
  2. 运用仿函数进行键值比较

1.3.3 Insert

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* 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 make_pair(iterator(cur), false);}}Node* newnode = new Node(data);cur = newnode;if (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (grandparent->_right == parent)//uncle在左,parent在右{Node* uncle = grandparent->_left;if (uncle && uncle->_col == RED)//uncle为红,变色+向上调整{parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else//uncle为空或为黑,变色+旋转{if (parent->_right == cur)//左单旋{RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else//右左旋{RotateR(parent);RotateL(grandparent);cur->_col = BLACK;grandparent->_col = RED;}}}else//parent在左,uncle在右{Node* uncle = grandparent->_right;if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandparent->_col = RED;cur = grandparent;parent = cur->_parent;}else{if (parent->_left == cur)//右单旋{RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else//左右旋{RotateL(parent);RotateR(grandparent);cur->_col = BLACK;grandparent->_col = RED;}}}}_root->_col = BLACK;return make_pair(iterator(newnode), true);
}

细节:

  1. 返回pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用仿函数进行键值比较

二、set

2.1 成员变量与仿函数

template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:
protected:RBTree<K, K, SetKeyOfT> _t;
};

细节:

  1. set类仿函数,直接返回参数key
  2. 成员变量的第二个模板参数为K,第三个模板参数为SetKeyOfT

2.2 begin和end

typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
typedef typename RBTree<K, 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();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. set中存储的键值key均不允许修改,所以其普通迭代器和const迭代器均为红黑树的const迭代器
  3. 由于set的普通迭代器也是红黑树的const迭代器,调用普通begin()时,便有从普通迭代器到const迭代器的转换,此时之前写的拷贝构造(以普通迭代器拷贝构造const迭代器)便派上用场了。

2.3 find

iterator find(const K& key)
{return _t.Find(key);
}

2.4 insert

pair<iterator, bool> insert(const K& key)
{return _t.Insert(key);
}

细节:

  1. 插入参数类型为K(键值)
  2. 此时也有从普通迭代器到const迭代器的转换

三、map

3.1 成员变量与仿函数

template<class K, class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
public:
protected:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

细节:

  1. map类仿函数,返回参数pair的first
  2. 成员变量的第二个模板参数为pair,第三个模板参数为MapKeyOfT

3.2 begin和end

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();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. map同样不允许修改key,故加上const修饰,但是允许修改存储的value,所以普通和const迭代器一一对应

此时,可能有人会问,那刚刚set不允许修改key,为什么不也直接用const修饰呢?请看以下这段代码:

typedef RBTreeIterator<T, const T&, const T*> const_iterator;

如果变成第二个模板参数T传入const K,那么就会形成两个连续的const,这是不被允许的。所以才想了其他方法来补救。

3.3 find

iterator find(const K& key)
{return _t.Find(key);
}

3.4 insert

pair<iterator, bool> insert(const pair<const K, V>& kv)
{return _t.Insert(kv);
}

细节:插入参数类型为pair(键值对)

3.5 operator[ ]

map最好用的重载运算符[ ],我们肯定也要实现,平常插入和修改使用[ ]更加方便。

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));return ret.first->second;
}

细节:

  1. 插入成功便是插入,插入失败便是查找+修改
  2. 返回value的引用,可以直接插入或修改

真诚点赞,手有余香

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

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

相关文章

安卓开发Gson插件的使用

Gson 是一个流行的 Java 库&#xff0c;用于序列化和反序列化 Java 对象。在 Android 开发中&#xff0c;使用 Gson 插件可以方便地将 Java 对象转换为 JSON 格式&#xff0c;并将 JSON 数据转换回 Java 对象。 以下是使用 Gson 插件的一般步骤&#xff1a; 1. 添加 Gson 库&…

Vite为什么比Webpack快得多?

Vite为什么比Webpack快得多&#xff1f; 在前端开发中&#xff0c;构建工具扮演着至关重要的角色&#xff0c;而Vite和Webpack无疑是两个备受关注的工具。然而&#xff0c;众多开发者纷纷赞誉Vite的速度之快&#xff0c;本文将深入探讨Vite相较于Webpack为何更快的原因&#xf…

c++基础学习第七天(stl)

c基础学习第七天&#xff08;stl&#xff09; 文章目录 1、STL初识1.1 、STL的诞生1.2、STL基本概念1.3、STL六大组件2.4 、STL中容器、算法、迭代器1.5、容器算法迭代器初识1.5.1、vector存放内置数据类型2.5.2、 Vector存放自定义数据类型2.5.3、 Vector容器嵌套容器 2、 STL…

企业知识库搭建不再是难题,靠这几个软件就可以了

在当今知识为王的时代&#xff0c;具备一套强大且实用的企业知识库&#xff08;Knowledge Base&#xff09;已成为提升工作效率、促进团队合作不可或缺的工具。那么&#xff0c;问题来了&#xff0c;我们该如何搭建一套属于自己的知识库呢&#xff1f;今天&#xff0c;我就给大…

WMware虚拟机配置静态IP

注意&#xff1a;如果是克隆的虚拟机&#xff0c;需要先重新生成mac地址&#xff0c;如下图所示 修改配置文件 &#xff1a;/etc/sysconfig/network-scripts/ifcfg-ens33 注意&#xff1a;1. BOOTPROTO设置为static 2.将下面的IPADDR地址替换为你实际要设置的ip地址 3.NAT模式…

ArrayList 与 List的区别 数组与集合的区别

ArrayList是一种非泛型集合类型&#xff0c;允许用户存储任何数据类型的对象&#xff0c; List 是一种泛型集合类型&#xff0c;允许用户存储声明时 List 指定的数据类型的对象。它是一个非常强类型的集合&#xff0c; var arrayList new ArrayList(); arrayList.Add(1); // …

前端学习<二>CSS基础——13-CSS3属性:Flex布局图文详解

前言 CSS3中的 flex 属性&#xff0c;在布局方面做了非常大的改进&#xff0c;使得我们对多个元素之间的布局排列变得十分灵活&#xff0c;适应性非常强。其强大的伸缩性和自适应性&#xff0c;在网页开中可以发挥极大的作用。 flex 初体验 我们先来看看下面这个最简单的布局…

软考数据库

目录 分值分布1. 事务管理1.1 事物的基本概念1.2 数据库的并发控制1.2.1 事务调度概念1.2.2 并发操作带来的问题1.2.3 并发控制技术1.2.4 隔离级别&#xff1a; 1.3 数据库的备份和恢复1.3.1 故障种类1.3.2 备份方法1.3.3 日志文件1.3.4 恢复 SQL语言发权限收权限视图触发器创建…

【华为OD机试C++】004:字符串分隔

《最新华为OD机试题目带答案解析》&#xff1a;最新华为OD机试题目带答案解析&#xff0c;语言包括C、C、Python、Java、JavaScript等。订阅专栏&#xff0c;获取专栏内所有文章阅读权限&#xff0c;持续同步更新&#xff01; 文章目录 描述输入描述输出描述示例代码 描述 •输…

Ubuntu搭建环境Cmake-Libtorch-Torchvision-PCL-VTK-OpenCV

Ubuntu搭建环境Cmake-Libtorch-Torchvision-PCL-VTK-OpenCV 安装Cmake安装libtorch安装torchvision安装PCL安装PCL的依赖项PCL多版本共存问题编译PCL库程序验证创建CMakeLists.txt 安装VTK安装OpenCV编译OpenCV库编译版本环境配置&#xff1a;程序验证创建opencv_test文件夹 和…

BaseDao封装增删改查(超详解)

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍对数据库中表中的数据进行增改删查询&#xff0c;封装一个工具类&#xff08;BaseDao&#xff09;的详细使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &…

动态规划——回文串问题

目录 练习1&#xff1a;回文子串 练习2&#xff1a;最长回文子串 练习3&#xff1a;回文串分割IV 练习4&#xff1a;分割回文串 练习5&#xff1a;最长回文子序列 练习6&#xff1a;让字符串成为回文串的最小插入次数 本篇文章主要学习使用动态规划来解决回文串相关问题&…

ES6 学习(一)-- 基础知识

文章目录 1. 初识 ES62. let 声明变量3. const 声明常量4. 解构赋值 1. 初识 ES6 ECMAScript6.0(以下简称ES6)是JavaScript语言的下一代标准&#xff0c;已经在2015年6月正式发布了。它的目标&#xff0c;是使得」JavaScript语言可以用来编写复杂的大型应用程序&#xff0c;成为…

python 报错问题汇总

error: [WinError 32] 另一个程序正在使用此文件&#xff0c;进程无法访问。: d:\\anaconda\\envs\\yolov5\\lib\\site-packages\\ISR-2.2.0-py3.7.egg 解决方法&#xff1a;重启pycharm python-contrib 无法安装 opencv-contrib-python 安装包网址&#xff1a;安装包下载链接…

c++ vector介绍

1、什么是vector std::vector 是C标准库&#xff08;STL&#xff09;中的一个动态数组类模板。它允许存储相同类型的元素集合&#xff0c;并且可以根据需要动态地增加或减少其大小。std::vector 提供了一组函数来访问、插入和删除元素&#xff0c;以及执行其他与数组操作相关的…

新收获——蓝桥杯单片机第十四届国赛程序设计题

大家要是初学&#xff0c;可以去看西风那里的系统课程&#xff0c;非常全面&#xff0c;有利于形成你自己的代码风格。 笔者发文章只是分享性的&#xff0c;有需要者才拿去用其中的一个小片段。 代码在这&#xff1a; 一、这个是首写自主完成的&#xff0c;bug应该也是没有的…

预处理详解(一) -- 预定义符号与#define定义

目录 一. 预定义符号二. #define1.#define定义常量2.#define定义宏3.带有副作用的宏参数4.宏替换的规则5.宏和函数的对比 一. 预定义符号 %s _ _FILE_ _ //文件 %s _ _ DATE_ _ //日期 %s _ _ TIME_ _ //时间 %d _ _ LINE_ _ //行号 %d _ _ STDC_ _ //如果编译器支持 ANSI C,那…

Vmware下减小Ubuntu系统占用系统盘大小

1、虚拟机设置下占用空间 如图&#xff0c;给虚拟机分配了120GB&#xff0c;已经占用116.9GB&#xff0c;开机会提示空间不足。 2、实际使用空间 ubuntu系统下使用“df -h”命令查看实际使用空间大小50GB左右 造成这个原因是&#xff0c;虚拟机的bug&#xff1a;在虚拟机的ub…

算法学习——LeetCode力扣动态规划篇6

算法学习——LeetCode力扣动态规划篇6 121. 买卖股票的最佳时机 121. 买卖股票的最佳时机 - 力扣&#xff08;LeetCode&#xff09; 描述 给定一个数组 prices &#xff0c;它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&…

ADC--数模转换器的使用

目录 前言 ADC接口使用 配置准备 确定引脚编号 光敏电阻--PF7​编辑 ADC3_IN5 开始配置 实验进阶 MQ_3--酒精传感器、水位传感器、火焰传感器 前言 ADC(analog-digital conversion)顾名思义模拟数字转换器,把外界的譬如温度、湿度、酒精含量、水位、特殊光波等等的现实…