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

C++修炼:map和set的封装

        Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》、《C++修炼之路》

        这期我们模拟实现一下基于红黑树的map和set。

目录

1、封装set和map,解决KeyOfT

2、iterator

3、key不支持修改的问题

4、operator[]


 

1、封装set和map,解决KeyOfT

        首先我们先把set和map的大类实现一下:

map:

#pragma once
#include"RBTree.h"template<class K,class V>
class map
{
public:private:RBTree<K, pair<K, V>> _t;};

set:

#pragma once
#include"RBTree.h"template<class K>
class set
{
public:private:RBTree<K, K> _t;
};

        对于传入红黑树的两个参数,第一个我们传入关键值key,第二个传入我们存储的实际类型,如果是key就再传入一次,如果是key_value就传入pair类型。

        我们需要一个KeyOfT,将key_value里面的key值取出来,因为我们不知道存储的值到底是key还是key_value类型,所以对于map和set我们都需要一个对应的KeyOfT。

map:

struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};

 set:

struct SetKeyOfT
{const K& operator()(const K& key){return key;}
};

         这样的话,我们在传入红黑树时需要往模板中传入三个参数,所以我们对红黑树进行改造:

template<class K,class T,class KeyOfT>
class RBTree
{typedef  RBTreeNode<T> Node;public:pair<Iterator,bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点必须是黑return { Iterator(_root,_root),true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){//这访函数返回T值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->_col = RED;//新增的必须是redif (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED)//如果满足这个情况要一直向上走{Node* grandfather = parent->_parent;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{//u不存在或u存在且为黑//单边高if (cur == parent->_left){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}//对应的是AVL树左边右边高的情况else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return { Iterator(newnode,_root),false };}......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,_root);}}return End();}private:......
private:Node* _root = nullptr;
};

        为了节省篇幅我把所有不涉及改动的函数都先去掉了。另外insert里面涉及的迭代器我们之后再说。

2、iterator

        接着我们实现map和set的迭代器。首先我们在红黑树所在的头文件中把迭代器定义出来:

template<class T,class ptr,class ref>
class RBTreeIterator
{
public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T,ptr,ref> Self;RBTreeIterator(Node* node, Node* root):_node(node),_root(root){}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* minleft = _node->_right;while (minleft->_left){minleft = minleft->_left;}_node = minleft;}else{//节点右为空,找找祖先,并且该祖先是其父亲的左Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right)//parent是空也结束{cur = parent;parent = parent->_parent;}//如果parent为nullptr,nullptr为end()_node = parent;}return *this;}Self& operator--(){if (_node == nullptr) // end(){// --end(),特殊处理,⾛到中序最后⼀个结点,整棵树的最右结点 Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){// 左⼦树不为空,中序左⼦树最后⼀个 Node* rightMost = _node->_left;while (rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else{// 孩⼦是⽗亲右的那个祖先 Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}private:Node* _node;Node* _root;
};

        对于迭代器这个类,++,--两个操作需要特殊说明一下。

        对于++操作,首先,因为我们map和set++是有序数的下一个,对应的就是红黑树的中序遍历的下一个节点。那么我们不可能因为一个++就遍历整个红黑树去找下一个节点吧?所以我们分情况讨论一下,看看红黑树的下一个节点理应出现在哪。如果当前节点右子树不为空,下一个节点就应该是右子树的最左节点。如果当前节点的右子树为空,下一个节点就应该是父节点,并且该父节点一定是他的父节点(父节点的父节点)的左子树。如果一直查找直到nullptr,就说明++之后应该是end,那就返回nullptr就可以了。

        对于--操作,首先我们要对当前节点是end()的情况做特殊处理,这个节点的上一个节点应该是整棵树的最右节点。第二种情况就是左子树不为空,如果左子树不为空,说明他的上一个节点为左子树的最右节点。第三种情况就是左子树为空,此时他的上一个节点应该是父节点,并且此父节点一定是他的父节点(父节点的父节点)的右子节点。

接下来我们对红黑树进行改造,新增迭代器的部分:

typedef  RBTreeIterator<T,T*,T&> Iterator;
typedef  RBTreeIterator<T,const T*,const T&> ConstIterator;
Iterator Begin()
{Node* minleft = _root;while (minleft && minleft->_left)//这棵树可能是空的{minleft = minleft->_left;}return Iterator(minleft,_root);
}
Iterator End()
{return Iterator(nullptr,_root);
}
ConstIterator Begin() const
{Node* minleft = _root;while (minleft && minleft->_left)//这棵树可能是空的{minleft = minleft->_left;}return ConstIterator(minleft, _root);
}
ConstIterator End() const
{return ConstIterator(nullptr, _root);
}

接下来我们对insert进行改造,使他的返回值和源码一样:

pair<Iterator,bool> Insert(const T& data)
{if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//根节点必须是黑return { Iterator(_root,_root),true };}KeyOfT kot;Node* parent = nullptr;Node* cur = _root;while (cur){//这访函数返回T值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->_col = RED;//新增的必须是redif (kot(parent->_data) < kot(data)){parent->_right = cur;}else{parent->_left = cur;}cur->_parent = parent;while (parent && parent->_col == RED)//如果满足这个情况要一直向上走{Node* grandfather = parent->_parent;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){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}else{Node* uncle = grandfather->_left;if (uncle && uncle->_col == RED){uncle->_col = BLACK;parent->_col = BLACK;grandfather->_col = RED;cur = grandfather;parent = cur->_parent;}else{if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}_root->_col = BLACK;return { Iterator(newnode,_root),false };
}

         接下来我们再去map和set的头文件中,对迭代器进行进一步的封装:

map:

typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::Iterator iterator;
typedef typename RBTree < K, pair<K, V>, MapKeyOfT>::ConstIterator const_iterator;
pair<iterator,bool> insert(const pair<K, V>& kv)
{return _t.Insert(kv);
}
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}
const_iterator begin() const 
{return _t.Begin();
}
const_iterator end() const 
{return _t.End();
}
iterator find(const K& key)
{return _t.Find(key);
}

set:

typedef typename RBTree < K, K, SetKeyOfT>::Iterator iterator;
typedef typename RBTree < K, K, SetKeyOfT>::ConstIterator const_iterator;
pair<iterator, bool> insert(const K& key)
{ return _t.Insert(key);
}
iterator begin()
{return _t.Begin();
}
iterator end()
{return _t.End();
}
const_iterator begin() const
{return _t.Begin();
}
const_iterator end() const
{return _t.End();
}
iterator find(const K& key)
{return _t.Find(key);
}

        注意这里我们typedef的迭代器一定要是在红黑树的大类中我们已经封装好的迭代器,不然没有对应的Begin,End的操作。并且我们要用typename标记,不然编译器不知道iterator到底是一个类还是一个变量。

3、key不支持修改的问题

        这个问题就很好解决了,对于set,我们只需要在第二个参数加上const 就行了:

RBTree<K,const K, SetKeyOfT> _t;

        对于map,我们在第二个参数pair中的key值之前加上const:

RBTree<K, pair<const K, V>, MapKeyOfT> _t;

 4、operator[]

        这里的[]本质还是对insert的复用,因为[]本身也可以插入,如果插入成功,就返回新插入的值对应的value值,如果插入失败,就返回原有的该值的value值(具体可以看map和set的使用这一篇)。那这个操作完全可以通过复用insert来实现:

map:

V& operator[](const K& key)
{pair<iterator, bool> t = insert({ key ,V()});//int的缺省值是0auto it = t.first;return it->second;//这里会省略一个->
}

set:

        set不支持operator[]。

        好了,到这里我们就基本实现完了map和set,现在解决大部分读者在学习这里的时候会遇到的一个疑问。为啥模板类中要传入一个K再传入一个T呢?假设在set中,我们存储的值类型为int,那么传两个int进来不是妥妥的重复吗?

        首先,我们模拟实现要和源码尽可能保持一致,源码是这么写的,所以我们也这么写。那么源码为什么要这么写呢?

        确实,对于set来说就是重复传了。但是对于map来说,第一个K的作用是指示key值的类型。因为find和erase操作的函数参数都是K类型的。也就是说我们得用一个类型来指示我们形参是什么类型的。假设map中我们只传一个pair进来,我们根本无法确定key值的类型是什么。

        并且,本着能用一套逻辑就用一套逻辑的原则,通过传一个key的类型来解决这个问题,总比写两套逻辑,一个是key类型红黑树,一个是key_value类型红黑树要方便得多。

         完整代码放在我的gitee了: 

         RBTree · 主线学习简单项目 - 码云 - 开源中国

        好了,今天的内容就分享到这,我们下期再见!

 

http://www.xdnf.cn/news/689509.html

相关文章:

  • 【线程与进程区别】
  • 高效合并 Excel 表格实用工具
  • AIoT赋能场馆数字化转型:智能管理新生态
  • 拨云见日:Arbitrum引领EVM的未来
  • Condition源码解读(二)
  • 4.8.2 利用Spark SQL计算总分与平均分
  • Flink 核心机制与源码剖析系列
  • spark- ResultStage 和 ShuffleMapStage介绍
  • 力扣HOT100之回溯:51. N 皇后
  • 电脑长期不关机会怎样?
  • 「Python教案」通用序列操作
  • 股指期货的基差跟升贴水概念
  • 力扣-找到字符串中所有字母异位符
  • JDBC+HTML+AJAX实现登陆和单表的CRUD
  • 互联网大厂Java求职面试:AI大模型推理服务性能优化与向量数据库分布式检索
  • linux 性能优化-内存
  • windows安装启动elasticsearch
  • Linux之高效文本编辑利器 —— vim
  • 家用热水器用户行为分析与事件识别
  • 微信小程序页面嵌套web-view点击系统导航返回时进行弹窗处理
  • nt!CcGetVacbMiss函数分析之设置好nt!_VACB然后调用函数nt!SetVacb
  • LiveWallpaperMacOS:让你的 Mac 桌面动起来
  • Mac完美终端(iterm2 + oh my zash + tmux+ControlMaster)
  • Axure项目实战:运输统计页引入echarts实现高保真设计(JS代码ctrl+c ctrl+v懂得来)
  • OpenHarmony定制系统组合按键(二)
  • Pytest 是什么
  • 进阶知识:Selenium底层原理深度解析
  • Grafana-Gauge仪表盘
  • 5.28 后端面经
  • docker部署redis mysql nacos seata rabbitmq minio onlyoffice nginx实战