C++STL详解(九)--使用红黑树封装实现set和map

控制底层红黑树模板参数

如果我们用一棵KV模型的红黑树同时实现map和set,我们就需要控制map和set中的所传入红黑树中的模板参数,其中,封装过程中,为了与红黑树的模板参数区分,我们将红黑树中模板参数由V改成T.

template <class K, class T>
struct RBTree
{
   
   
   // 
};

对于T来说,如果是set容器,那么T实例化出来的便是Key

namespace mySet
{
   
   
    template<class K>
    class set
    {
   
   
    public:
        //
    private:
        RBTree<K, K>> _t;
    };

}

如果是map容器,那么T实例化出来的便是pair<K,V>键值对.

namespace myMap
{
   
   
    template<class K,class V>
    class map
    {
   
   
    public:
        //
    private:
        RBTree<K, pair<K, V>> _t;
    };
}

T实例化例图如下:
在这里插入图片描述

那么,在红黑树中我们可不可以只保留一个模板参数T,根据实参类型转换为所需要的存储类型呢?

不可以,之所以还需要第一个模板参数K,就是因为STL中map所提供的find和erase接口需要key来指定删除或者查找的结点.

模板参数中的仿函数

在插入中,结点中存储的类型T可能为Set容器中的key,也有可能是Map中的pair<K,V>键值对,底层红黑树如何针对不同类型的容器进行比较呢?

我们所需要的是:
如果是set容器,那么T就是类型key,我们需要取数据类型为T的data就行.

如果是map容器,那么T就为键值对pair<K,V>,我们需要取键值对pair中的first与所给值key比较.

所以,综合以上情况,我们需要在上层set,map中添加一个仿函数,根据实参所传的不同类型进而获取不同数据类型来比较两个结点.

例如:
以下为set,map容器分别传入一个仿函数,在比较时会更具容器类型不同进而调用所需要的仿函数.
set仿函数:

namespace mySet
{
   
   
    template<class K>
    class set
    {
   
   
    public:
        struct SetKeyOfT
        {
   
   
            const K& operator()(const K& key )
            {
   
   
                return key;
            }
        };
    private:
        RBTree<K, K, SetKeyOfT> _t;
    };
    }

map仿函数:

namespace myMap
{
   
   
    template<class K,class V>
    class map
    {
   
   
    public:
        struct MapKeyOfT
        {
   
   
            const K& operator()(const pair<K, V>& kv)
            {
   
   
                return kv.first;
            }
        };
    private:
        RBTree<K, pair<K, V>,MapKeyOfT> _t;
    };
    }

所以,当容器为set时,红黑树中的仿函数就实例化为set容器中的仿函数,当容器为map时,红黑树中的仿函数就实例化为map中的仿函数.

在这里插入图片描述
进而,当我们通过上层容器中的Insert函数调用底层红黑树中的Insert函数时,在比较时就会根据存储类型T,分别调用所需要的仿函数进行结点的比较.
例如:
以下是底层红黑树Insert函数仿函数调用插入新结点部分代码:

bool Insert(const T& data )            //插入函数
	{
   
   
		KeyOfT kot;
		
		if (_root == nullptr) //如果为空树,直接插入结点就可以了.
		{
   
   
			_root = new Node(data);
			cout << kot(_root->_data);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)                          //寻找插入位置
		{
   
   
			if (kot(data) <  kot(cur->_data))
			{
   
   
				parent = cur;
				cur = cur->_right;
			}
			else if ( kot( cur->_data) <  kot(data))
			{
   
   
				parent = cur;
				cur = cur->_left;
			}
			else
			{
   
   
				return false;
			}
		}
		cur = new Node(data);               //找到插入位置并插入.
		//......
		}

map,set中的正向迭代器

map和set中的迭代器实际上就是对指向结点的指针进行封装,正向迭代器中一个内置成员.

其中Ref,Ptr分别代表目标数据类型的引用,指针类型.

template <class T,class Ref,class Ptr>
struct _RBTreeIterator
{
   
   
	typedef RBTreeNode<T> Node;       //重新定义红黑树结点类型为Node
	typedef _RBTreeIterator<T, Ref, Ptr> Self; //重新定义迭代器类型为Self
	Node* _node;
}

正向迭代器的构造:

_RBTreeIterator(Node* node = nullptr)
		:_node(node)
	{
   
   }

正向迭代器的解引用运算符重载:
当我们使用正向迭代器的解引用运算符重载时,直接返回目标数据的引用.

    Ref operator*()
	{
   
   
		return _node->_data;
	}

正向迭代器的->运算符重载
当我们使用正向迭代器的->运算符重载,直接返回目标数据的地址.

Ptr operator->()
	{
   
   
		return &_node->_data;
	}

正向迭代器还包括了==,!=运算符重载,我们直接判断两个迭代器中的结点指针是否相同即可.

bool operator!=(const Self& s) const //普通对象和const对象都可以进行调用.
	{
   
   
		return _node != s._node;
	}
	bool operator==(const Self& s) const 
	{
   
   
		return _node == s._node;
	}

前置++运算符重载:
前置++运算重载是按照中序遍历进行访问的,主要有两个步骤
1: 如果it所指的结点中右子树存在,则访问右子树的最左结点.

2: 如果it所指的结点中右子树不存在,则寻找孩子不是父亲右的那个祖先,并对其进行访问.

在这里插入图片描述

代码如下:

Self& operator++() //前置++,返回的是自己.
	{
   
   
		if (_node->_right) //右子树存在,访问的是右子树的最左节点.
		{
   
   
			Node* left = _node->_right;
			while ( left->_left)
			{
   
   
				left = left->_left;
			}
			_node = left;
		}
		else     //右子树不存在,寻找孩子不是父亲右的那个祖先.
		{
   
   
			Node* parent = _node->_parent;
			Node* cur = _node;
			while ( parent && cur == parent->_right)
			{
   
   
				cur = cur->_parent;
				parent = parent->_parent;
			}
			_node = parent;
		}
		return *this;  //返回迭代器.
	}

前置–运算符重载:
前置–运算重载是按照中序遍历进行访问的,主要有两个步骤:
1: 如果it所指的结点中左子树存在,则访问左子树的最右结点.

2: 如果it所指的结点中左子树不存在,则寻找孩子不是父亲左的那个祖先,并对其进行访问.
在这里插入图片描述
当正向迭代器实现后,我们可以在上层对该迭代器进一步封装.可以在底层红黑树的public区域中重新定义迭代器的类型为iterator,并且封装两个函数:
1: begin函数中迭代器指向底层红黑树的最左结点.

2: end函数中原本返回的是最后一个数据的下一个地址,如果有哨兵位则返回哨兵位,为了方便,我们采用的是返回nullptr构造的迭代器.(但是这样在it从end–遍历时会访问空指针造成程序崩溃Bug)

begin函数和end函数代码如下:

	iterator begin()          //指向的是底层红黑树的最左结点.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清欢 Allen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值