【C++从入门到踹门】第十七篇(下):map与set实现


在这里插入图片描述

基本框架

红黑树是map和set的底层数据结构,红黑树的代码实现戳红黑树实现

现在要用红黑树封装出map和set两个容器,过程中需要对红黑树略加修改,读者可以打开红黑树代码一起看。

我们知道map中封装有两个变量:key和value,而set中则是以key同时作为键和值。

这里我们先看下map和set的STL源码如何对这两个变量进行封装:

  • map

在这里插入图片描述

map源码中的键(key)值(value)类型:key —— Key ,value —— pair<const Key ,Value>

源码把value封装成了pair,first仍为Key,second为数据类型T。

  • set

在这里插入图片描述

map源码中的键(key)值(value)类型:key —— Key ,value —— Key

显然这两个容器的key还保持模板参数Key的的类型,但是value的类型产生了区别,

红黑树模板参数Value接受的类型将分别是map的 pair<const Key,T>和set的 Key

我们再查看STL中红黑树的部分源码:

在这里插入图片描述

可以看到源码中红黑树的结点模板参数Value决定了树结点中存放值的类型,因为map和set的value中都已经包含了键值key,在红黑树中是可以通过索引定位到具体结点的。

我们根据结点的类型先定下map和set的基本框架,

  • map基本框架
namespace mystd
{
	template<class K,class Value>
	class map
	{
	public:
	private:
		RBTree<K, pair<const K, Value>> _t;//Value类型为pair
	};
}
  • set基本框架
namespace mystd
{
    template<class K>
    class set
    {
    public:
    private:
        RBTree<K,K> _t;//Value类型为K
    };
}
  • 红黑树结点代码,只接受Value类型即可:
//红黑树的树结点
template<class Value>
struct RBTreeNode
{
	RBTreeNode<Value>* _left;
	RBTreeNode<Value>* _right;
	RBTreeNode<Value>* _parent;
	Value _data;
	Colour _colour;

	RBTreeNode(const Value& data):
		_left(nullptr),
		_right(nullptr),
		_parent(nullptr),
		_data(data),
		_colour(RED)
	{}
};
  • 红黑树的基本框架:
template<class K, class Value>
class RBTree
{
	typedef RBTreeNode<Value> Node;//根据Value的类型判断map还是set,pair<const K,V>或者 K
private:
	Node* _root;
};

红黑树仿函数 KeyofValue

我们观察红黑树源码的类模板的第三个参数:

在这里插入图片描述

这个类有什么用呢?

我们知道map和set的底层是一棵红黑树,那么红黑树在插入,删除和搜索时会将红黑树结点中的 _data与函数中的索引参数 key比较大小,从而满足二叉搜索树的属性(左小右大,或左大右小),但是树结点并不知道 _data 是什么类型?

在这里插入图片描述

如果我们正在使用map,那么_data的类型是pair< K , Value >,于是就要提取_data中的K类型值来和函数参数key作比较。

如果正在使用set,那么_data本身就是K类型,直接可以和key进行比较。

由于_data是什么类型是由map或set决定的,谁提供_data,谁就要提供解析_data中key值的方法,于是我们需要在map和set类中各自添加一个仿函数类,如下

  • map中添加仿函数类 MapKOfValue
class map
{
public:
    //内部类 仿函数 拿到Value中的key值
    struct MapKOfValue
    {
        //map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
        const K& operator()(const pair<const K, Value>& kv)
        {
            return kv.first;
        }
    };
private:
	RBTree<K, pair<const K, Value>,MapKOfValue> _t;
};
  • set 中添加仿函数类 SetKOfValue
class set
{
public:
    struct SetKOfValue
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
private:
    RBTree<K,K,SetKOfValue> _t;
};
  • 红黑树类模板也迎来更新
template<class K, class Value,class KeyOfValue>//增加仿函数类KeyOfValue
class RBTree
{
	typedef RBTreeNode<Value> Node;
private:
	Node* _root;
};

红黑树迭代器

要实现map和set的迭代器,我们需要先为红黑树创建迭代器,随后封装在map和set中即可。

红黑树迭代器框架如下:

template <class Value,class Ref,class Ptr>
struct RBTreeIterator
{
    typedef RBTreeNode<Value> Node;
    typedef RBTreeIterator<Value,Ref,Ptr> Self; 
  
    //将提供给反向迭代器 作为返回类型
    typedef Ref Reference;
    typedef Ptr Pointer;

    Node* _node;

    RBTreeIterator(Node* node) :_node(node)
	{}

	Ref operator*()
	{
        //....
	}

	Ptr operator->()
	{
        //....
	}

	bool operator!=(const Self& s)const
	{
        //....
	}

	bool operator==(const Self& s)const
	{
        //....
	}

	Self operator++()
	{
        //....
	}

    Self operator--()
    {
        //.....
    }

};

迭代器的成员变量是结点指针,所有的成员函数都

其中类模板参数:

  • Value: 结点中的存放数据类型
  • Ref : 结点类型的引用
  • Ptr : 结点类型的指针

Ref和Ptr是为了方便建立const修饰的迭代器,在迭代器重载 operator&operator->时,不论外边传入的模板参数是什么,都可以返回Ref和Ptr,普通迭代器的 Ref匹配 Value&,Ptr匹配 Value*,而const迭代器的 Ref匹配 const Value&,Ptr匹配 const Value*

可以看到这两个模板参数的引入使得代码更加灵活,体会泛型编程与类型的力量。

operator* 和 operator->

  • 解引用返回结点存储的数据的引用
  • 箭头返回数据的指针
Ref operator*()
{
    return _node->_data;
}

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

注意:因为箭头的重载返回的是指针,真正实用的时候应该是it->->,但是编译器会优化,所以届时只使用一个箭头即可。

operator== 和 operator!=

bool operator!=(const Self& s)const
{
    return _node!=s._node;
}

bool operator==(const Self& s)const
{
    return _node==s._node;
}

operator++ 和 operator–

先看++操作,根据中序的规则,那么迭代器自增1后找到下一个结点的规则如下:

  • 右树不空, 访问右树中的中序序列的第一个结点(即右树的最左结点)

在这里插入图片描述

  • 右树为空,沿着父指针往上找,直到找到该路径上溯的孩子不是自身右孩子的祖先结点

在这里插入图片描述

Self& operator++()
{
    if(_node->_right)
    {
        Node* cur=_node->_right;
        while(cur->_left)
        {
            cur=cur->_left;
        }
        _node=cur;
    }
    else
    {
        Node* parent=_node->_parent;
        Node* cur=_node;
        while(parent && parent->_right==cur)
        {
            cur=parent;
            parent=parent->_parent;
        }
        _node=parent;
    }
    return *this;
}

现在看–操作,可以理解为++操作的逆序(右根左),找到上一个结点的规则如下:

  • 当前结点左树不空,找到左树的最右结点

在这里插入图片描述

  • 左树为空,沿着父指针往上找,直到找到该路径上溯的孩子不是自身左孩子的祖先结点

在这里插入图片描述

Self& operator--()
{
    if (_node ->_left)
    {
        Node* cur = _node->_left;
        while (cur->_right)
        {
            cur = cur->_right;
        }
        _node = cur;
    }
    else
    {
        Node* cur = _node;
        Node* parent = _node->_parent;
        while (parent && cur == parent->_left)
        {
            cur = parent;
            parent = parent->_parent;
        }
        _node = parent;
    }
    return *this;
}

反向迭代器

反向迭代器则是正向迭代器的容器适配器:

// 反向迭代器——使用正向迭代器封装
template <class Iterator>
struct RBTreeReverseIterator
{
public:
	typedef typename Iterator::Reference Ref;
	typedef typename Iterator::Pointer Ptr;
	typedef RBTreeReverseIterator<Iterator> Self;

	Iterator _it;//正向迭代器作为成员变量

	RBTreeReverseIterator(Iterator it) :_it(it._node)
	{}

	Ref operator*()
	{
		return *_it;
	}

	Ptr operator->()
	{
		return _it.operator->();
	}

	bool operator!=(const Self& s)const
	{
		return _it != s._it;
	}

	bool operator==(const Self& s)const
	{
		return _it == s._it;
	}


    //自增自减与正向迭代器呈反向操作
	Self& operator++()
	{
		--_it;
		return *this;
	}
	Self& operator--()
	{
		++_it;
		return *this;
	}
};

在这里插入图片描述

封装成map和set的迭代器

首先将迭代器放入红黑树中

template<class K, class Value,class KeyOfValue>
class RBTree
{
public:
	typedef RBTreeNode<Value> Node;
	typedef RBTreeIterator<Value, Value&, Value*> iterator;
	typedef RBTreeIterator<Value, const Value&,const Value*> const_iterator;
	typedef RBTreeReverseIterator<iterator> reverse_iterator;

    //....
};

由于该迭代器是需要给map和set封装访问的,所以typedef迭代器为public范围。

然后封装进map和set类:

🚩注意我们需要在迭代器前添加 typename

使用 typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 RBTree<K, pair<const K, Value>::iterator 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。

直到确定了 RBTree是什么东西,编译器才会知道 RBTree<K, pair<const K, Value>::iterator是不是一个类型; 然而当模板被解析时,RBTree还是不确定的。这时我们声明它为一个类型才能通过编译!

  • map中添加 迭代器
class map
{
public:
    //内部类 仿函数 拿到Value中的key值
    struct MapKOfValue
    {
        //map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
        const K& operator()(const pair<const K, Value>& kv)
        {
            return kv.first;
        }
    };
public:
    typedef typename RBTree<K, pair<const K, Value>::iterator iterator;
    typedef typename RBTree<K, pair<const K, Value>::const_iterator const_iterator;//typename 确保后面为类型名而非变量
    typedef	typename RBTree<K, pair<const K, Value>, MapKOfValue>::reverse_iterator reverse_iterator;

private:
	RBTree<K, pair<const K, Value>,MapKOfValue> _t;
};
  • set 中添加 迭代器
class set
{
public:
    struct SetKOfValue
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
    typedef typename RBTree<K, K, SetKOfValue>::iterator iterator;//typename 确保其为类型而非变量
    typedef typename RBTree<K, K,SetKOfValue>::const_iterator const_iterator;  
    typedef typename RBTree<K, K, SetKOfValue>::reverse_iterator reverse_iterator;
private:
    RBTree<K,K,SetKOfValue> _t;
};

完善类功能

插入结点——insert函数

STL中map和set返回的类型为:

在这里插入图片描述

在这里插入图片描述

所以需要对红黑树中insert函数的返回值稍作修改,注意在下面修改的insert函数时仿函数KeyOfValue的使用:

    pair<iterator, bool> Insert(const Value& data)
	{
		KeyOfValue kot;//仿函数类实例化
		if (_root == nullptr)
		{
			_root = new Node(data);
			_root->_colour = BLACK;
			return make_pair(iterator(_root), true);
		}

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (kot(cur->_data) > kot(data))//提取出Value类型中的key值,再进行比较
			{
				cur = cur->_left;
			}
			else if (kot(cur->_data) < kot(data))
			{
				cur = cur->_right;
			}
			else
			{
				return make_pair(iterator(cur), false);
			}
		}

		Node* newnode = new Node(data);
		newnode->_parent = parent;
		if (kot(parent->_data) >kot(data))
		{
			parent->_left = newnode;
		}
		else
		{
			parent->_right = newnode;
		}
		cur = newnode;

		//开始调整
		while (parent && parent->_colour == RED)//最差情况,处理到根节点为止
		{
			Node* grandfather = parent->_parent;
			Node* uncle = nullptr;
			if (grandfather->_right == parent)     //parent为grandfather右树
			{
				uncle = grandfather->_left;
				if (uncle && uncle->_colour == RED)//情况1,仅需变色,且考虑往上追溯
				{
					parent->_colour = BLACK;
					uncle->_colour = BLACK;
					grandfather->_colour = RED;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;
				}
				else                      //情况2+3 uncle为黑或者不存在,需要旋转+变色
				{
					//这里不会对uncle进行旋转或是变色处理,我们只关心是折线还是直线情况。
					if (cur == parent->_left)//折线情况
					{
						//对parent做右单旋
						RotateR(parent);
						swap(cur, parent);
					}
					//统一处理为直线情况
					RotateL(grandfather);
					parent->_colour = BLACK;//parent(当前子树的根节点)变为黑色
					grandfather->_colour = RED;//grandfather变为红色,以保证路径的黑色结点数量不变

					//无需再往上调整了,下次的while会因为parent为黑直接结束循环,不用在此break
				}
			}
			else                               //parent为grandfather左树
			{
				uncle = grandfather->_right;
				if (uncle && uncle->_colour == RED)//情况1,仅需变色,且考虑往上追溯
				{
					parent->_colour = BLACK;
					uncle->_colour = BLACK;
					grandfather->_colour = RED;
					//继续往上处理
					cur = grandfather;
					parent = cur->_parent;

				}
				else                            //情况2+3 uncle为黑或者不存在,需要旋转+变色
				{
					//这里不会对uncle进行旋转或是变色处理,我们只关心是折线还是直线情况。
					if (cur == parent->_right)
					{
						//对parent做左单旋
						RotateL(parent);
						swap(cur, parent);
					}
					//统一处理为直线情况
					RotateR(grandfather);
					parent->_colour = BLACK;
					grandfather->_colour = RED;

					//无需再往上调整了,下次的while会因为parent为黑直接结束循环,不用在此break
				}
			}
		}//while
		_root->_colour = BLACK;//如果grandfather为根节点,那么让其变黑色

		return make_pair(iterator(newnode), true);
	}
  • insert封装进map
public:
    pair<iterator,bool> insert(const pair<const K,Value>& data)
    {
        return _t.Insert(data);
    }
  • insert封装进set
public:
    pair<iterator,bool> insert(const K& data)
    {
        return  _t.Insert(data);
    }

下标访问——operator[]函数

  • map
public:
    Value& operator[](const K& key)
    {
        pair<iterator, bool> ret = _t.Insert(make_pair(key, Value()));
        return ret.first->second;
    }

删除结点——erase函数

红黑树的Erase函数很长就不放在这里,可以通过文末的链接查看红黑树类中的删除结点代码。

这里需要注意的是,删除结点是会遇到替换的情况的(删除结点的左右孩子都存在)。

set类可以直接交换待删除结点和替换结点的_data,但是map类无法交换,因为map类的_data为pair< const K,Value > 类型,const修饰无法更改,所以如果需要替换,将const去掉即可。

完整代码

  • map
#include "RBTree.h"
namespace mystd
{
	template<class K,class Value>
	class map
	{

	public:
		//内部类 仿函数 拿到Value中的key值
		struct MapKOfValue
		{
			//map 传给红黑树的value类型是pair,为了比较大小,需要提取出key值
			const K& operator()(const pair<const K, Value>& kv)
			{
				return kv.first;
			}
		};

	public:
		// typename 告诉编译器这只是一个名字,暂时不用对模板进行实例化
		typedef typename RBTree<K, pair<const K, Value>, MapKOfValue>::iterator iterator;//typename 确保后面为类型名而非变量
		typedef	typename RBTree<K, pair<const K, Value>, MapKOfValue>::const_iterator const_iterator;
		typedef	typename RBTree<K, pair<const K, Value>, MapKOfValue>::reverse_iterator reverse_iterator;
		iterator begin()
		{
			return _t.begin();
		}

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

		reverse_iterator rbegin()
		{
			return _t.rbegin();
		}

		reverse_iterator rend()
		{
			return _t.rend();
		}

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

		Value& operator[](const K& key)
		{
			pair<iterator, bool> ret = _t.Insert(make_pair(key, Value()));
			return ret.first->second;
		}

		bool erase(const K& key)
		{
			return _t.Erase(key);
		}

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

	private:
		RBTree<K, pair<const K, Value>,MapKOfValue> _t;
	};
}
  • set
#include "RBTree.h"

namespace mystd
{
	template<class K>
	class set
	{

	public:
		struct SetKOfValue
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename RBTree<K, K, SetKOfValue>::iterator iterator;//typename 确保其为类型而非变量
		typedef typename RBTree<K, K, SetKOfValue>::const_iterator const_iterator;
		typedef typename RBTree<K, K, SetKOfValue>::reverse_iterator reverse_iterator;
		pair<iterator,bool> insert(const K& data)
		{
			return  _t.Insert(data);
		}

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

		iterator end()
		{
			return _t.end();
		}
		reverse_iterator rbegin()
		{
			return _t.rbegin();
		}

		reverse_iterator rend()
		{
			return _t.rend();
		}

		bool erase(const K& key)
		{
			return _t.Erase(key);
		}

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

	private:
		RBTree<K,K,SetKOfValue> _t;
	};
}

修改后的红黑树代码戳map与set实现

— end —

青山不改 绿水长流

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值