C++ map的简单实现

Posted WhiteShirtI

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ map的简单实现相关的知识,希望对你有一定的参考价值。

map和set的底层都是通过红黑树来实现的,但并不是原生态的红黑树,而是经过改造后的红黑树。且容器都会在各自的类中添加一些独特的函数来解决各自适配的问题

map和set底层是改造后的红黑树,我们先来看看改造后的红黑树
在这里插入图片描述
和普通的红黑树不同的是,在根节点上再加了一个头结点,该结点不是真实的结点,只是一个辅助结点,是为了后面实现红黑树的迭代器而出现的。该header结点的父节点就是真实的根节点,其左孩子是这棵树的最左结点,其右孩子是这棵树的最右节点。
我们现在通过STL源码来简单剖析一下map和set中如何利用红黑树来实现各自不同的功能的
在这里插入图片描述
在map中有两个泛型参数,一个是Key,一个是T,也就是value。其中Key的别名是key_type,然后再将Key和T作为pair对象的一个泛型参数,将其的别名改为value_type。成员只有一棵树,该树就是红黑树,红黑树有两个泛型参数,一个是Key,一个是Value。Key就是红黑树中结点的值的类型,Value就是结点Key对应的Value值。结点中继承了一个结点类,其就相当有5个成员,颜色、父类指针、左孩子指针、右孩子指针、结点的值。

在set中只有一个泛型参数Key。在该容器中,由于使用红黑树底层必须提供两个泛型参数,set就将vkey当做value。此时传过去的红黑树的泛型参数就是相同的,都是Key。

所以两个容器的不同,起最关键的就是value类型不同,map容器底层中的红黑树的value是一个pair对象;而set容器中的的红黑树的value就是set中本身的key。在红黑树中做特殊处理,就可以获得他们的value。
在这里插入图片描述

以下代码就是对红黑树的一种改进,适配于map和set关联容器

#include <iostream>
#include <utility>
#include <algorithm>
using namespace std;

enum COLOR
{
	BLACK,
	RED
};

template <class V>
struct RBTreeNode
{
	RBTreeNode<V>* _parent; //父节点
	RBTreeNode<V>* _left; //左孩子
	RBTreeNode<V>* _right; //右孩子
	V _val;
	COLOR _color; //颜色

	RBTreeNode(const V& val = V())
		:_parent(nullptr)
		, _left(nullptr)
		, _right(nullptr)
		, _val(val)
		, _color(RED)
	{}
};

template <class K, class V, class KeyOfValue>
class RBTree
{
public:
	typedef RBTreeNode<V> Node;

	RBTree()
		:_header(new Node)
	{
		//创建空树
		_header->_left = _header->_right = _header;
	}

	bool insert(const V& val)
	{
		if (_header->_parent == nullptr)
		{
			Node* root = new Node(val);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;

			//根节点为黑色
			root->_color = BLACK;
			return true;
		}

		Node* cur = _header->_parent;
		Node* parent = nullptr;

		KeyOfValue kov;
		//1.寻找到要插入的结点的位置
		while (cur)
		{
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return false;
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//2.创建节点
		cur = new Node(val);
		if (kov(parent->_val) > kov(cur->_val))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//3.颜色的修改或者结构的调整
		while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整
		{
			parent = cur->_parent;
			Node* gfather = parent->_parent;

			if (gfather->_left == parent)
			{
				Node* uncle = gfather->_right;
				//情况1.uncle存在且为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_right == cur)//情况3
					{
						RotateL(parent);
						swap(cur, parent);
					}
					//情况2.uncle不存在或者uncle为黑
					RotateR(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			else
			{
				Node* uncle = gfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_left == cur)
					{
						RotateR(parent);
						swap(cur, parent);
					}

					RotateL(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
		}

		//根节点为黑色
		_header->_parent->_color = BLACK;
		//更新头结点的左右指向
		_header->_left = leftMost();
		_header->_right = rightMost();
		return true;
	}

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

		parent->_right = subRL;
		if (subRL)
			subRL->_parent = parent;
		if (parent == _header->_parent)
		{
			_header->_parent = subR;
			subR->_parent = _header;
		}
		else
		{
			Node* gfather = parent->_parent;
			if (gfather->_left == parent)
				gfather->_left = subR;
			else
				gfather->_right = subR;
			subR->_parent = gfather;
		}
		subR->_left = parent;
		parent->_parent = subR;
	}

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

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

		if (parent == _header->_parent)
		{
			_header->_parent = subL;
			subL->_parent = _header;
		}
		else
		{
			Node* gfather = parent->_parent;
			if (gfather->_left == parent)
				gfather->_left = subL;
			else
				gfather->_right = subL;
			subL->_parent = gfather;
		}
		subL->_right = parent;
		parent->_parent = subL;
	}

	Node* leftMost()
	{
		Node* cur = _header->_parent;
		while (cur && cur->_left)
		{
			cur = cur->_left;
		}
		return cur;
	}

	Node* rightMost()
	{
		Node* cur = _header->_parent;
		while (cur && cur->_right)
		{
			cur = cur->_right;
		}
		return cur;
	}
private:
	Node* _header;
};

template<class K, class T>
class Map
{
	struct MapKeyOfValue
	{
		const K& operator()(const pair<K, T>& val)
		{
			return val.first;
		}
	};
public:
	bool insert(const pair<K, T>& kv)
	{
		return _rbt.insert(kv);
	}

	T& operator[](const K& key)
	{
		bool ret = _rbt.insert(make_pair(key, T()));
	}

private:
	typedef RBTree<K, pair<K, T>, MapKeyOfValue> rbt;
	rbt _rbt;
};

template <class K>
class Set
{
	struct SetKeyOfValue
	{
		const K& operator()(const K& val)
		{
			return val;
		}
	};

public:
	bool insert(const K& val)
	{
		return _rbt.insert(val);
	}

private:
	typedef RBTree<K, K, SetKeyOfValue> rbt;
	rbt _rbt;
};

迭代器

我们原生的Node结点迭代器是无法实现迭代器的普通操作的,所以我们必须要对结点进行另一层的封装,重载对应的操作运算符。在该类中只有一个成员变量,就是Node结点。

对迭代器的解引用,就是获得迭代器的val值

	V& operator*()
	{
		return _node->_val;
	}

对迭代器的箭头操作,就是获得迭代器值的地址

	V* operator->()
	{
		return &_node->_val;
	}

两个迭代器的判等操作就是结点的地址是否相同

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

迭代器的++和- -是要符合中序遍历的有序遍历,下面我们来一起分析
迭代器的begin()位置应该是树中最小的结点,而树中最小的结点就是树的最左结点;迭代器的end()位置应该是树中最大的结点,而树中最大的结点就是树的最右结点
在这里插入图片描述
迭代器的++操作分为两种情况,第一种是存在右子树的情况,第二种是不存在右子树的情况

当存在右子树时,我们需要遍历到右子树的最左结点,然后访问该结点例如我们现在的迭代器在8的位置,根据中序遍历的条件,我们应该访问的是10号结点,所以我们先走到8结点的右子树11号结点,然后遍历11的最左结点,该结点为10,也正是我们要访问的结点
在这里插入图片描述

在这里插入图片描述

		if (_node->_right)
		{
			//右子树的最左结点
			_node = _node->_right;
			while (_node->_left)
			{
				_node = _node->_left;
			}
		}

第二种情况是不存在右子树。当不存在右子树,我们需要向上回溯,中序遍历的遍历规则就是左孩子、根、右孩子。所以我们要判断当前节点是父节点的左孩子还是右孩子,如果是左孩子则表示父节点还未访问,此时需要访问父节点;如果是右孩子则表示父节点也访问过了,需要向上回溯,直至该结点不为父节点的右孩子。例如我们节点在5号结点的位置,需要判断该结点父节点6号结点的左边还是右边,此时5号结点再6号结点的左边,可以表示6号结点也未访问,此时将迭代器更新的父节点的位置即可。
在这里插入图片描述
当我们迭代器在7号结点时,7号结点时在6号结点的右边,此时需要向上回溯,让结点更新置父节点,然后父节再向上回溯至其父节点的位置,再判断当前节点的位置是否为父节点的右边,此时6号结点是8号结点的左边,表示8号结点还未访问,此时访问8号结点即可
在这里插入图片描述
但是我们需要考虑到一种特殊的情况,就是该树的根节点没有右孩子时
在这里插入图片描述

正常来说,我们对迭代器的++操作应该会移动到空的头结点的位置,但是我们再回溯的过程中会出现问题。此时因为it没有右结点,需要判断该结点是在父节点的左边还是右边,此时是在右边,就会向上回溯
在这里插入图片描述
这样子对迭代器的++是一个死操作,永远不会走到空的位置。所以我们需要再跟结点处做特殊的处理。当node结点的右孩子为自己的父亲时,就不用更新结点了,此时已经走到end()的了,如果还更新置parent结点,则该++操作就没有发生改变

我们现在进行测试
迭代器部分代码

template <class  V>
struct RBTreeIterator
{
	typedef RBTreeNode<V> Node;
	typedef RBTreeIterator<V> Self;
	Node* _node;

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

	//解引用
	V& operator*()
	{
		return _node->_val;
	}

	V* operator->()
	{
		return &_node->_val;
	}

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

	Self& operator++()
	{
		if (_node->_right) //存在右节点
		{
			//右子树的最左结点
			_node = _node->_right;
			while (_node->_left)
			{
				_node = _node->_left;
			}
		}
		else //不存在右节点
		{
			Node* parent = _node->_parent;
			while (_node == parent->_right)//回溯
			{
				_node = parent;
				parent = parent->_parent;
			}
			//特殊情况:根节点没有右孩子,则不需要更新结点
			if (_node->_right != parent) 
				_node = parent;
		}
		return *this;
	}
};

红黑树添加迭代器代码

	typedef RBTreeIterator<V> iterator;

	iterator begin()
	{
		return iterator(_header->_left);
	}
	iterator end()
	{
		return iterator(_header);
	}

map中添加红黑树迭代器代码

	typedef typename RBTree<K, pair<K, T>, MapKeyOfValue>::iterator iterator;

	iterator begin()
	{
		return _rbt.begin();
	}
	iterator end()
	{
		return _rbt.end();
	}

测试结果:
在这里插入图片描述
这里直接给出迭代器- -的代码,原理和++类似

在迭代器类中

	Self& operator--()
	{
		if (_node->_left)
		{
			//右子树的最左结点
			_node = _node->_left;
			while (_node->_right)
			{
				_node = _node->_right;
			}
		}
		else
		{
			Node* parent = _node->_parent;
			while (_node == parent->_left)
			{
				_node = parent;
				parent = parent->_parent;
			}
			if (_node->_left != parent)
				_node = parent;
		}
		return *this;
	}

在红黑树类中添加反向迭代器用于测试

iterator rbegin()
	{
		return iterator(_header->_right);
	}

map中也添加反向迭代器

	iterator rbegin()
	{
		return _rbt.rbegin();
	}

测试:
在这里插入图片描述

方括号[]

插入的返回值是返回一个pair对象,所以我们在插入时的返回值都需要修改成一个pair对象,且pair对象的first为插入后的结点的迭代器,second为插入是否成功

	pair<iterator, bool> insert(const V& val)
	{
		if (_header->_parent == nullptr)
		{
			Node* root = new Node(val);

			_header->_parent = root;
			root->_parent = _header;
			_header->_left = _header->_right = root;

			//根节点为黑色
			root->_color = BLACK;
			return make_pair(iterator(root), true);
		}

		Node* cur = _header->_parent;
		Node* parent = nullptr;

		KeyOfValue kov;
		//1.寻找到要插入的结点的位置
		while (cur)
		{
			parent = cur;
			if (kov(cur->_val) == kov(val))
				return make_pair(iterator(cur), false);
			else if (kov(cur->_val) > kov(val))
				cur = cur->_left;
			else
				cur = cur->_right;
		}
		//2.创建节点
		cur = new Node(val);
		Node* node = cur;
		if (kov(parent->_val) > kov(cur->_val))
			parent->_left = cur;
		else
			parent->_right = cur;
		cur->_parent = parent;

		//3.颜色的修改或者结构的调整
		while (cur != _header->_parent && cur->_parent->_color == RED)//不为根且存在连续红色,则需要调整
		{
			parent = cur->_parent;
			Node* gfather = parent->_parent;

			if (gfather->_left == parent)
			{
				Node* uncle = gfather->_right;
				//情况1.uncle存在且为红
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;
				}
				else
				{
					if (parent->_right == cur)//情况3
					{
						RotateL(parent);
						swap(cur, parent);
					}
					//情况2.uncle不存在或者uncle为黑
					RotateR(gfather);
					parent->_color = BLACK;
					gfather->_color = RED;
					break;
				}
			}
			else
			{
				Node* uncle = gfather->_left;
				if (uncle && uncle->_color == RED)
				{
					parent->_color = uncle->_color = BLACK;
					gfather->_color = RED;
					//向上追溯
					cur = gfather;以上是关于C++ map的简单实现的主要内容,如果未能解决你的问题,请参考以下文章

代码片段 - Golang 实现集合操作

lua 如何实现 C++ 里的 map

C++进阶map和set模拟实现

C++进阶map和set模拟实现

C++进阶map和set模拟实现

详细实例说明+典型案例实现 对动态规划法进行全面分析 | C++