C++——STL——封装红黑树实现mymap与myset

#新星杯·14天创作挑战营·第11期#

目录

1.map与set的底层框架以及源码剖析

1.1 源码以及框架分析

2.模拟实现map 与 set

2.1 实现出复用红黑树的框架,并支持insert

2.2 支持iterator的实现

2.2.1 iterator核心源码

2.2.2 iterator 实现思路分析:

2.2.2.1 operator()++:

2.2.2.2 operator()--:

 2.2.3 迭代器不可修改问题

2.2.4 map的[]

2.2.5 总代码以及一些注意事项 


1.map与set的底层框架以及源码剖析

咱们之前学过map与set,multimap与multiset的使用了,那么咱们今天来学习如何使用红黑树来实现map与set的底层原理。

1.1 源码以及框架分析

SGI-STL30版本源代码,map和set的源代码在map/set/stl_map.h/stl_set.h/stl_tree.h等几个头文件 中。

OK,咱们先来看map与set实现结构框架的核心部分吧:

// set
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_set.h>
#include <stl_multiset.h>
// map
#ifndef __SGI_STL_INTERNAL_TREE_H
#include <stl_tree.h>
#endif
#include <stl_map.h>
#include <stl_multimap.h>
// stl_set.h
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
	// typedefs:
	typedef Key key_type;
	typedef Key value_type;
private:
	typedef rb_tree<key_type, value_type,
		identity<value_type>, key_compare, Alloc> rep_type;
	rep_type t;  // red-black tree representing set
};
// stl_map.h
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
	// typedefs:
	typedef Key key_type;
	typedef T mapped_type;
	typedef pair<const Key, T> value_type;
private:
	typedef rb_tree<key_type, value_type,
		select1st<value_type>, key_compare, Alloc> rep_type;
	rep_type t;  // red-black tree representing map
};
// stl_tree.h
struct __rb_tree_node_base
{
	typedef __rb_tree_color_type color_type;
	typedef __rb_tree_node_base* base_ptr;
	color_type color;
	base_ptr parent;
	base_ptr left;
	base_ptr right;
};
// stl_tree.h
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc
	= alloc>
class rb_tree {
protected:
	typedef void* void_pointer;
	typedef __rb_tree_node_base* base_ptr;
	typedef __rb_tree_node<Value> rb_tree_node;
	typedef rb_tree_node* link_type;
	typedef Key key_type;
	typedef Value value_type;
public:
	// insert⽤的是第⼆个模板参数左形参

	pair<iterator, bool> insert_unique(const value_type& x);
	// erase和find⽤第⼀个模板参数做形参

	size_type erase(const key_type& x);
	iterator find(const key_type& x);
protected:
	size_type node_count; // keeps track of size of tree
	link_type header;
};
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
	typedef __rb_tree_node<Value>* link_type;
	Value value_field;
};

ok,接下来,咱们就来一点一点的进行分析:

先来看set底层源码,可以看出,这个set底层是有两个key的,但是这两个key的作用不同,这也就是为什么不只用一个key,咱们待会讲他们的作用分别是什么。

这事map的底层源码,需要注意的是,这里的第二个模板参数是pair类型,第一个模板参数同样也是key,从这也可以看出,第一个模板参数的功能应该相同。

好了,到了重点的地方了,咱们之前的map与set不是都有两个模板参数嘛?这里第二个模板参数决定了你底层的node节点应该用什么数据类型。 

画出整体的图就是这样的:

这样,就可以实现用一个tree模板,同时实现map与set这两个分别要传什么数据。

1.通过对框架的分析,我们可以看到源码中rb_tree用了⼀个巧妙的泛型思想实现,rb_tree是实 现key的搜索场景,还是key/value的搜索场景不是直接写死的,而是由第二个模板参数Value决定 _rb_tree_node中存储的数据类型。

2. set实例化rb_tree时第⼆个模板参数给的是key,map实例化rb_tree时第二个模板参数给的是 pair,这样一颗红黑树既可以实现key搜索场景的set,也可以实现key/value搜索场 景的map。

3. 要注意⼀下,源码里面模板参数是用T代表value,而内部写的value_type不是我们我们日常 key/value场景中说的value,源码中的value_type反而是红黑树结点中存储的真实的数据的类型。

那么第一个模板参数功能相同,那么具体是什么功能呢?

rb_tree第二个模板参数Value已经控制了红黑树结点中存储的数据类型,为什么还要传第一个模板 参数Key呢?尤其是set,两个模板参数是⼀样的,这是很多博友的⼀个疑问。要注意的是对于 map和set,find/erase时的函数参数都是Key,所以第⼀个模板参数是传给find/erase等函数做形 参的类型的。对于set而言两个参数是⼀样的,但是对于map而言就完全不⼀样了,map insert的 是pair对象,但是find和ease的是Key对象。(所以第一个模板参数主要是为了给find/erase,做参数准备的)

2.模拟实现map 与 set

2.1 实现出复用红黑树的框架,并支持insert

参考源码框架,map和set复用之前我们实现的红黑树

我们这里相比源码调整⼀下,key参数就用K,value参数就用V,红黑树中的数据类型,我们使用 T。

好,那么在这里,有一个问题,就是但凡涉及到比较大小的问题,比如插入时候,需要比较大小吧,比如插入的数值比当前数值大,那么我要往右子树移动,等等。咱们前面还说了,map与set底层的数据是依靠第二个模板参数来实现的,但是!第二个模板参数,map的是pair类型的呀,咱们知道,pair类型的比较大小,是first(key)要比较,second(value)也要比较,就是要比较两个。但是,咱们的insert比较,只需要比较一个即可,我们需要时的任 何时候只比较key,所以这里,咱们看源码中,是不是有一个KeyOfValue呀,就是构造一个仿函数,可以用这个仿函数取出pair类型中的first。所以我们在map和set层分别实现⼀个MapKeyOfT和SetKeyOfT的仿函数传给 RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进行比较。

口头上光说,可能有点晦涩难懂,所以,咱们上代码:

//mymap.h
template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		//map 和 set 分别提供不同的仿函数(如 _KeyOfValue),用于从节点中提取键值:
		//map 提取 pair::first。
		//set 直接返回节点值本身。
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
     bool insert(const pair<K, V>& kv)
         {
             return _t.Insert(kv);
         }
private:
	// pair可以修改,pair的K类型用const修饰,保证key不能被修改,但是value是可以被修改的
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};
//myset.h
template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
	// 第一个模板参数带const,保证key不能被修改
	RBTree<K, const K, SetKeyOfT> _t;
};
//RBTree.h
// red black
template<class T>
struct RBTreeNode
{
	//这里的T就相当于是红黑树中的数据类型,即源码中对应的value
	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)
		, _col(RED)
	{}
};
template<class K, class T, class KeyOfT>
 class RBTree
 {
 private:
    typedef RBTreeNode<T> Node;
    Node* _root = nullptr;
    
public:
    bool Insert(const T& data)
    {
        if (_root == nullptr)
        {
                _root = new Node(data);
                _root->_col = BLACK;
                return true;
        }
        KeyOfT kot;
        Node* parent = nullptr;
        Node* cur = _root;
        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 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;
        //...
        return true;
    }
 }

这里的插入代码,咱们只放出来一部分,因为重点不在插入代码,而是在构造仿函数。在RBTree.h中,可以看出使用仿函数提取pair类型中的first数据了。因为_data为T实例化出的对象,也就是说_data就是pair类型,所以可以使用kot(cur->_data),直接获取里面的key。

OK,这个问题解决了,咱们来看下一个:

2.2 支持iterator的实现

2.2.1 iterator核心源码

struct __rb_tree_base_iterator
 {
  typedef __rb_tree_node_base::base_ptr base_ptr;
  base_ptr node;
  void increment()
  {
    if (node->right != 0) {
      node = node->right;
      while (node->left != 0)
        node = node->left;
    }
    else {
      base_ptr y = node->parent;
      while (node == y->right) {
        node = y;
        y = y->parent;
      }
      if (node->right != y)
        node = y;
    }
  }
  void decrement()
  {
     if (node->color == __rb_tree_red &&node->parent->parent == node)
             node = node->right;
     else if (node->left != 0) 
    {
         base_ptr y = node->left;
         while (y->right != 0)
         y = y->right;
         node = y;
     }
     else 
    {
         base_ptr y = node->parent;
         while (node == y->left) 
            {
                 node = y;
                 y = y->parent;
             }
         node = y;
     }
 }
 };
 template <class Value, class Ref, class Ptr>
 struct __rb_tree_iterator : public __rb_tree_base_iterator
 {
 typedef Value value_type;
 typedef Ref reference;
 typedef Ptr pointer;
 typedef __rb_tree_iterator<Value, Value&, Value*>             
    __rb_tree_iterator() {}
 __rb_tree_iterator(link_type x) { node = x; }
 __rb_tree_iterator(const iterator& it) { node = it.node; }
 iterator;
 reference operator*() const { return link_type(node)->value_field; }
 #ifndef __SGI_STL_NO_ARROW_OPERATOR
 pointer operator->() const { return &(operator*()); }
 #endif /* __SGI_STL_NO_ARROW_OPERATOR */
 self& operator++() { increment(); return *this; }
 self& operator--() { decrement(); return *this; }
 inline bool operator==(const __rb_tree_base_iterator& x,
 const __rb_tree_base_iterator& y) {
 return x.node == y.node;
 }
 inline bool operator!=(const __rb_tree_base_iterator& x,
 const __rb_tree_base_iterator& y) {
 return x.node != y.node;
}

2.2.2 iterator 实现思路分析:

1.iterator实现的大框架跟list的iterator思路是⼀致的,用一个类型封装结点的指针,再通过重载运算 符实现,迭代器像指针一样访问的行为。

首先,我要声明一下的是,map与set迭代器是双向迭代器,只支持++,--,不支持+,-。所以说咱们在实现iterator的时候,直接实现++,--,就可以了。

2.这里的难点是operator++和operator--的实现。之前使用部分,我们分析了,map和set的迭代器走的是中序遍历,左子树->根结点->右子树那么begin()会返回中序第一个结点的iterator也就是10 所在结点的迭代器。

中序第一个节点,肯定就是根据左根右,是最左节点喽。 

Node* minLeft = _root;
while (minLeft && minLeft->_left)
{
	minLeft = minLeft->_left;
}

上面的代码是寻找begin()节点的代码。 

3.迭代器++的核心逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下一个结点。

2.2.2.1 operator()++:

对于重载++,其实根据左-根-右,就可以推断出,一个节点的下一个节点是哪个节点,因为这都是类似的。但是在这里,我还是将方法总结一下吧。

【1】如果当前结点是父亲的左,根据中序左子树->根结点->右子树,那么下一个访问的结点就是当前结 点的父亲;如下图:it指向25,25右为空,25是30的左,所以下一个访问的结点就是30。

这个符合左根右的规则吧,那么到了30呢?下一步应该怎么去做呢?

【2】迭代器++时,如果it指向的结点的右子树不为空,代表当前结点已经访问完了,要访问下一个结点 是右子树的中序第⼀个,一棵树中序第⼀个是最左结点,所以直接找右子树的最左结点即可。 

还是上面那个图,此时的it指向的是30,那么30的右子树不是空,那也就是说,咱们下一步就是要去找那棵不为空的右子树的中序第一个节点(最左节点),就是35吧。

【3】迭代器++时,如果it指向的结点的右子树空,代表当前结点已经访问完了且当前结点所在的子树也 访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上 找。(那么具体怎么找,找什么?下面马上就会讲了)

【4】如果当前结点是父亲的右,根据中序左子树->根结点->右子树,当前当前结点所在的子树访问完 了,当前结点所在父亲的子树也访问完了,那么下一个访问的需要继续往根的祖先中去找,直到找 到孩子是父亲左的那个祖先就是中序要问题的下⼀个结点(如果发现孩子是父亲的右,不要着急,将孩子移到父亲的位置,父亲移动祖先的位置,继续寻找,直到找到为止)。如下图:it指向15,15右为空,15是10 的右,15所在子树话访问完了,10所在子树也访问完了,继续往上找,10是18的左,那么下⼀个 访问的结点就是18。

多说无意,咱们还是来看代码吧:


		if (_node->_right)
		{
			// 1、当前节点的右不为空
			// 下一个就是右子树中序第一个(最左节点)
			Node* minLeft = _node->_right;
			while (minLeft->_left)
			{
				minLeft = minLeft->_left;
			}

			_node = minLeft;
		}
		else
		{
			// 2、当前节点的右为空
			// 下一个是当前节点的祖先,孩子是父亲左那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			// parent为空,cur是根,说明当前树走完了,
			// nullptr给_node,nullptr做end()
			//并且这个地方需要的是孩子是父亲的左,所以说,
			//如果遇到孩子是父亲的右的,进入循环,继续寻找
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

与代码相关的一些注意事项,我也都写在了代码的旁边。

 好,那么下面来思考另外一个问题,如果it已经走到了整个二叉树的最右节点,那么下一步是不是要走到end()了,但是二叉树的end()该如何表示呢?

end()如何表示呢?如上图:当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18 到根没有父亲,没有找到孩子是父亲左的那个祖先,这是父亲为空了,那我们就把it中的结点指针 置为nullptr,我们用nullptr去充当end

需要注意的是stl源码空,红黑树增加了⼀个哨兵位头结点 做为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点。

相比我们用 nullptr作为end(),差别不大,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处 理⼀下,让迭代器结点指向最右结点

 其实用不用header去实现,差别不大,那么header的作用是什么呢?

   header 的作用与设计原理
    结构设计
    header 是一个辅助节点,不存储实际数据,其成员含义:

    parent:指向红黑树的根节点。

    left:指向树的最小节点(最左叶子)。

    right:指向树的最大节点(最右叶子)。

    根节点的 parent 也指向 header,形成闭环。

    边界处理:
    end() 迭代器:指向 header。

    begin() 迭代器:指向 header->left(最小节点)。

    关键行为:

    end()--(即 header--)返回 header->right(最大节点)。

    最大节点的 ++ 会走到 header(即 end())。

    最小节点的 -- 会走到 header,但通常不允许对 begin() 执行 --。

那为什么end()(也就是header)--才可以到最右节点呢?别急,你这个东西你用常规的思想是理解不了的,这个得借助咱的operator()--(重载--)来实现。

2.2.2.2 operator()--:

减减的逻辑与++的逻辑基本是相反的,++的情况是以右子树为空进行分类,那么--,就是以左子树为空进行分类:

1.左子树不为空,那么就进入左子树,去寻找左子树中的最右节点。之后按照右-根-左的顺序进行减减。

2.左子树为空,说明这个局部的二叉树已经减完了(因为左是减减的最后一个部分)。那么就需要往祖先里面去寻找要减的东西了。就去找当前孩子节点位于父亲节点的右的那个祖先节点。

还是以这幅图为例子,那么it从50,先减减到35,之后发现35的左侧没有子树了,这个时候,就需要向上进行回溯。找孩子节点位于父亲的右的那个祖先,嗷,找到了,是30,那么35--后的下一个节点就是30.

OK,咱们来看代码:

//从根节点 _root 出发,不断向右子树遍历,直到找到最右侧节点(最大值节点)。
//将 _node 设为该节点,完成从 end() 到最后一个元素的移动。
if (_node == nullptr)//这个为什么要判断多余的一步呢?
	//其实也不是多余,因为你如果没有这一句判断
	//那么你的end()指向的是空,则_node也是空,
	// 访问 _node->_left 会导致 空指针解引用,引发未定义行为(如程序崩溃),
	//这不铁铁的坑了吗?
{
	// --end(),到最右节点
	//这一句判断代码,才是从end()到最右节点的关键代码。
	Node* maxRight = _root;
	while (maxRight && maxRight->_right)
	{
		maxRight = maxRight->_right;
	}

	_node = maxRight;
}
//在红黑树(或标准容器)中,end() 通常指向容器末尾的“超尾”位置(如 nullptr)
// ,表示一个无效的哨兵位置。
//当用户对 end() 迭代器执行 -- 操作时,预期是获取容器中的最后一个有效元素。
else if (_node->_left)
{
	// 左子树不为空,中序左子树最后一个
	Node* rightMost = _node->_left;
	while (rightMost->_right)
	{
		rightMost = rightMost->_right;
	}
	_node = rightMost;
}
else
{	//左为空,这棵树的局部那一部分已经减完了。
	// 孩子是父亲右的那个祖先
	Node* cur = _node;
	Node* parent = cur->_parent;
	while (parent && cur == parent->_left)
	{
		cur = parent;
		parent = cur->_parent;
	}
	_node = parent;
}

代码里面的一些注意事项,我都有注释在旁边进行讲解。

 2.2.3 迭代器不可修改问题

咱们知道set的迭代器是不可以修改的,所以,我们把set的第二个模板参数改成constK即可, RBTree<k,const K , SetKeyOfT> _t;

而map的iterator不支持修改key但是可以修改value,我们把map的第二个模板参数pair的第一个参 数改成constK即可, RBTree <k,pair<const k,v>, MapKeyOfT> _t;

2.2.4 map的[]

咱们知道,map支持方括号,那么底层是由于insert,才可以实现的,而insert的返回类型是pair类型。接下来,请看代码:

//operator[]的实现是关键,这里首先尝试插入一个键为key,值默认初始化的V类型的pair。
//如果键存在,插入失败,但返回的迭代器指向已存在的元素;如果不存在,则插入新元素.
// 插入新元素后,返回的迭代器也指向这个新插入的元素
//然后返回该迭代器的second,也就是值的引用,返回新插入的节点的值。这符合标准map的operator[]的行为,
//即允许通过[]访问并可能插入新元素。这里的V()是默认构造的,例如如果是int就是0,
//如果是string就是空字符串。
V& operator[](const K& key)
{
	pair<iterator, bool> ret = _t.Insert({ key, V() });
	iterator it = ret.first;
	//it->second,而it的类型是RBTree的迭代器。当RBTree存储的是pair<const K, V>时,
	//迭代器的operator->应该返回指向该pair的指针,因此it->second应该是V& 类型,允许修改
	return it->second;
}

2.2.5 总代码以及一些注意事项 

那么到这里,基本上主要的咱们都实现了,还有一些东西,大家请看代码,里面都有我的注释,不懂的,请看注释:

//RBTree.h
enum Colour
{
	RED,
	BLACK
};

// red black
template<class T>
struct RBTreeNode
{
	//这里的T就相当于是红黑树中的数据类型,即源码中对应的value
	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)
		, _col(RED)
	{}
};
//这个地方,迭代器的结构体,只用于实现迭代器的一些内部的东西
//而对于像迭代器begin,end这些东西,不是我内部需要实现的
// 并且,这个是双向迭代器,所以只支持++,--,不支持+,-
// 这个东西,是谁用,谁就去定义就可以了
//这东西咱们之前在stl容器的实现中,也讲过
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
	Node* _root;

	//`_root`成员变量用于在迭代过程中进行回溯
	RBTreeIterator(Node* node, Node* root)
		:_node(node)
		, _root(root)
	{}

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

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

	Self& operator++()
	{
		if (_node->_right)
		{
			// 1、当前节点的右不为空
			// 下一个就是右子树中序第一个(最左节点)
			Node* minLeft = _node->_right;
			while (minLeft->_left)
			{
				minLeft = minLeft->_left;
			}

			_node = minLeft;
		}
		else
		{
			// 2、当前节点的右为空
			// 下一个是当前节点的祖先,孩子是父亲左那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			// parent为空,cur是根,说明当前树走完了,
			// nullptr给_node,nullptr做end()
			//并且这个地方需要的是孩子是父亲的左,所以说,
			//如果遇到孩子是父亲的右的,进入循环,继续寻找
			while (parent && cur == parent->_right)
			{
				cur = parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
	/*header 的作用与设计原理
		结构设计
		header 是一个辅助节点,不存储实际数据,其成员含义:

		parent:指向红黑树的根节点。

		left:指向树的最小节点(最左叶子)。

		right:指向树的最大节点(最右叶子)。

		根节点的 parent 也指向 header,形成闭环。

		边界处理:
		end() 迭代器:指向 header。

		begin() 迭代器:指向 header->left(最小节点)。

		关键行为:

		end()--(即 header--)返回 header->right(最大节点)。

		最大节点的 ++ 会走到 header(即 end())。

		最小节点的 -- 会走到 header,但通常不允许对 begin() 执行 --。*/
	Self& operator--()
	{
		//从根节点 _root 出发,不断向右子树遍历,直到找到最右侧节点(最大值节点)。
		//将 _node 设为该节点,完成从 end() 到最后一个元素的移动。
		if (_node == nullptr)//这个为什么要判断多余的一步呢?
			//其实也不是多余,因为你如果没有这一句判断
			//那么你的end()指向的是空,则_node也是空,
			// 访问 _node->_left 会导致 空指针解引用,引发未定义行为(如程序崩溃),
			//这不铁铁的坑了吗?
		{
			// --end(),到最右节点
			//这一句判断代码,才是从end()到最右节点的关键代码。
			Node* maxRight = _root;
			while (maxRight && maxRight->_right)
			{
				maxRight = maxRight->_right;
			}

			_node = maxRight;
		}
		//在红黑树(或标准容器)中,end() 通常指向容器末尾的“超尾”位置(如 nullptr)
		// ,表示一个无效的哨兵位置。
		//当用户对 end() 迭代器执行 -- 操作时,预期是获取容器中的最后一个有效元素。
		else if (_node->_left)
		{
			// 左子树不为空,中序左子树最后一个
			Node* rightMost = _node->_left;
			while (rightMost->_right)
			{
				rightMost = rightMost->_right;
			}
			_node = rightMost;
		}
		else
		{	//左为空,这棵树的局部那一部分已经减完了。
			// 孩子是父亲右的那个祖先
			Node* cur = _node;
			Node* parent = cur->_parent;
			while (parent && cur == parent->_left)
			{
				cur = parent;
				parent = cur->_parent;
			}
			_node = parent;
		}

		return *this;
	}

	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*> ConstIterator;

	Iterator Begin()
	{
		Node* minLeft = _root;
		while (minLeft && minLeft->_left)
		{
			minLeft = minLeft->_left;
		}
		//构造一个迭代器,其 _node 成员指向该最小节点,_root 成员保存树的根节点
		//咱们上面不是写了一个RBTreeIterator了嘛?这个Iterator就是他的typedef
		//不过没关系,你看准上面的RBTreeIterator的两个成员变量,一个是node,当前节点
		//一个是根节点,所以得跟上面的保持一致,才可以构造迭代器。
		return Iterator(minLeft, _root);
	}

	Iterator End()
	{
		//end() 通常指向最后一个元素的下一个位置(无效位置),用 nullptr 表示。
		//反向遍历支持:当对 end() 迭代器执行 -- 操作时,代码会根据 _root 找到最大节点
		// (见之前的 operator-- 实现)。
		return Iterator(nullptr, _root);
	}

	ConstIterator Begin() const
	{
		Node* minLeft = _root;
		while (minLeft && minLeft->_left)
		{
			minLeft = minLeft->_left;
		}

		return ConstIterator(minLeft, _root);
	}

	ConstIterator End() const
	{
		return ConstIterator(nullptr, _root);
	}

	pair<Iterator, bool> Insert(const T& data)
	{
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_col = BLACK;
			//注意看,插入是pair类型的,所以这里要用花括号,
			//多参数的隐式类型转换,要用花括号,前面的代表iterator
			//后面的代表bool类型
			//这里的iterator中两个都是_root,第二个都是_root,但是第一个表示当前节点
			return { Iterator(_root, _root), true };
		}

		KeyOfT kot;
		Node* parent = nullptr;
		Node* cur = _root;
		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
			{
				//第二个都是_root,但是第一个表示当前节点
				return { Iterator(cur, _root), 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;

		while (parent && parent->_col == RED)
		{
			Node* grandfather = parent->_parent;
			if (parent == grandfather->_left)
			{
				Node* uncle = grandfather->_right;

				// 1、u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else  // 2、u不存在或者u存在且为黑
				{
					if (cur == parent->_left)
					{
						//     g
						//  p     u
						//c
						RotateR(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//  p     u
						//     c

						RotateL(parent);
						RotateR(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}
			}
			else // (parent == grandfather->_right)
			{

				Node* uncle = grandfather->_left;

				// 1、u存在且为红
				if (uncle && uncle->_col == RED)
				{
					// 变色
					parent->_col = uncle->_col = BLACK;
					grandfather->_col = RED;

					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else  // 2、u不存在或者u存在且为黑
				{
					if (cur == parent->_right)
					{
						//     g
						//  u    p 
						//         c
						RotateL(grandfather);
						parent->_col = BLACK;
						grandfather->_col = RED;
					}
					else
					{
						//     g
						//  u     p
						//     c

						RotateR(parent);
						RotateL(grandfather);

						cur->_col = BLACK;
						grandfather->_col = RED;
					}

					break;
				}

			}
		}

		_root->_col = BLACK;
		//第二个都是_root,但是第一个表示当前节点
		return { Iterator(newNode, _root), true };
	}


	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	int Height()
	{
		return _Height(_root);
	}

	int Size()
	{
		return _Size(_root);
	}

	Iterator Find(const K& key)
	{
		KeyOfT kot;
		Node* cur = _root;
		while (cur)
		{
			if (kot(cur->_data) < key)
			{
				cur = cur->_right;
			}
			else if (kot(cur->_data) > key)
			{
				cur = cur->_left;
			}
			else
			{
				//第二个都是_root,但是第一个表示当前节点
				return Iterator(cur, _root);
			}
		}

		return End();//如果没找到,就返回end()
	}

private:
	int _Size(Node* root)
	{
		return root == nullptr ? 0 :
			_Size(root->_left) + _Size(root->_right) + 1;
	}

	int _Height(Node* root)
	{
		if (root == nullptr)
			return 0;

		int leftHeight = _Height(root->_left);
		int rightHeight = _Height(root->_right);

		return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
	}

	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		_InOrder(root->_left);
		cout << root->_kv.first << " ";
		_InOrder(root->_right);
	}

	void RotateR(Node* parent)
	{
		Node* subL = parent->_left;
		Node* subLR = subL->_right;

		parent->_left = subLR;
		if (subLR)
			subLR->_parent = parent;

		Node* ppnode = parent->_parent;

		subL->_right = parent;
		parent->_parent = subL;

		if (parent == _root)
		{
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subL;
			}
			else
			{
				ppnode->_right = subL;
			}

			subL->_parent = ppnode;
		}
	}

	void RotateL(Node* parent)
	{
		Node* subR = parent->_right;
		Node* subRL = subR->_left;

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;

		Node* ppnode = parent->_parent;

		subR->_left = parent;
		parent->_parent = subR;

		if (parent == _root)
		{
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
			if (ppnode->_left == parent)
			{
				ppnode->_left = subR;
			}
			else
			{
				ppnode->_right = subR;
			}

			subR->_parent = ppnode;
		}
	}

private:
	Node* _root = nullptr;
};

 上面是RBTree.h中的代码,也是主要的底层的框架代码。

//mymap.h
template<class K, class V>
class map
{
	struct MapKeyOfT
	{
		//map 和 set 分别提供不同的仿函数(如 _KeyOfValue),用于从节点中提取键值:
		//map 提取 pair::first。
		//set 直接返回节点值本身。
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};

public:
	
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator 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);
	}

	iterator find(const K& key)
	{
		return _t.Find(key);
	}

	//operator[]的实现是关键,这里首先尝试插入一个键为key,值默认初始化的V类型的pair。
	//如果键存在,插入失败,但返回的迭代器指向已存在的元素;如果不存在,则插入新元素.
	// 插入新元素后,返回的迭代器也指向这个新插入的元素
	//然后返回该迭代器的second,也就是值的引用,返回新插入的节点的值。这符合标准map的operator[]的行为,
	//即允许通过[]访问并可能插入新元素。这里的V()是默认构造的,例如如果是int就是0,
	//如果是string就是空字符串。
	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = _t.Insert({ key, V() });
		iterator it = ret.first;
		//it->second,而it的类型是RBTree的迭代器。当RBTree存储的是pair<const K, V>时,
		//迭代器的operator->应该返回指向该pair的指针,因此it->second应该是V& 类型,允许修改
		return it->second;
	}

private:
	// pair可以修改,pair的K类型用const修饰,保证key不能被修改,但是value是可以被修改的
	RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

以上是mymap.h中的代码,即根据最底层的东西实现map。

template<class K>
class set
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	//这里使用了typename关键字来告诉编译器RBTree<K, const K, SetKeyOfT>::Iterator是一个类型
	//,而不是静态成员变量或者其他。
	typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
	typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

	iterator begin()
	{
		return _t.Begin();
	}

	iterator end()
	{
		return _t.End();
	}

	const_iterator begin() const
	{
		//这个地方代表_t这个成员变量的类型是const的,即const RBTree<K, const K, SetKeyOfT>
		//_t是const的,所以会调用const版本的Begin()
		//正好,RBTree中也有const版本的Begin()
		return _t.Begin();
	}

	const_iterator end() const
	{
		return _t.End();
	}

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

	iterator find(const K& key)
	{
		return _t.Find(key);
	}

private:
	// 第一个模板参数带const,保证key不能被修改
	RBTree<K, const K, SetKeyOfT> _t;
};

这个是myset.h,是根据最底层的来实现set。 

 OK,这一章代码可能比较多,但我要说的话都结合着代码,因为这样效果更佳。

本篇完......................

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值