关于map和set的实现先看库里面是怎样的.

 

 库里面的map和set的底层都是一个叫做_Tree的封装类来完成的.下图中可以看到,map和set中的迭代器也基本上用的是_tree里面实现的迭代器.

 这与之前学的栈与队列很像,栈与队列是将一个线性容器封装起来,通过调用线性容器自身的函数,实现栈和队列的一些功能,map和set也是将_Tree封装起来实现他们的一些功能.

 别的不认识先不说,就是最后的那个枚举常量中的red和black这就是一颗红黑树.

所以map和set同时封装了红黑树,作为底层实现他们各自的功能的.

map中的数据是一个键值对,而set中的数据是值.如何用一个红黑树同时满足两个容器的实现呢.可以想象map和set只是数据不同而大部分内容都是相同的,若为此写两个红黑树代码未免太冗余了,这种情况在之前的学习中实现优先级时遇到过就是通过调节一个仿函数的模版参数来实现大小数优先级的问题,无非就是有个建大堆还是建小堆的问题,他满之间的区别就是大于号和小于号的差别,当时就是用了一个仿函数,实现大小优先级的灵活变化.如下图:

 

 可以看到先定义了两个大小仿函数,然后在优先级队列模板参数部分,多传了一个,仿函数参数,然后在比较大小时用这个仿函数完成大小的比较.

 还有就是在之前实现list迭代器过程中由于list时链式的结构,每个节点都是一个指针,若是单使用const迭代器限制这个指针只读,但是并没有限制指针的解引用和解引用后的引用,导致const迭代器中的数据被改变,所以这时需要将T*和T&也限制为只读(T为节点自定义类).可以将迭代器的模版参数设置为三个,分别是指针,解引用和引用,根据不同的参数调用不同的迭代器.

 这也是一种复用.

那么map与set同时复用一个红黑树的困难在哪里呢,

比如将红黑树中的模版参数变为一个T,T既可以是键值对也可以是一个值,这是困难来了,在map和set用key_value时是如何区分并使用的的呢.

思路一样还是和上面一样,往红黑树中加入一个仿函数参数,在使用到键值时用这个仿函数重载(),在map和set中实现这个仿函数,并在使用T时根据情况,若是map使用红黑树,则调用map中实现的仿函数.若是set就调用set中的,以此就解决了键值使用的问题.

 上图中KofT是仿函数参数.

 

 分别在set与map中实现这个仿函数,在需要用到的时候map返回pair中的first,set直接返回key.

 

还有一个需要注意的小问题:问什么set的中的key和value是一样的,而map中既有一个key,还有一个键值对pair<key,value>一起作为模版参数,乍一看是冗余的,但是将看似多余的第一个模板参数K去掉后,发现map使用异常,因为map的find函数的参数可以是key_type,但是去掉了后就只剩下value_type,虽然后者也包含key_type,但是Key_type无法作为一个参数传进find函数中,就像下面这样

 find函数必须传进来一个类型为K的参数来比较.所以红黑树就有了三个模版参数,因为set需要复用这个红黑书就不得不在使用前将key,和value都设置为一样的int

 

 接下来就是迭代器的实现:

与之前一样定义一个迭代器的自定义类,用红黑树中的节点默认构造迭代器,重载指针运算符,解引用运算符,和最重要的自增运算符,比较难搞的就是自增运算符,一般中序遍历用的多,所以默认按照中序遍历自增,

首先是begin函数,将根节点一直向左迭代,直到子树不为空,将cur赋给此迭代器的节点,然后返回重载解引用的this指针.

end直接构造空返回.

重载前置自增运算符:

首先分为两大类情况L:

1:此节点的右节点不为空,则表示没有中序遍历到末尾,下一个节点就是右子树的最左端.

2:若右节点为空,表示可能迭代到了末尾也可能迭代到了一颗子树完毕,下一个节点是其祖先节点,此时又有两种小情况:

1>:此节点的祖先是双亲节点的右节点,表示以其祖先的双亲结点为根结点的子树便利完了,若这个节点是最后的根节点那么中序遍历结束.

2>:若是左结点则遍历到的右子树为空的节点的下一个中序遍历节点就是这个左结点,返回它的迭代器即可.

 

 

 

 

 上图为相关代码,自减运算符与自增运算符思路相反.

tips:最后一幅图中模版没有实例化,若要将这个重新typedef就得加上typename.

重载最特殊的方括号运算符后面补充.....

Copyright © 2024 玄灵镜
Powered by .NET 8.0 on Kubernetes