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

封装map和set(红黑树作为底层结构如何实现map和set插入遍历)

🎬 胖咕噜的稞达鸭:个人主页

🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》

⛺️技术的杠杆,撬动整个世界!

在这里插入图片描述
在这里插入图片描述

红黑树泛型模式如何适用于map和set?

问题一:set容器存的只有一个单纯的键,key,map中存储的是一个键值对,pair<K,V>,在利用红黑树底层的插入实现map和set的插入时,这个参数要怎么传?

红黑树是map和set的底层结构,但红黑树本身是个通用模板—— 它不知道上层容器存的是 “单纯的键(如set里的int)”,还是 “键值对(如map<int, string>里的pair<int, string>)”。
而红黑树要维持 “按键有序”,必须能从存储的元素中 ** 提取出 “键”** 来比较。
也即是说插入的时候要比较cur所在的_data判断大于还是小于要插入的data,往_data的左子树插入还是右子树插入,这个操作要同时适用于只有一个单纯的键的set,和有键值对的map,就要引进一个仿函数,实现比较逻辑。

RBTree.h实现
RBTree.h中模板内加入第三个参数,class KeyOfT,(template<class K,class T,class KeyOfT>)这个函数的作用就是如果是set的存储类型,传的K就是set中的k,如果是map的存储类型,传的第二个参数就是pair<k,v>,RBTree实现了泛型不知道T参数导致是K,还是pair<K,V>

template<class K, class T, class KeyOfT>

插入的时候,将键提取值KeyOfT kot传到插入的实现逻辑中,用于当一个key要插入到红黑树中,判断是插入在左边还是右边(子树),kot用来提取插入的是set中的key,还是map中的kv.first

map.和set中

但是对于红黑树来说,仅仅进行第三个参数的引用还不足以实现作为map和set的通用模板,因为红黑树本身是个通用模板—— 它不知道上层容器存的是 “单纯的键(如set里的int)”,还是 “键值对(如map<int, string>里的pair<int, string>)”。
为了让红黑树能通用处理set和map的不同存储类型,STL 设计了两个专门的 “取键工具”(仿函数),分别给set和map使用:
给set用的仿函数键提取工具(SetKeyOfT):因为set里存的元素就是 “键” 本身(比如set存的是int),所以这个仿函数的逻辑很简单:输入一个元素,直接返回它自己(因为它本身就是键)。
给map用的仿函数键提取工具(MapKeyOfT):因为map里存的是 “键值对”(比如pair<int, string>),所以这个仿函数的逻辑是:输入一个键值对,返回它的第一个元素(即键)。

具体实现细节:

对于set要实现一个键提取工具,setKeyOfT,构建一个结构体,结构体中const K& operator()(const K& key),重载了operator[],参数const K& key,接收一个K 类型的常量,返回值是 K类型的引用(直接返回传入的key本身),也即是输入一个键,直接返回他自己。
对于map要实现一个键提取工具,mapKeyOfT,构建一个结构体,结构体中const K& operator()(const pair<k,v>& kv),
参数传的是pair<k,v>& kv,接收一个pair<k,v>类型的常量引用,然后返回的是pair<k,v>类型的kv的第一个元素(键,k)。也就是从键值对中提取键,并将其传递给红黑树底层。

struct mapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};
private:RBTree<K, pair<const K, V>, mapKeyOfT>_t;
struct setKeyOfT
{const K& operator()(const K& key){return key;}
};
private:RBTree<K, const  K, setKeyOfT>_t;

除此之外,要借助红黑树实现set和map的插入,还需要迭代器:

iterator的 实现思路,按照中序遍历的方法。
来实现一下红黑树set类和map类的迭代器:
将红黑树(RBTree)内部定义的迭代器类型 “暴露” 给set类,作为set自身的迭代器类型:

public:typedef typename RBTree<K, const K, setKeyOfT>::iterator iterator;typedef typename RBTree<K, const K, setKeyOfT>::const_iterator const_iterator;

将红黑树(RBTree)内部定义的迭代器类型 “暴露” 给map类,作为map自身的迭代器类型:

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;

问题二:迭代器插入实现妙处体现在哪里?map支持【】:

对于set:普通的插入逻辑是 bool Insert(const K& key),插入一个值key,返回bool,用户知晓插入成功或者失败,迭代器实现插入的pair<iterator,bool> Insert(const K& key),插入一个key值,返回bool,用于表示插入成功或者失败,iterator的作用是返回元素在set中的位置,
如果插入成功bool为true,迭代器指向新插入的节点;
如果插入失败bool为false,迭代器指向原有位置的节点(已存在)。
用pair进行封装pair的第一个元素(first)是迭代器(指向元素位置),pair的第二个元素(second)是布尔值(表示插入是否成功)。

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

**对于map:**迭代器实现的插入逻辑是pair<iterator,bool>Insert(const pair<k,v>& kv),插入的元素类型是pair<k,v>,k是键类型,用于红黑树排序和去重,v是值类型,是实际需要插入的值,返回 pair<iterator,bool>,
插入成功,bool为true,iterator指向新插入的键值对kv,
插入失败,bool为false, iterator指向已存在的,键相同的旧键值对(可通过该迭代器修改旧值,如 it->second = 新值)。

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

问题三:怎么理解map 容器的 operator[] 实现代码?

利用operator[],下标访问运算符,通过键 key 访问或插入对应的值,返回值类型是V&,支持对值的直接修改,(map[key] = value),调用Insert插入一个键值对,Insert({ key,V() }); 插入key和value的缺省值,插入成功或者失败,first都会去调用second,
成功就是true,这个键值对充当插入,查找和修改的功能,
失败了就是false.键值对充当查找和修改的功能。
Insert 函数的返回值是 pair<iterator, bool>
第一个元素 iterator:指向插入的键值对(若键已存在,则指向已有键值对)。
第二个元素 bool:表示是否成功插入新键(true 表示插入新键,false 表示键已存在)。

在这里插入图片描述

问题四:实现红黑树的迭代器为什么需要显示定义构造函数?

跟list的迭代器也需要显示定义构造函数一起理解,迭代器的作用是“指向容器中的元素”并支持遍历,
红黑树时一种平衡二叉搜索树,在遍历的时候,要有父亲节点和左右孩子之间有联系才可以实现遍历,因此需要两个关键信息,node* _node(当前指向的节点),node* _root(红黑树的根节点)这些信息无法默认初始化,需要传显示定义构造函数进行初始化。(注意,构造函数传参要有顺序,迭代器返回参数第一个是当前节点,第二个参数是红黑树的根节点,一旦传错了顺序,就会打印不出,代码逻辑出现错误!!!)如下这个错误示范!!!
在这里插入图片描述

list是一种链表构造,一个节点一个节点之间是不连续的所以遍历的时候要有存储前序指针的节点和存储后序指针的节点,Node* _prev, Node* _next;迭代器在遍历的时候,需要有一个节点,这个节点在容器内部实现,将节点的地址传给迭代器进行遍历,Node* node,所以list的迭代器也需要构造函数。

struct RBtreeIterator
{typedef typename RBTreeNode<T> Node;//红黑树节点名称typedef typename RBtreeIterator<T, Ref, Ptr> Self;//红黑树的迭代器Node* _root;//指向根节点Node* _node;//指向红黑树当前节点RBtreeIterator( Node* node,Node* root):_node(node),_root(root){}//构造函数初始化迭代器,分别赋值给node,root
}

在这里插入图片描述

实现迭代器遍历的操作

迭代器++路径是:左子树-根节点-右子树
在这里插入图片描述

迭代器–路径是:右子树-根节点-左子树
看代码注释:

Self operator++()
{if (_node->_right)//如果右子树不为空{Node* LeftMin = _node->_right;while (LeftMin->_left) { LeftMin = LeftMin->_left; }_node = LeftMin;}else//右子树为空{Node* cur = _node; Node* ancestor = cur->_parent;while (ancestor && cur == ancestor->_right) { cur = ancestor; ancestor = cur->_parent; }_node = ancestor;}//右子树为空,说明当前路径已经走完了,要接续遍历整个红黑树,需要找到当前节点是其父亲节点的左孩子的祖先节点//看图,15的右子树为空,15是父亲节点10的右子树,10(cur)是父亲节点18的左子树,cur==ancestor->_left,10访问完了,// cur = ancestor =10 ,ancestor = cur->_parent,找到10的祖先,18.18 就是下一个访问的元素,因为此时ancestor不存在了,出循环return *this;
}Self operator--()
{if (_node == nullptr)//处理特殊情况--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* ancestor = cur->_parent;while (ancestor && cur == ancestor->_left) { cur = ancestor; ancestor = cur->_parent; }_node = ancestor;}return *this;
}

完整源码


#pragma once
#include<iostream>
#include<assert.h>using namespace std;
// 枚举值表示颜色
enum colour
{RED,BLACK
};
template<class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;colour _col;RBTreeNode(const T& data): _data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}};template<class T, class Ref, class Ptr>
struct RBtreeIterator
{typedef typename RBTreeNode<T> Node;//红黑树节点名称typedef typename RBtreeIterator<T, Ref, Ptr> Self;//红黑树的迭代器Node* _root;//指向根节点Node* _node;//指向红黑树当前节点RBtreeIterator( Node* node,Node* root):_node(node),_root(root){}//构造函数初始化迭代器,分别赋值给node,rootSelf operator++(){if (_node->_right)//如果右子树不为空{Node* LeftMin = _node->_right;while (LeftMin->_left) { LeftMin = LeftMin->_left; }_node = LeftMin;}else//右子树为空{Node* cur = _node; Node* ancestor = cur->_parent;while (ancestor && cur == ancestor->_right) { cur = ancestor; ancestor = cur->_parent; }_node = ancestor;}//右子树为空,说明当前路径已经走完了,要接续遍历整个红黑树,需要找到当前节点是其父亲节点的左孩子的祖先节点//看图,15的右子树为空,15是父亲节点10的右子树,10(cur)是父亲节点18的左子树,cur==ancestor->_left,10访问完了,// cur = ancestor =10 ,ancestor = cur->_parent,找到10的祖先,18.18 就是下一个访问的元素,因为此时ancestor不存在了,出循环return *this;}Self operator--(){if (_node == nullptr)//处理特殊情况--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* ancestor = cur->_parent;while (ancestor && cur == ancestor->_left) { cur = ancestor; ancestor = cur->_parent; }_node = ancestor;}return *this;}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;}};
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef RBtreeIterator <T, T&, T* > iterator;typedef RBtreeIterator <T, const T&, const T* > const_iterator;iterator begin(){Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur, _root);}iterator end(){return iterator(nullptr, _root);}const_iterator begin() const{Node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return const_iterator(cur, _root);}const_iterator end() const{return const_iterator(nullptr, _root);}public:pair<iterator, bool> Insert(const T& data){KeyOfT kot;//根节点为空,插入,根节点为黑if (_root == nullptr){_root = new Node(data); _root->_col = BLACK;return { iterator(_root,_root), true };}Node* parent = nullptr; Node* cur = _root;//插入节点,大于根向右插,小于根向左插while (cur){if (kot(cur->_data) > kot(data)) { parent = cur; cur = cur->_left; }else if (kot(cur->_data) < kot(data)) { parent = cur; cur = cur->_right; }else { return { iterator(cur,_root), false }; }}//找到新节点的位置,插入,新节点为红色cur = new Node(data); cur->_col = RED;Node* newnode = cur;if (kot(parent->_data) > kot(data)) { parent->_left = cur; }else { parent->_right = cur; }cur->_parent = parent;//链接父亲while (parent && parent->_col == RED){Node* grandparent = parent->_parent;if (grandparent->_left == parent)					//p是g的左,c的位置可左可右{Node* uncle = grandparent->_right;//叔叔节点是红色的:仅变色,不旋转if (uncle && uncle->_col == RED){grandparent->_col = RED;//爷爷节点是红色uncle->_col = parent->_col = BLACK;//变色再继续向上处理cur = grandparent;parent = cur->_parent;}else//叔叔节点是黑色的:{if (parent->_left == cur)					//p是g的左,c是p的左{//    g             p//  p   u         c   g// c                    urotateRight(grandparent);parent->_col = BLACK; grandparent->_col = RED;}else //parent->_right == cur				// p是g的左,c是p的右{//   g             g              c// p   u         c   u          p   g  //  c          p                      urotateLeft(parent); rotateRight(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}else // (grandparent->_right == parent)				//p是g的右,c的位置可左可右{Node* uncle = grandparent->_left;//叔叔节点是红色的:仅变色,不旋转if (uncle && uncle->_col == RED){grandparent->_col = RED;//爷爷节点是红色uncle->_col = BLACK; parent->_col = BLACK;//继续向上处理cur = grandparent;parent = cur->_parent;}else //叔叔节点是黑色的{if (parent->_right == cur)					//p是g的右,c是p的右{//   g			    p// u   p		  g   c//      c		urotateLeft(grandparent);parent->_col = BLACK; grandparent->_col = RED;}else  //parent->_left == cur				//p是g的右,c是p的左{//   g             g				   c// u   p         u   c				 g   p  //    c                p		   u      rotateRight(parent); rotateLeft(grandparent);cur->_col = BLACK;grandparent->_col = RED;}break;}}}_root->_col = BLACK;//最终的根节点一定是黑色的,不管进行完上述操作根节点是不是还是黑色的,这里都要进行更新。return { iterator(newnode, _root), true };}void rotateLeft(Node* parent){//记录节点subR,subRLNode* subR = parent->_right;Node* subRL = subR->_left;//如果subRL不为空,链接subRL和parentparent->_right = subRL;if (subRL) { subRL->_parent = parent; }//链接subR和parentNode* grandparent = parent->_parent;//保存爷爷节点,用于插入新的subLsubR->_left = parent; parent->_parent = subR;if (parent == _root) { _root = subR; subR->_parent = nullptr; }else{//原来父亲节点在爷爷的什么位置,subL就插入到什么位置if (parent == grandparent->_left) { grandparent->_left = subR; }else { grandparent->_right = subR; }subR->_parent = grandparent;}}void rotateRight(Node* parent){//记录节点subL,subLRNode* subL = parent->_left;Node* subLR = subL->_right;//如果subLR不为空,链接subLR和parentparent->_left = subLR;if (subLR) { subLR->_parent = parent; }//链接subL和parentNode* grandparent = parent->_parent;//保存爷爷节点,用于插入新的subLsubL->_right = parent; parent->_parent = subL;if (parent == _root) { _root = subL; subL->_parent = nullptr; }else{//原来父亲节点在爷爷的什么位置,subL就插入到什么位置if (parent == grandparent->_left) { grandparent->_left = subL; }else { grandparent->_right = subL; }subL->_parent = grandparent;}}
private:Node* _root = nullptr;
};
#pragma once
#include"RBTree.h"
#include<iostream>using namespace std;
namespace Keda
{template<class K>class set{struct setKeyOfT{const K& operator()(const K& key){return key;}};private:RBTree<K, const  K, setKeyOfT>_t;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(); }iterator end() { return _t.end(); }const_iterator begin()const{return _t.begin();}pair<iterator, bool>Insert(const K& key) { return _t.Insert(key); }};}

#pragma once
#include"RBTree.h"
#include<iostream>using namespace std;
namespace Keda
{template<class K, class V>class map{struct mapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<const K, V>, mapKeyOfT>_t;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(); }iterator end() { return _t.end(); }const_iterator begin()const { return _t.begin(); }const_iterator end()const { 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({ key,V() });return ret.first->second;}};
}

测试:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>
using namespace std;
#include"myset.h"
#include"mymap.h"//void Print(const Keda::set<int>& s)
//{
//	Keda::set<int>::const_iterator it = s.end();//会报错
//	while (it !=s.begin())
//	{
//		//--it;
//		cout << *it << " ";
//	}
//	cout << endl;
//}int main()
{Keda::set<int> s;s.Insert(5);s.Insert(1);s.Insert(3);s.Insert(2);s.Insert(6);Keda::set<int>::iterator sit = s.begin();//*sit += 10;会报错,不可修改while (sit != s.end()){cout << *sit << " ";++sit;}cout << endl;for (auto& e : s){cout << e << " ";}cout << endl;//Print(s);Keda::map<string, string> dict;dict.Insert({ "sort", "排序" });dict.Insert({ "left", "左边" });dict.Insert({ "right", "右边" });Keda::map<string, string>::iterator it = dict.begin();while (it != dict.end()){// 不能修改first,可以修改second//it->first += 'x';//it->second = 'x';cout << it->first << ":" << it->second << endl;++it;}cout << endl;for (auto& kv : dict){cout << kv.first << ":" << kv.second << endl;}return 0;}

在这里插入图片描述
在这里插入图片描述

http://www.dtcms.com/a/535684.html

相关文章:

  • 如何保证RabbitMQ不出现消息丢失?
  • 购物网站建设 属于信息系统管理与设计么?一个网站的制作特点
  • 如何快速进行时间序列模型复现(以LSTM进行股票预测为例)
  • Git 远程操作:克隆、推送、拉取与冲突解决
  • Telegram 被封是什么原因?如何解决?(附 @letstgbot 搜索引擎重连技巧)
  • uniapp(1)
  • 河北建站公司优化大师的功能有哪些
  • 电力电网安全实训难题多?VR安全教育软件给出新方案
  • [MySQL]表——聚合函数
  • Java 测验
  • d42:SpringCloud单架构拆分,Nacos注册中心,OpenFeign,网关路由,配置管理
  • 构建智能对话系统:基于LangChain的超级智能体架构解析
  • 幸福指数数据分析与预测:从数据预处理到模型构建完整案例
  • 做网站要费用多少wordpress注册美化
  • 城建亚泰建设集团网站手机网站建设教程视频教程
  • 产品定制网站开发网站建设分析案例
  • 总结企业网站建设的流程网站没备案可以上线吗
  • 公司核名在哪个网站专门做字体设计的网站
  • 潍坊医院网站建设酒生产企业网站建设的目的
  • 购物网站排名前100物流行业网站源码
  • 苏州公司做变更网站免费咨询法律问题的网站
  • 医生可以自己做网站吗服务类网站建设策划书
  • phpcms 网站名称标签松原网站建设公司
  • 打折网站建设教程下载松江网站建设公司怎么样
  • 网站运营计划书网络营销服务商有哪些
  • 网网站开发和设计wordpress 海量数据
  • 买房子上哪个网站最好什么网站做h5没有广告
  • 大型行业门户网站开发建设wordpress自定义首页布局
  • wordpress评论ip网站推广及seo方案
  • 为什么网站找不到了网络营销的内容