C++之模拟实现map和set

模拟实现map和set

  • 红黑树源代码
  • 红黑树模板参数控制
  • 红黑树结点当中存储的数据
  • 仿函数的增加
  • 正向迭代器的实现
    • * 运算符重载
    • ->运算符重载
    • !=和==运算符重载
    • ++运算符重载
    • - -运算符重载
    • begin()与end()实现
  • set的模拟实现
  • map的模拟实现
  • 封装后红黑树代码

红黑树源代码

#pragma onceenum color
{BLACK,RED
};
template<class K, class V>
struct RBTreeNode
{//三叉链RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;//存储的键值对pair<K, V> _kv;//结点的颜色color _col; //红/黑//构造函数RBTreeNode(const pair<K, V>& kv):_left(nullptr), _right(nullptr), _parent(nullptr), _kv(kv), _col(RED){}
};template <class K, class V>
class RBTree
{typedef RBTreeNode<K, V> Node;
public:bool Insert(const pair<K, V>& kv){//根结点为空if (_root == nullptr){//创建根结点_root = new Node(kv);//颜色变为黑_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){//如果插入key值小于根结点key值if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}//如果插入key值大于根结点key值else if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}//相等,返回falseelse{return false;}}//创建cur结点cur = new Node(kv);//颜色初始化为红色cur->_col = RED;//如果插入key值小于parent结点key值if (parent->_kv.first > kv.first){//在左边插入parent->_left = cur;}//如果插入key值大于parent结点key值else{//在右边插入parent->_right = cur;}//跟父结点连接起来cur->_parent = parent;while (parent && parent->_col == RED){//定义祖父节点Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//如果父结点在祖父节点左边if (parent == grandfather->_left){//定义叔叔结点在祖父结点右边Node* uncle = grandfather->_right;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//进行颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点左边if (cur == parent->_left){//右单旋+颜色调整RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//左右双旋+颜色调整RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//如果父结点在祖父节点右边else{//定义叔叔结点在祖父结点左边Node* uncle = grandfather->_left;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点右边if (cur == parent->_right){//左单旋+颜色调整RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//右左双旋+颜色调整RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//根结点变为黑色_root->_col = BLACK;return true;}
private:void RotateL(Node* parent){//记录parent结点的父结点Node* ppNode = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;//建立parent与subRL之间的关系parent->_right = subRL;if (subRL)subRL->_parent = parent;//建立parent与subR之间的关系subR->_left = parent;parent->_parent = subR;//判断根结点是否就是parentif (_root == parent){_root = subR;subR->_parent = nullptr;}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;Node* ppNode = parent->_parent;//建立subLR与parent关系parent->_left = subLR;if (subLR)subLR->_parent = parent;//建立subL与parent关系subL->_right = parent;parent->_parent = subL;//判断根结点是否就是parentif (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}
private:Node* _root = nullptr;
};

红黑树模板参数控制

我们可以知道,set属于K模型的容器,map属于KV模型的容器,那我们如何使用一棵红黑树来实现map和set呢?

我们就需要控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T。

template <class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;
public://....
private:Node* _root = nullptr;

T模板参数可能只是键值Key,也可能是由Key和Value共同构成的键值对。如果是set容器,那么它传入底层红黑树的模板参数就是Key和Key:

namespace gtt
{template<class K>class set{public://private:RBTree<K, K> _t;};
}

map它传入底层红黑树的模板参数就是Key以及Key和Value构成的键值对:

namespace gtt
{template<class K, class V>class map{public://private:RBTree<K, pair<K, V>> _t;};
}

我们需要注意的是模板中第一个参数K是不可以省略掉的,对于set容器来说第一个参数K和第二个参数K都是一样的,并不会影响什么,对于map容器来说,进行find或者erase时,我们依然需要返回一个key类型的迭代器,所以第一个K不可以省略。

红黑树结点当中存储的数据

对于上层结构:

  1. map:K代表键值Key,T代表由Key和Value构成的键值对。
  2. set容器:K和T都代表键值Key。

对于set容器来说,底层红黑树结点当中存储K和T都是一样的,但是对于map容器来说,底层红黑树就只能存储T了。由于底层红黑树并不知道上层容器到底是map还是set,因此红黑树的结点当中直接存储T就行了,这样一来,当上层容器是set的时候,结点当中存储的是键值Key;当上层容器是map的时候,结点当中存储的就是<Key, Value>键值对。
在这里插入图片描述
代码如下:

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

仿函数的增加

对于set容器,我们需要进行键值比较就是对key值进行比较,也就是直接比较T就可以了,但是对于map容器来说,我们需要比较的是键值对<key,value>中的key,我们需要先将key提取出来,在进行比较。

所以,我们需要在上层map和set中各提供一个仿函数,根据传入的T类型分开进行比较操作:

set仿函数:

namespace gtt
{template<class K>class set{//仿函数struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://private:RBTree<K, K, SetKeyOfT> _t;};
}

map仿函数:

namespace gtt
{template<class K, class V>class map{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public://private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};
}

我们提供了仿函数以后,在进行传参的过程,编译器就会识别我们是需要调用map进行比较还是调用set进行比较,不同的容器调用不同的比较方式:
在这里插入图片描述

正向迭代器的实现

红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。

template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ref, Ptr> Self;Node* _node;//构造函数__TreeIterator(Node* node):_node(node){}
};

* 运算符重载

解引用操作,我们直接返回结点数据的引用即可:

//解引用
Ref operator*()
{return _node->_data;
}

->运算符重载

->也就是返回结点数据的地址:

//->
Ptr operator->()
{return &(_node->_data);
}

!=和==运算符重载

!=和==就是判断两个迭代器所封装的结点是否是同一个

//不等于
bool operator!=(const Self& s) const
{return _node != s._node;
}

++运算符重载

红黑树的正向迭代器时,一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。

逻辑就是:

  1. 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点。
  2. 如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。
    在这里插入图片描述
    前置++代码如下:
Self& operator++()
{//右子树不为空,就去找右子树中最左节点if (_node->_right){Node* left = _node->_right;while (left->_left){left = left->_left;}//++后变为该结点_node = left;}//右子树为空,就去找孩子不在父亲右的祖先else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}//++后变为该结点_node = parent;}return *this;
}

- -运算符重载

红黑树的正向迭代器时,一个结点的正向迭代器进行–操作后,应该根据红黑树中序遍历的序列找到当前结点的前一个结点:

逻辑如下:

  1. 如果当前结点的左子树不为空,则–操作后应该找到其左子树当中的最右结点。
  2. 如果当前结点的左子树为空,则–操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。

代码如下:

Self& operator--()
{//左子树不为空,就去找左子树中最右节点if (_node->_left){Node* right = _node->_left;while (right->_right){right = right->_right;}//--后变为该结点_node = right;}//左子树为空,就去找孩子不在父亲左的祖先else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}//--变为该结点_node = parent;}
}

begin()与end()实现

  1. begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。
  2. end函数返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器。
template <class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __TreeIterator<T, T&, T*> iterator;//正向迭代器iterator begin(){Node* left = _root;//寻找最左结点while (left && left->_left){left = left->_left;}//返回最左结点的正向迭代器return iterator(left);}iterator end(){//返回由nullptr构造得到的正向迭代器return iterator(nullptr);}
private:Node* _root = nullptr;

set的模拟实现

namespace gtt
{template<class K>class set{//仿函数struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;//begin()iterator begin(){//调用红黑树的begin()return _t.begin();}//end()iterator end(){//调用红黑树的end()return _t.end();}//插入函数pair<iterator, bool> Insert(const K& key){//调用红黑树的Insertreturn _t.Insert(key);}private:RBTree<K, K, SetKeyOfT> _t;};

map的模拟实现

namespace gtt
{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<K, V>, MapKeyOfT>::iterator iterator;//begin()iterator begin(){return _t.begin();}//end()iterator end(){return _t.end();}//插入函数pair<iterator, bool> Insert(const pair<K, V>& kv){return _t.Insert(kv);}//[]运算符重载V& operator[](const K& key){pair<iterator, bool> ret = Insert(make_pair(key, V()));return ret.first->second;}private:RBTree<K, pair<K, V>, MapKeyOfT> _t;};

封装后红黑树代码

#pragma onceenum color
{BLACK,RED
};
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){}
};template<class T, class Ref, class Ptr>
struct __TreeIterator
{typedef RBTreeNode<T> Node;typedef __TreeIterator<T, Ref, Ptr> Self;Node* _node;//构造函数__TreeIterator(Node* node):_node(node){}//解引用Ref operator*(){return _node->_data;}//->Ptr operator->(){return &(_node->_data);}//不等于bool operator!=(const Self& s) const{return _node != s._node;}//等于bool operator==(const Self& s) const{return _node == s._node;}Self& operator++(){//右子树不为空,就去找右子树中最左节点if (_node->_right){Node* left = _node->_right;while (left->_left){left = left->_left;}//++后变为该结点_node = left;}//右子树为空,就去找孩子不在父亲右的祖先else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}//++后变为该结点_node = parent;}return *this;}Self& operator--(){//左子树不为空,就去找左子树中最右节点if (_node->_left){Node* right = _node->_left;while (right->_right){right = right->_right;}//--后变为该结点_node = right;}//左子树为空,就去找孩子不在父亲左的祖先else{Node* parent = _node->_parent;Node* cur = _node;while (parent && cur == parent->_left){cur = parent;parent = parent->_parent;}//--变为该结点_node = parent;}}
};template <class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef __TreeIterator<T, T&, T*> iterator;//正向迭代器iterator begin(){Node* left = _root;//寻找最左结点while (left && left->_left){left = left->_left;}//返回最左结点的正向迭代器return iterator(left);}iterator end(){//返回由nullptr构造得到的正向迭代器return iterator(nullptr);}pair<iterator, bool> Insert(const T& data){KeyOfT kot;//根结点为空if (_root == nullptr){//创建根结点_root = new Node(data);//颜色变为黑_root->_col = BLACK;return make_pair(iterator(_root), true);}Node* cur = _root;Node* parent = nullptr;while (cur){//如果插入key值小于根结点key值if (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}//如果插入key值大于根结点key值else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}//相等,返回falseelse{return make_pair(iterator(cur), false);}}//创建cur结点cur = new Node(data);Node* newnode = cur;//颜色初始化为红色cur->_col = RED;//如果插入key值小于parent结点key值if (kot(parent->_data) > kot(data)){//在左边插入parent->_left = cur;}//如果插入key值大于parent结点key值else{//在右边插入parent->_right = cur;}//跟父结点连接起来cur->_parent = parent;while (parent && parent->_col == RED){//定义祖父节点Node* grandfather = parent->_parent;assert(grandfather);assert(grandfather->_col == BLACK);//如果父结点在祖父节点左边if (parent == grandfather->_left){//定义叔叔结点在祖父结点右边Node* uncle = grandfather->_right;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//进行颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//继续向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点左边if (cur == parent->_left){//右单旋+颜色调整RotateR(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//左右双旋+颜色调整RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}//如果父结点在祖父节点右边else{//定义叔叔结点在祖父结点左边Node* uncle = grandfather->_left;//情况一://叔叔结点存在且为红if (uncle && uncle->_col == RED){//颜色调整parent->_col = uncle->_col = BLACK;grandfather->_col = RED;//向上调整cur = grandfather;parent = cur->_parent;}//情况二和三://叔叔结点不存在或者存在且为黑else{//插入结点在父结点右边if (cur == parent->_right){//左单旋+颜色调整RotateL(grandfather);grandfather->_col = RED;parent->_col = BLACK;}//插入结点在父结点左边else{//右左双旋+颜色调整RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}//根结点变为黑色_root->_col = BLACK;return make_pair(iterator(newnode), true);}void RotateL(Node* parent){//记录parent结点的父结点Node* ppNode = parent->_parent;Node* subR = parent->_right;Node* subRL = subR->_left;//建立parent与subRL之间的关系parent->_right = subRL;if (subRL)subRL->_parent = parent;//建立parent与subR之间的关系subR->_left = parent;parent->_parent = subR;//判断根结点是否就是parentif (_root == parent){_root = subR;subR->_parent = nullptr;}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;Node* ppNode = parent->_parent;//建立subLR与parent关系parent->_left = subLR;if (subLR)subLR->_parent = parent;//建立subL与parent关系subL->_right = parent;parent->_parent = subL;//判断根结点是否就是parentif (_root == parent){_root = subL;subL->_parent = nullptr;}else{if (ppNode->_left == parent){ppNode->_left = subL;}else{ppNode->_right = subL;}subL->_parent = ppNode;}}
private:Node* _root = nullptr;
};

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

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

相关文章

SUMPRODUCT函数

SUMPRODUCT函数返回相应范围或数组的个数之和。 默认操作是乘法&#xff0c;但也可以执行加减除运算。 本示例使用 SUMPRODUCT 返回给定项和大小的总销售额&#xff1a; SUMPRODUCT 匹配项 Y/大小 M 的所有实例并求和&#xff0c;因此对于此示例&#xff0c;21 加 41 等于 62。…

JVM系列 运行时数据区

系列文章目录 第一章 运行区实验 文章目录 系列文章目录前言一、堆&#xff08;Heap&#xff09;1.1、新生代/Young区1.1.1、Eden区1.1.2、Survival区 1.2、年老代&#xff08;old区&#xff09; 二、虚拟机栈&#xff08;Stack&#xff09;2.1、栈顶缓存技术2.2、溢出2.3、栈…

NUC980webServer开发

目录 1.RTL8189FTV驱动移植 2.wifi配置工具hostapd移植 1.openssl-1.0.2r交叉编译 2.libnl-3.2.25.tar.gz交叉编译 3.hostapd-2.9.tar.gz交叉编译 4.移植相关工具到开发板 1.RTL8189FTV驱动移植 1. 把驱动文件源码放在linux源码的drivers/net/wireless/realtek/rtlwifi/目录…

VBA技术资料MF52:VBA_在Excel中突出显示前 10 个值

【分享成果&#xff0c;随喜正能量】一言之善&#xff0c;重于千金。善良不分大小&#xff0c;有时候你以为的一句话&#xff0c;小小的举手之劳&#xff0c;也可能就是别人的救赎&#xff01;不要吝啬你的善良&#xff0c;因为你永远不知道那小小的善良能给多少人带来光明。。…

leetcode687. 最长同值路径(java)

最长同值路径 题目描述DFS 深度遍历代码演示 题目描述 难度 - 中等 LC - 687. 最长同值路径 给定一个二叉树的 root &#xff0c;返回 最长的路径的长度 &#xff0c;这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。 两个节点之间的路径长度 由它们之…

Docker安装

一、CentOS7安装Docker 1、安装 Docker CE 支持 64 位版本 CentOS 7&#xff0c;并且要求内核版本不低于 3.10&#xff0c; CentOS 7 满足最低内核的要求&#xff0c;所以我们在CentOS 7安装Docker。 ①如果虚拟机存在Docker 先卸载 yum remove docker \ …

怎样获取字符串数组的长度_使用sizeof(array) / sizeof(array[0])

使用sizeof() C、C中没有提供直接获取数组长度的函数&#xff0c;对于存放字符串的字符数组提供了一个strlen函数获取长度&#xff0c;那么对于其他类型的数组如何获取他们的长度呢&#xff1f; 其中一种方法是使用sizeof(array) / sizeof(array[0]), 在C语言中习惯上在使用时…

排序算法:快速排序(三种排序方式、递归和非递归)

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关排序算法的相关知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通…

美业创新之路:广告电商模式让你的品牌脱颖而出

美业是一个巨大的市场&#xff0c;但也面临着激烈的竞争和消费者的多样化需求。如何在这个市场中脱颖而出&#xff0c;实现品牌的增长和盈利呢&#xff1f;答案就是广告电商模式。 广告电商模式是一种结合了社交电商和广告分佣的新型电商模式&#xff0c;它可以让消费者在购物的…

MATLAB入门-字符串操作

MATLAB入门-字符串操作 注&#xff1a;本篇文章是学习笔记&#xff0c;课程链接是&#xff1a;link MATLAB中的字符串特性&#xff1a; 无论是字符还是字符串&#xff0c;都要使用单引号来‘’表示&#xff1b;在MATLAB中&#xff0c;字符都是在矩阵中存储的&#xff0c;无论…

CFTC可能比SEC更可怕,将监管炮口直接对准DeFi?

还未开始享受Uniswap在法庭上为DeFi行业带来的“胜利果实”&#xff0c;美国商品期货委员会&#xff08;CFTC&#xff09;在一个星期之后立即将其无情砸碎&#xff0c;并将其监管大炮直接对准了DeFi衍生品市场&#xff0c;乃至整个DeFi行业。 2023年9月7日&#xff0c;CFTC宣布…

Spring Reactive:响应式编程与WebFlux的深度探索

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…