• C++ set map 的模拟实现


    在这里插入图片描述

    set 的模拟实现

    我们在很早之前就提到过,set 的底层数据结构是红黑树。红黑树的实现一般都是 key-value 的结构。但是我们在使用 set 的时候明明只传入了一个模板参数哇!我们来看库中的实现:

    在这里插入图片描述

    我们可以看到,set 的模板参数 Key 就是存储的数据类型,库中对 Key 进行了重命名:key_type 和 value_type,然后将 key_type 和 value_type 分别作为红黑树的 key 和 value。因此 set 的底层就是存储了两个 key 的红黑树。

    我们仿照库里面的实现方式,就可以定义出来我们自己的 set 的结构啦:

    #pragma once
    #include"RBTree.h"
    
    namespace Tchey
    {
        template<class K>
        class set
        {
        public:
    
    
        private:
            RBTree<K, K> _t;
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    记得复制一份我们自己实现的红黑树哦!

    修改红黑树节点的定义

    为了使得一个红黑树的模板类能同时适配出来 map 和 set 我们需要对红黑树节点的定义做修改!

    在上面的 set 类中成员属性是这么写的:RBTree _t。在使用 map 的时候我们知道 map 的数据是以 key-value 的形式存储的。那 map 类中成员属性该怎么写呢?是这样吗:RBTree _t。我们先来看我们之前实现的红黑树对于节点的定义:

    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) //节点默认的颜色是红色
    	{}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    这是以 key-value 的形式定义的红黑树节点。如果 map 就是上面那样定义的话,那么节点中的 K 和 V 都是存储的数据。

    • 当我们在实现红黑树的迭代器的时候,重载 * 运算符的返回值就很难书写,对于 set 返回 K 就行,但是对于 map 就必须返回 pair 无法做到统一,那么意味着你就需要对 set 写一个红黑树迭代器的实现,map 写一个红黑树迭代器的实现。就会有大量重复的代码,这是不被容忍的!
    • 其次这么写在 set 插入数据的时候就必须插入一个 pair 导致红黑树中的节点中存储了冗余的数据。

    因此我们就需要对红黑树的节点稍作修改,使得一个红黑树就能适配出 map 和 set。在我们之前实现其他容器的时候我们已经积累了相关的经验,只要不一样,直接将不一样的地方提炼成模板参数就行啦,根据传入参数的不同,实例化出来不同的类型。

    于是我们对红黑树节点做出如下修改:

    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) //节点默认的颜色是红色
    	{}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    修改红黑树的成员属性

    RBTree 的实现中,成员变量是红黑树的根节点,当时我们是这样定义的:

    template<class K, class V>
    class RBTree
    {
        typedef RBTreeNode<K, V> Node;
        Node* _root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    现在我们修改了红黑树节点的定义自然不能这么写了!联想到 set 中是这么使用红黑树的:RBTree _t 看上去定义 Node 既可以传 K 也可以传 V。但是为了适配 map,我们会选择传递 V 过去,像下面这样:

    template<class K, class V>
    class RBTree
    {
        typedef RBTreeNode<V> Node;
        Node* _root;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    改到这里,想必你就明白了 map 中的红黑树应该怎么定义了:真正的数据类型是 RBTree 的第二个模板参数,因此 map 中应该这么使用红黑树:RBTree> _t

    通过这样的方式构造出来的红黑树节点,里面的数据既不会冗余,也可以做到同时适配 map 和 set。

    迭代器实现

    红黑树,节点的物理空间不连续,无法使用原生指针作为红黑树的迭代器,需要对节点进行封装,构造一个迭代器类!同 list 的迭代器,因为我们不仅要实现普通的迭代器,还需要实现 const 的迭代器,所以还需要加上两个模板参数,用来控制 operator*operator-> 返回不同的值。

    下面就是最基本的结构啦:

    template<class T, class Ref, class Ptr>
    struct __TreeIterator
    {
    	typedef RBTreeNodeL<T> Node;
    	typedef __TreeIterator<T, Ref, Ptr> self;
    
    	Node* _node;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    构造函数

    在等会实现 begin,end 等接口时都需要构造迭代器返回。迭代器的构造显然是通过节点的指针来的!

    template<class T, class Ref, class Ptr>
    struct __TreeIterator
    {
    	typedef RBTreeNodeL<T> Node;
    	typedef __TreeIterator<T, Ref, Ptr> self;
    	__TreeIterator(Node* node)
    		:_node(node)
    	{}
    
    	Node* _node;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Ref operator*() 和 Ptr operator->()

    有了前面的铺垫,我们知道 operator*operator-> 是可以直接适配 map 和 set 的,直接上手写就行啦。

    Ref operator*()
    {
        return _node->_data;
    }
    
    Ptr operator->()
    {
        return &_node->_data;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    bool operator!=(const self& s) const

    判断两个迭代器是不是不想等,就是判断节点的指针是否相等!

    bool operator!=(const self& s) const
    {
        return _node != s._node;
    }
    
    • 1
    • 2
    • 3
    • 4

    self& operator++()

    我们通过例子来看看 operator++() 的逻辑是怎么样的?在 set 的学习部分我们知道了,使用迭代器遍历 set 之后得到的是一个有序的序列,显然迭代器遍历的顺序就是走的中序遍历。

    如下图所示的例子:iterator 对应的节点是 13 那么加加之后显然就是 15。我们来抽一下,假设 15 是一颗红黑树,而不是单纯的一个节点!那么 13 这个节点对应的迭代器加加之后,就是右子树对应的最左节点。

    在这里插入图片描述

    经过这么抽象,我们就得到了 operator++ 的关键操作之一:如果当前迭代器对应的节点的右子树不为空,那么加加之后的结果就是右子树的最小节点(右子树的最左节点)。

    那么如果右子树为空怎么办呢?

    我们来看下面的情况,iterator 对应的节点值为 22,那么这个位置的迭代器加加之后显然就是 25 这个节点对应的迭代器。根据这个现象,如果当前 iterator 对应节点的右子树为空,那么就是父节点对应位置的迭代器嘛?

    在这里插入图片描述

    答案是不完全是哦!我们来看下面的情况:

    这个 iterator 对应的节点为 15,那么加加之后该指向哪个节点呢?是 13 吗?根据中序遍历的结果来看,加加之后正确的结果应该是 17 这个节点的迭代器嘛!

    在这里插入图片描述

    显然,当右子树为空的时候,父节点不一定是当前位置的迭代器加加之后的结果。只有当 当前节点位于父节点的左侧的时候,加加的结果才是父节点对应的迭代器。如果当前节点位于父节点的右侧,那么就要向上跟新 cur(当前节点) 和 parent(父节点) 直到 cur(当前节点) 位于 parent(父节点) 的左侧,此时的父节点就是加加之后的结果。

    Self& operator++()
    {
        if (_node->_right)
        {
            // 右树的最左节点(最小节点)
            Node* subLeft = _node->_right;
            while (subLeft->_left)
            {
                subLeft = subLeft->_left;
            }
    
            _node = subLeft;
        }
        else
        {
            Node* cur = _node;
            Node* parent = cur->_parent;
            // 找孩子是父亲左的那个祖先节点,就是下一个要访问的节点
            while (parent && cur == parent->_right)
            {
                cur = cur->_parent;
                parent = parent->_parent;	
            }
    
            _node = parent;
        }
    
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    self& operator–()

    减减的逻辑和加加的逻辑差不多!减减就是先判断左子树是不是为 nullptr,如果不为空那么就找到左子树的最右节点(最大节点)。要是当前节点的左子树为空:如果当前节点位于父节点的右侧,那么父节点就是减减之后的结果;如果当前节点位于父节点的左侧,那么就需要向上更新 cur(当前节点) 和 parent(父节点) 直到 cur 位于 parent 的右侧,此时 parent 对应位置的迭代器就是减减之后的结果。

    Self& operator--()
    {
        if (_node->_left) //如果左子树不为空,找到左子树的最大节点(最右节点)
        {
            Node* subRight = _node->_left;
            while (subRight->_right)
            {
                subRight = subRight->_right;
            }
    
            _node = subRight;
        }
        else
        {
            // 当前节点的左子树为空
            Node* cur = _node;
            Node* parent = cur->_parent;
            //找到cur 位于 parent 右侧的那个节点,这个 parent 就是减减之后的结果
            while (parent && cur == parent->_left)
            {
                cur = cur->_parent;
                parent = parent->_parent;
            }
            _node = parent;
        }
    
        return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    红黑树的 begin()

    begin 返回的迭代器就是整颗红黑树中最小的那个节点,也就是整颗红黑树中最左侧的那个节点。

    iterator begin()
    {
        Node* cur = _root;
        while(cur && cur->_left)
        {
            cur = cur->_left;
        }
        return cur; //单参数的构造函数支持隐士类型转化
    }
    
    //这是 const 的版本
    const_iterator end() const
    {
        return nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    红黑树的 end()

    end 返回的迭代器可以是用 nullptr 构造出来的迭代器。

    iterator end()
    {
        return nullptr;
    }
    
    const_iterator end() const
    {
        return nullptr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    set 的 begin 和 end

    还记得我们在 set 的使用部分讲的,set 中的元素为什么不能被修改嘛。因为无论是普通的迭代器还是 const 迭代器,本质上都是红黑树的 const 迭代器,所以通过迭代器来修改 set 中的值是不现实的。根据这个逻辑,我们自己就能实现 set 的 begin 和 end 啦。

    iterator begin()
    {
        return _t.begin();
    }
    
    iterator end()
    {
        return _t.end();
    }
    
    const_iterator begin() const
    {
        return _t.begin();
    }
    
    const_iterator end() const
    {
        return _t.end();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    因为在 set 中无论是 iterator 还是 const_iterator 实际上都是 const_iterator,所以不加 const 版本的 begin()end() 可以不写的。

    红黑树的 pair Insert(const T& data)

    我们之前实现的红黑树 insert 的返回值都是 bool 类型的,我们需要在每一个返回的位置都进行处理。

    第一个要处理的位置:寻找插入节点的比较方式。之前的比较是已经确定了插入的数据是一个 pair,因此用插入数据的 first 和 节点存储的 pair 的 first 比较就可以确定插入位置。

    在这里插入图片描述

    然而,为了同时适配 map 和 set 我们的存储类型已经变成了一个模板参数 T。对于 set,红黑树节点存储 的数据类型 T 就是实例化 set 是传入的那个一个类型;对于 map,红黑树节点存储的数据类型就是一个 pair,因此在比较的时候就没有办法做到统一,怎么解决呢?

    答案就是仿函数!!!

    我们可以给 RBTree 增加一个模板参数 KeyOfT,来获取到不同 T 对应的 key 值。你可能会说 RBTree 不是有一个 K 的模板参数嘛,能直接用到这里嘛,当然是不能的哦!K 仅仅是一个类型,不是节点存储的数据哦!

    在这里插入图片描述

    我们来看看 set 怎么实例化红黑树的:

    template<class K>
    class set
    {
        struct SetKeyOfT
        {
            const K& operator()(const K& key)
            {
                return key;
            }
        };
    public:
    
    private:
        RBTree<K, K, SetKeyOfT> _t;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    调用 operator() 的时候,根据如果传入的是 K 类型的数据,直接返回传入的 key 就行啦。但是如果传入的是 pair 类型的数据,那么就需要返回传入数据的 first 这才是我们比较需要的 Key 值。这个处理逻辑就是 map 的啦!

    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<const K, V>, MapKeyOfT> _t;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    处理之后的结果就是这样啦,接下来我们只需要在 set 的实现中复用红黑树的 Insert 接口就行啦。

    template<class K, class T, class KeyOfT>
    class RBTree
    {
    	typedef RBTreeNode<T> Node;
    public:
    	typedef __TreeIterator<T, T&, T*> iterator;
    	typedef __TreeIterator<T, const T&, const T*> const_iterator;
    
    public:
    	
    	iterator begin()
    	{
    		Node* cur = _root;
    		while(cur && cur->_left)
    		{
    			cur = cur->_left;
    		}
    		return cur; //单参数的构造函数支持隐士类型转化
    	}
    
    	const_iterator begin() const
    	{
    		Node* cur = _root;
    		while(cur && cur->_left)
    		{
    			cur = cur->_left;
    		}
    		return cur; //单参数的构造函数支持隐士类型转化
    	}
    
    	iterator end()
    	{
    		return nullptr;
    	}
    
    	const_iterator end() const
    	{
    		return nullptr;
    	}
    
    	
    	pair<iterator, bool> Insert(const T& data)
    	{
    		//插入的时候如果根节点为 nullptr, 申请一个节点,将该节点的颜色改为黑色之后作为根节点就行啦
    		if (_root == nullptr)
    		{
    			_root = new Node(data);
    			_root->_col = BLACK;
    			return make_pair(iterator(_root), true);
    		}
    		//记录父节点
    		Node* parent = nullptr;
    		Node* cur = _root;
    		KeyOfT kot; //实例化一个对象用来调用 operator()
    		while (cur) //找到新节点的插入位置
    		{
    			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 make_pair(iterator(cur), false);
    			}
    		}
    		//申请新的节点
    		cur = new Node(data);
    
    		Node* newNode = cur; //记录一下新插入的节点,方便构造 
    
    		//解耦操作
    		cur->_col = RED;
    		//确定新的节点插入到那个位置,左孩子还是右孩子
    		if (kot(parent->_data) < kot(data))
    		{
    			parent->_right = cur;
    		}
    		else
    		{
    			parent->_left = cur;
    		}
    		//向上链接父节点
    		cur->_parent = parent;
    
    		//调整节点的颜色,使之满足红黑树的性质,只有 parent 的颜色为红才会调整节点的颜色,parent 为黑就直接完成插入了嘛
    		while (parent && parent->_col == RED)
    		{
    			//祖父节点
    			Node* grandfather = parent->_parent;
    			//这里根据父节点在祖父节点的位置进行分类,因为我们要确定 uncle 的位置嘛,这样分类代码比较简洁
    			//parent 位于 grandparent 的左侧
    			if (parent == grandfather->_left)
    			{
    				Node* uncle = grandfather->_right;
    				// uncle 存在且为红
    				if (uncle && uncle->_col == RED)
    				{
    					// 变色
    					parent->_col = uncle->_col = BLACK;
    					grandfather->_col = RED;
    
    					// 继续向上处理
    					cur = grandfather;
    					parent = cur->_parent;
    				}
    				else // uncle 不存在 或 uncle 存在且为黑
    				{
    					if (cur == parent->_left)
    					{
    						//     g
    						//   p
    						// c
    						//右单旋
    						RotateR(grandfather);
    						parent->_col = BLACK;
    						grandfather->_col = RED;
    					}
    					else
    					{
    						//     g
    						//   p
    						//		c
    						//左右双旋
    						RotateL(parent);
    						RotateR(grandfather);
    
    						cur->_col = BLACK;
    						grandfather->_col = RED;
    					}
    
    					break; //一旦经过旋转调整颜色之后就一定满足红黑树的性质了,直接结束循环
    				}
    			}
    			else // parent == grandfather->_right
    			{
    				//找到叔叔节点
    				Node* uncle = grandfather->_left;
    				// uncle 存在且为红
    				if (uncle && uncle->_col == RED)
    				{
    					// 变色
    					parent->_col = uncle->_col = BLACK;
    					grandfather->_col = RED;
    
    					// 继续向上处理
    					cur = grandfather;
    					parent = cur->_parent;
    				}
    				else
    				{
    					if (cur == parent->_right)
    					{
    						// g
    						//	  p
    						//       c
    						//左单旋
    						RotateL(grandfather);
    						grandfather->_col = RED;
    						parent->_col = BLACK;
    					}
    					else
    					{
    						// g
    						//	  p
    						// c
    						//右左双旭那
    						RotateR(parent);
    						RotateL(grandfather);
    						//调整颜色
    						cur->_col = BLACK;
    						grandfather->_col = RED;
    					}
    
    					break; //一旦经过旋转调整颜色之后就一定满足红黑树的性质了,直接结束循环
    				}
    			}
    		}
    
    		_root->_col = BLACK; // 无论根节点的颜色是否在调整的过程中变成了红色,最后我们都将根节点的颜色变为黑色,方便写代码
    
    		return make_pair(iterator(newNode), true);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189

    set 的 insert()

    你可能会说,set 里面的 insert 直接复用红黑树里面的 insert 不就行了吗?我们直接来试试:

    在这里插入图片描述

    我们发现无法编译通过,说什么无法转化。这是什么原因呢?其实就是 set 的 iterator 有问题。因为 set 的 iterator 就是 const_iterator 调用红黑树里面的 insert 接口,返回的是一个普通的迭代器,而 set 中的insert 返回的实际上是一个 const_iterator。虽然红黑树中的 iterator 与 const_iterator 来自与同一个类模板,但是因为传入模板参数的不同,压根就是两个不同的类型,不能转化实属正常。

    你会怎么解决这个问题呢?可以将红黑树中的 insert 的返回值改成 const_iterator 嘛?这么改的确解决了 set 的问题。map 怎么办?要是 map 调用红黑树中的 insert 得到的就是个 const_iterator,但是 map 需要的是 iterator 哇!这种办法行不通。

    看来就只能提供一个 iterator 到 const_iterator 的转换函数啦!怎么实现呢?

    template<class T, class Ref, class Ptr>
    struct __TreeIterator
    {
    	typedef RBTreeNode<T> Node;
    	typedef __TreeIterator<T, Ref, Ptr> self;
    	__TreeIterator(Node* node)
    		:_node(node)
    	{}
    
    	typedef __TreeIterator<T, T&, T*> iterator;
    
    	__TreeIterator(const iterator& it)
    		:_node(it._node)
    	{}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    我们增加了一个看上去很像拷贝构造的构造函数。__TreeIterator 中的 iterator 模板参数的类型是 __TreeIterator。说明这个 iterator 无论模板参数 RefPtr 传入什么都是非 const 的iterator。当 RefPtr 分别传入 T&T* ,那么 __TreeIterator(const iterator& it) 就是一个拷贝构造函数,因为 iterator 与类实例化出来的对象时一个类型嘛;当 RefPtr 分别传入 const T&const T* ,那么 __TreeIterator(const iterator& it) 就是一个将非 const 的迭代器转化为 const 迭代器的构造函数。是不是相当美妙。

    通过添加这个看上去十分像拷贝构造的函数,就可以实现同时适配 map 和 set 啦!
    在这里插入图片描述

    测试通过,set 模拟实现完成。

    map 的模拟实现

    在 set 的模拟实现部分,我们已经将红黑树改成同时适配 map 和 set 的结构了,因此 map 的实现直接调用红黑树对应的接口就好啦!

    V& operator[]

    map 的使用部分浅浅的讲过。opertor[] 就是先调用 insert 函数,如果说插入成功返回插入成功的节点对应的 value 值就可以;如果插入失败,返回与新插入节点 key 值相同的那个节点对应的 value 值就行。这恰好根 insert 的返回值对应上了,我们只需要返回 insert 的返回值中 first 迭代器对应节点的 value 值就大功告成了。

    V& operator[](const K& key)
    {
        pair<iterator, bool> ret = insert(make_pair(key, V()));
        return ret.first->second;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    map 的代码

    #pragma once
    #include"RBTree.h"
    
    namespace Tchey
    {
        template<class K, class V>
        class map
        {
            struct MapKeyOfT
            {
                const K& operator()(const pair<K, V>& kv)
                {
                    return kv.first;
                }
            };
        
        typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
        typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;
    
        public:
            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(make_pair(key, V()));
                return ret.first->second;
            }
    
        private:
            RBTree<K, pair<const K, V>, MapKeyOfT> _t;
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    测试:

    在这里插入图片描述

    到这里 map 和 set 的模拟实现就完成啦,还是比较有意思的,对吧。​😜

    在这里插入图片描述

  • 相关阅读:
    TARJAN复习 求强连通分量、割点、桥
    Guava Cache介绍-面试用
    Spark基础【RDD分区和并行度】
    STM32实战总结:HAL之电机
    【雕爷学编程】Arduino动手做(106)---US026超声波测距
    第一章:Class文件结构
    Python如何用Numba加速科学计算和数据分析
    AutoCAD Electrical 2022—项目特性
    有关javascript中事件对象e
    软考证书具体用途--详细介绍
  • 原文地址:https://blog.csdn.net/m0_73096566/article/details/134282262