C++语法——map与set的封装原理
创始人
2024-03-05 18:57:54

目录

一.数据类型封装

(一).封装方式

(二).封装后如何取key比较

 二.迭代器封装

(一).底层迭代器(红黑树中)

①迭代器++

②迭代器-- 

(二).begin&end&const


一.数据类型封装

(一).封装方式

我们知道,map与set所使用的都是红黑树,但是map是key&value模型,set是key模型。

map需要传两个类型而set只用传一个类型,这在底层红黑树中该怎么兼容呢?

透过SGI版stl_tree.h我们可以看到,红黑树有两个模板参数分别表示key和value。重点来了:

对于map而言需要传入key和pair分别给红黑树的key和value。

set需要传key和key给红黑树的key和value。

再看map与set源码(进行部分截取):

 如果自制的话,大致如此:

template
class Map
{typedef rb_tree, MapOfV>> tree;
public://...private:tree _t;
};
template
class Set
{typedef rb_tree> tree;
public://...private:tree _t;
};

(二).封装后如何取key比较

但是这种封装有一个问题,就是在底层红黑树中,是直接拿value进行比较,那这该怎么办呢?

用仿函数取value中key值来解决。

对于map而言,传仿函数给红黑树,调用仿函数取value(实际是pair)中的key值。

对于set而言,key和value中的值实际上都是key,也传仿函数取即可。

因此,对于上述代码我们看到map和set分别传入了仿函数MapOfV和SetOfV。

仿函数均在上层中(map/set)实现,传入底层红黑树。

代码实现如下(简易):

template
//传入分别是key和pair
//typedef rb_tree, MapOfV>> tree;
class MapOfV
{
public:K operator()(V kv) const{return kv.first;}
};
template
//typedef rb_tree> tree;
class SetOfV
{
public:K operator()(K k) const{return k;}
};

 二.迭代器封装

因为map和set底层都是红黑树,因此迭代器实现在红黑树中(底层),而map和set只是调用红黑树的迭代器。

(一).底层迭代器(红黑树中)

因为红黑树也是由一个个节点组成,这就很容易想到list的迭代器。list迭代器是用类封装,迭代器内部定义一个指针指向节点,对迭代器的操作底层是对迭代器内部指针的操作。

红黑树的迭代器与之类似,也是使用一个类来表示迭代器,内部有一个指向树节点的指针。只要了解过list迭代器都能想出来,不了解可以看看这篇文章哦:为什么List的迭代器是类模板?

对于解引用*和箭头->的封装与list相同,直接转化成对节点指针的操作。

关键在于++和--。 

①迭代器++

红黑树也是二叉树,我们就直接以二叉树为例:


 这里要分两种情况: 

 第一种:节点有右子树,此时要向下走。

以1号节点为例,因为有右子树,++后就是6号节点?——错!

二叉树迭代是前序遍历的方式,++后应该是5号节点,即右子树的最左节点!

第二种:节点没有右子树,此时要向上走。

对比3号和5号节点,都是叶子节点,但是3号节点++后是1号节点,5号++后是6号节点。

得出结论:如果当前节点是父节点的左子树,那++后就是父节点如果是右子树,那么需要向上找直到当前节点是父节点左子树,此时父节点是++后节点。

如果是节点7那么++会一直判断到根节点4,因此,迭代器end()可以设置为root的父节点(null)。

原理很简单,如果是右子节点那说明对于父节点而言你就是++后会找到的节点,但是对于自己而言++后就不可能是父节点。

代码如下:

Self operator++()
{goBack();return *this;
}
void goBack()
{if (_node->right)//如果右子节点不为空{Node* right = _node->right;while (right->left)//直到找到右子树最左节点{right = right->left;}_node = right;}else//右子树为空{Node* parent = _node->parent, * cur = _node;while (parent && cur == parent->right)//直到cur是parent的左子节点为止{cur = parent;parent = cur->parent;}_node = parent;}
}

②迭代器-- 

有了++的理解,--操作就简单了。

还是以该树为例:
 也要分两种情况:
第一种:有左子树,向下走。

以根节点4为例,--之后不是节点2而是节点3。对于有左子树的节点而言,--之后的节点是左子树的最右子节点。

第二种:没有左子树,向上走。

对比节点3和节点5,节点3--之后是节点2,节点5--之后是节点4。可以看出,如果节点是父节点的右子节点,那么--之后就是父节点。但如果是左子节点,那么需要向上判断直到是父节点的右子节点为止。或者如节点1一样,一直判断到根节点为止。

其原理就是如果是父节点的左子节点,那么说明父节点在自己之后,如果是右子节点那么说明在父节点之后。

代码如下:

Self operator--(int)
{Self it(_node);goHead();return it;
}
void goHead()
{if (_node->left)//有左子树{Node* right = _node->left;while (right->right)//直到左子树的最右子节点{right = right->right;}_node = right;}else//无左子树{Node* parent = _node->parent, * cur = _node;while (parent&& cur = parent->left)//直到cur是parent的右子节点{cur = parent;parent = cur->parent;}_node = parent;}
}

(二).begin&end&const

迭代器的begin和end只能在rb_tree中实现,对于begin而言,返回红黑树最左节点的迭代器,end返回root的父节点迭代器(空节点迭代器)。

对于const,首先我们应该知道不管map还是set均不支持对key的修改。

但在实现上map和set大有不同

map采用在传入时就传入const key,而set采用将迭代器都使用底层const迭代器。

用几个小时来制定计划,可以节省几周的编程时间—— 未名


如有错误,敬请斧正

相关内容

热门资讯

猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...