Detailed explanation of c++---map and set encapsulation

Preface

Through the previous study, we know that the data stored in the set container is k, and the data stored in the map container are k and v, but the bottom layer of these two containers is realized through red-black trees, so according to our normal thinking is , the red-black tree adapts these two containers respectively, that is to say, implement a kv version iterator for map, and implement a k version iterator for set, but if it is implemented in this way, it will definitely be very troublesome , will do many of the same things, and C++ has templates, so can we use templates to make the two containers contain different numbers of data, but they can be encapsulated by a template red-black tree class Into map or set? The answer is yes, the standard library we use uses a red-black tree to encapsulate map and set, then we will implement the encapsulation of map and set step by step.

Basic code of red-black tree

enum colour
{
    
    
	RED,
	BLACK,
};
template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{
    
    }
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};
template<class K, class V>
class RBTree
{
    
    
	typedef RBTreeNode<K, V> Node;
public:
	RBTree()
		:root(nullptr)
	{
    
    }
	bool insert(const pair<K, V>& _kv)
	{
    
    
		if (root == nullptr)
		{
    
    
			root = new Node(_kv);
			root->_col = BLACK;
			return true;
		}
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
    
    
			if (_cur->kv.first > _kv.first)
			{
    
    
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
    
    
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
    
    
				return false;
			}
		}
		_cur = new Node(_kv);
		if (_kv.first > _parent->kv.first)
			//如果插入的数据比parent的数据大
		{
    
    
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
    
    
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
    
    
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
    
    
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
    
    
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
    
    
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
    
    
					//情况二
					if (_cur == _parent->left)
					{
    
    
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
    
    
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}

			}
			else
				//祖先节点的右边要调整
			{
    
    
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
    
    
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
    
    
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
    
    
					//情况二
					if (_cur == _parent->right)
					{
    
    
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
    
    
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return true;

	}
	void inorder()
	{
    
    
		_inorder(root);
	}
	bool find(const K& val)
	{
    
    
		Node* cur = root;
		while (cur)
		{
    
    
			if (cur->kv.first == val)
			{
    
    
				return true;
			}
			else if (cur->kv.first > val)
			{
    
    
				cur = cur->left;
			}
			else
			{
    
    
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
    
    
		if (root == nullptr)
		{
    
    
			if (ref != BlackNums)
			{
    
    
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
    
    
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
    
    
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
    
    
		if (root == nullptr)
		{
    
    
			return true;
		}
		if (root->_col != BLACK)
		{
    
    
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
    
    
			if (_left->_col == BLACK)
			{
    
    
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	Node* root;
	void _inorder(const Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}
		_inorder(root->left);
		cout << root->kv.first << " " << root->kv.second << endl;
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
    
    
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
		_parent->right = subRL;
		if (subRL)
		{
    
    
			//如果subRL不为空则修改父节点的指向
			subRL->parent = _parent;
		}
		//把_parent放到subR的左
		subR->left = _parent;
		//修改_parent的parent的指向
		_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
    
    
			if (ppNode->right == _parent)
			{
    
    
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
    
    
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
    
    
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
    
    
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
    
    
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
    
    
			if (ppNode->right == _parent)
			{
    
    
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
    
    
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
    
    
			root = subL;
			subL->parent = nullptr;
		}
	}
};

If you don’t know the principle of red-black trees, or red-black trees cannot be implemented, you can read my last article.

Encapsulation of map and set

We know that the set container contains a kind of element k, and the map container contains two elements k and v. According to the previous study, we know that the map is loaded with two elements using a pair. Logically speaking, the storage in the map is an element pair, so can the red-black tree template be an element? So here we analyze it step by step. If the template of the red-black tree is an element, the encapsulation of map and set should look like this:

template<class K>
class set
{
    
    
public:

private:
	RBTree<K> tree;
};

template<class K,class V>
class map
{
    
    
public:

private:
	RBTree<pair<const K,V>> tree;

};

If I pass a K, then the instantiation of the red-black tree is a set, and if a pair is passed, then the instantiation of the red-black tree is a map. It seems reasonable, but in the standard library it seems Not so. In the standard library, map is instantiated with two parameters k and pair, and set is also instantiated with two parameters, but both parameters are k. The bottom layers of map and set are based on red Black tree for encapsulation. If the second parameter is k, then the instantiation is set. If the second parameter is pair, then the instantiation is map. For example, the following code:

template<class K>
class set
{
    
    
public:

private:
	RBTree<K,K> tree;
};

#include"RBTree.h"
template<class K,class V>
class map
{
    
    
public:

private:
	RBTree<K,pair<const K,V>> tree;

};

So there is a problem here. Since the second template parameter can determine who is map and who is set, why do we need to pass the first template parameter? In order to clarify this problem, let's first take a look at what problems will occur if there is only one parameter. The first is the insert function. The parameters of this function are as follows:

itertaor insert(const V& date)

If the current container is a set, then the type of V here is K, and if the current container is a map, then the type of V here is pair, and usually when we use the insert function to pass parameters, it is also corresponding, inserting data into the set to transfer It is K. When inserting data into the map, the pair is passed. If the template of the red-black tree has only one parameter, it can also be handled here. What about the find function? Regardless of whether it is a map container or a find container, when we use the find function, we pass the data of the K type. There is no such thing as saying that if the current container is a map, we will pass the pair to search, right! So if the red-black tree has only one parameter, how to declare the find function in the map container? Therefore, in order to properly encapsulate map and set, the red-black tree here must have two template parameters to represent the type of data. The current red-black tree must encapsulate both map and set, so we also need to do the structure describing the node. After modification, the form of the previous structure is as follows:

template<class K, class V>
struct RBTreeNode
{
    
    
	RBTreeNode(const pair<K, V>& _kv)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, kv(_kv)
		, _col(RED)
	{
    
    }
	RBTreeNode<K, V>* parent;
	RBTreeNode<K, V>* right;
	RBTreeNode<K, V>* left;
	pair<K, V> kv;
	colour _col;
};

We said above that the second parameter of the red-black tree can determine whether the current tree encapsulates a map or a set. The same is true here. The structure only needs one parameter to determine whether the current instantiation is a map node or a set. set node, so we change the number of template parameters to a type used to express the stored data, and then modify the pair variable to a variable created by the template type, for example, the following code:

template< class T>
struct RBTreeNode
{
    
    
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{
    
    }
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};

But a new problem arises here: inside the insert function and the find function, the passed parameters are compared. For set, this comparison is normal. You can directly compare the k value. What about map? Can he directly compare the internal pairs? So there are two questions here. First, can pairs use the operator ">" "<" for comparison, and secondly, do the rules of this comparison meet our expectations? The answer to the first question is that you can use the operator for comparison. Pair provides an overload of the operator, such as the picture below:
Insert image description here
Since comparison can be performed, let’s take a look at the overloading logic of the second question. Does it meet our expectations? For map, we hope that the comparison rule for its internal data is that the larger the first element of the pair, the greater the value of the pair. It has nothing to do with the second element. Is this how overloading in the library is implemented? Let's take a look at the picture below:
Insert image description here
If you look carefully, you will find that the implementation here is different from what we imagined. It also adds the second element to the size comparison of the pair, so if you use pair directly in the map There will definitely be problems when comparing. We only want to use the first element of the pair for comparison, that is, use K for comparison. So how to solve this problem here! The answer is to add a functor to the red-black tree template. The function of this functor is to specifically obtain the K of the internal data of the container. First, this functor requires a parameter. For set, the type of this parameter is K. For map, The type of this parameter is pair, for example, the following code:

//set.h
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
		}
	};
//map.h
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			
		}
	};

For the set functor, the data passed is key, so we can return it directly. For the map functor, the data passed is pair, and we can return the first element in the pair. Then the code here is as follows:

//set.h
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
			return key;
		}
	};
//map.h
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			return kv.first;
		}
	};

Now that the functor has been added, does the red-black tree have to be modified when displaying instantiation? Then the code here is as follows:

//set.h
template<class K>
class set
{
    
    
public:

private:
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
			return key;
		}
	};

	RBTree<K,K, SetofKey> tree;

};
//map.h
template<class K,class V>
class map
{
    
    
public:

private:
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			return kv.first;
		}
	};

	RBTree<K,pair<const K,V>,MapofKey> tree;
};

After adding the functor, the comparison logic of the red-black tree must also be modified. The previous comparison logic was as follows:

template<class K, class V>
class RBTree
{
    
    
	typedef RBTreeNode<K,V> Node;
	bool insert(const pair<K, V>& _kv)
	{
    
    
		//.....
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
    
    
			if (_cur->kv.first > _kv.first)
			{
    
    
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (_cur->kv.first < _kv.first)
			{
    
    
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
    
    
				return false;
			}
		}
		//.....
	}
}

After having the functor, we cannot rely on ourselves to obtain the K in the data, but use the functor object created by the functor to obtain the Key value of the data in the node. Don’t forget to modify the parameter type and node of the insert function here. The number of instantiated parameters and the number of parameters of the red-black tree template, then the modified code here is like this:

template<class K, class T,class KeyofT>
class RBTree
{
    
    
public:
typedef RBTreeNode<T> Node;
	bool insert(const T& _data)
	{
    
    
		//....
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
    
    
			if (kot(_cur->data) > kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
    
    
				return false;
			}
		}
		//....
	}
}

Of course, the places to be modified here are far more than the above places. As long as the first of the pair is used for comparison, the places that need to be modified must be modified, because of the space problem, I cannot show you one by one here. Then after modification, the insert function and find function of map and set can call the find and insert functions of red-black tree to implement, for example, the following code:

//map.h
template<class K, class V>
class map
{
    
    
public:
	bool insert(const pair<K,V>& data)
	{
    
    
		return tree.insert(data);
	}
	bool find(const K& key)
	{
    
    
		return tree.find(key);
	}
private:
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			return kv.first;
		}
	};
	RBTree<K, pair<const K, V>, MapofKey> tree;
};
//set.h
template<class K>
class set
{
    
    
public:
	bool insert(const K& key)
	{
    
    
		return tree.insert(key);
	}
	bool find(const K& key)
	{
    
    
		return tree.find(key);
	}
private:
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
			return key;
		}
	};
	RBTree<K, K, SetofKey> tree;

Red-black tree iterator

Let's first take a look at how iterators in the library are implemented:
Insert image description here

A header node is given in the library to point to the root node of the tree, and the left pointer of the header points to the leftmost node, so the begin function of the iterator points to the left of the header, and the right pointer of the header points to the rightmost node, so iteration The end function of the device points to the header, so this is the way the iterator is implemented in the library, then we do not add the header node for convenience, the begin function still returns the leftmost node, and the end function directly returns a null pointer, Then here we can implement these two functions. Based on the experience of the previous list, we know that the iterator here cannot be implemented inside the red-black tree class. Instead, a separate class must be created, because we currently want What is implemented is a normal iterator, so this class template has only one parameter to represent the data type that the current iterator only wants, because we need to perform some operations in this class, such as finding the leftmost node or the next node, etc. Wait, so we have to create a variable of this node type in this class to do some operations. There must be operator overloads of ++ and - in the iterator. The return type of these functions is the iterator itself, so in order to facilitate the following When writing, we rename commonly used types. Because there are member variables in this class, we have to implement the constructor of this class. So the code here is as follows:

template<class V>
struct _RBTreeIterator
{
    
    
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{
    
    }
};

Then we can implement several simple operator overloading functions. The first one is dereference overloading. This function directly returns the reference to the data data in the current node. The second one is -> operator overloading. The function of this function is to return The address of the current node’s internal data. The third one is the != operator overloading. These three functions are very simple. We won’t explain them. You can just look at the code:

template<class V>
struct _RBTreeIterator
{
    
    
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V> self;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{
    
    }
	V& operator*()
	{
    
    
		return _node->data;
	}
	V* operator->()
	{
    
    
		return &_node->data;
	}
	bool operator !=(const self& s)
	{
    
    
		return _node != s._node;
	}
};

We say that the results obtained by searching a binary tree using in-order traversal are ordered, and the order of in-order traversal is to first visit the left subtree, then visit the root node, and finally visit the right subtree. Using ++ is to make the current node go one step back. , the node currently staying must be the root node of a certain subtree. If you go back at this time, you must come to the leftmost node of the right subtree, but there is a situation here: what if the right node does not exist? Right? If the right node does not exist, it means that our current subtree has been visited and we have to visit the upper subtree. But is the subtree here also scored? If the current subtree is the left subtree, then we are Don’t you have to visit the root of the upper layer? If the current subtree is the right subtree, do you have to continue to adjust upwards until you encounter a current node that is to the left of the root node or the parent node is empty? First, judge me Whether the right subtree of is empty, if not, loop to the leftmost subtree of the right subtree, then the code here is as follows:

self& operator++()
{
    
    
	if (_node->right)
	{
    
    
		Node* mine = _node->right;
		while (mine->left)
		{
    
    
			mine = mine->left;
		}
		_node = mine;
	}
	else
	{
    
    
	}
}

If the right subtree is empty, it means that the current subtree has been traversed. At this time, we need to go up. If the current node is to the left of the parent node, we will stop. If the current node is to the right of the parent node, we have to continue. adjustment, so here we have to create two Node* pointers, one named parent to point to the parent node, and one named cur to point to the current node. Each time we loop, we will judge the positional relationship between cur and parent and whether parent is empty. , the loop body is to assign the value of parent to cur, then let parent point to the parent of parent, and finally assign the value of parent to _node, then the code here is as follows:

	self& operator++()
	{
    
    
		if (_node->right)
		{
    
    
			Node* mine = _node->right;
			while (mine->left)
			{
    
    
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
    
    
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
    
    
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}

Red-black tree iterator - -

The order of in-order traversal is to visit the left subtree first, then the root node, and finally the right subtree, - - is going backwards, so the order of nodes visited here is to visit the right subtree first, then the root node, and finally the left subtree, iteration The position of the iterator must be the root node of a certain subtree. When the iterator is at this position, it means that the right subtree of the node has been visited, and the left subtree of the node must be visited next because the right subtree must be visited first. , so the iterator - - will come to the rightmost node of the left subtree. If the left subtree is empty, the score will be discussed here. If the current node is the left of the parent node, it must continue to adjust upward. If the current node is to the right of the parent node, just adjust it upward once:

self& operator--()
{
    
    
	if (_node->left)//左边不为空
	{
    
    
		Node* mine = _node->left;
		while (mine->right)
		{
    
    
			mine = mine->right;
		}
		_node = mine;
	}	
	else//左边为空
	{
    
    
		Node* _parent = _node->parent;
		Node* cur = _node;
		while (_parent->left == cur && _parent != nullptr)
		{
    
    
			cur = _parent;
			_parent = _parent->parent;
		}
		_node = _parent;
	}
	return *this;
}

begin and end functions

The begin function of the red-black tree returns the leftmost node of the current tree. The begin function is implemented in the red-black tree class, so we have to add an iterator type to the red-black tree class, and then the return type of the begin function is an iterator type, then the code here is as follows:

iterator begin()
{
    
    
	Node* cur = root;
	while (cur->left)
	{
    
    
		cur = cur->left;
	}
	return iterator(cur);	
}

The end function simply constructs an iterator using a null pointer and returns it:

iterator end()
{
    
    
	return iterator(nullptr);
}

After the red-black tree iterator is completed, it can be used to encapsulate the iterators of map and set. Then the code here is as follows:

//set.h
template<class K>
class set
{
    
    
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
			return key;
		}
	};
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	iterator begin()
	{
    
    
		return tree.begin();
	}
	iterator end()
	{
    
    
		return tree.end();
	}
//....
private:
	RBTree<K, K, SetofKey> tree;
};
//map.h
template<class K, class V>
class map
{
    
    
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	iterator begin()
	{
    
    
		return tree.begin();
	}
	iterator end()
	{
    
    
		return tree.end();
	}
//....
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

We used the type iterator in RBTree in map and set, but we did not instantiate a red-black tree object before using it, so this will cause the compiler to not know whether the iterator is a type or a type when using this iterator type RBTree<K, pair<const K, V>, MapofKey>::iterator. A static variable, so here we have to add a typename to tell the compiler that although no object is instantiated here, this iterator is a type name.

code testing

After completing the above code, we can write a piece of code to test the correctness of the above code. First, test whether the insertion function and search function of the set are implemented correctly. Then the test code here is as follows:

#include"set.h"
int main()
{
    
    
	srand(time(0));
	set<int> s1;
	const size_t N = 100000;
	for (size_t i = 0; i < N; ++i)
	{
    
    
		size_t x = rand();
		s1.insert(x);
	}
	s1.insert(100);
	cout << s1.check() << endl;
	cout << s1.find(100) << endl;
	return 0;
}

The running result of the code is as follows:
Insert image description here
it meets our expectations, then let's test whether the iterator implementation of set is true, then the test code here is as follows:

int main()
{
    
    
	srand(time(0));
	set<int> s1;
	for (int i = 0; i < 100; i++)
	{
    
    
		int x = rand();
		s1.insert(x);
	}
	set<int>::iterator it = s1.begin();
	while (it != s1.end())
	{
    
    
		cout << *it << " ";
		++it;
	}
	return 0;
}

The running results of the code are as follows:
Insert image description here
If it meets our expectations, this means that the results of our code are correct. Next, we will see if the insert function and find function of map are implemented correctly:

#include"set.h"
#include"map.h"
int main()
{
    
    
	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 10000; i++)
	{
    
    
		int x = rand();
		m.insert(make_pair(x, x));
	}
	m.insert(make_pair(100, 100));
	cout << m.find(100) << endl;
	return 0;
}

The running results of the code are as follows:
Insert image description here
Let’s take a look at the iterator test code:

	map<int, int> m;
	srand(time(0));
	for (int i = 0; i <= 100; i++)
	{
    
    
		int x = rand();
		m.insert(make_pair(x, x));
	}
	map<int, int>::iterator it = m.begin();
	while (it != m.end())
	{
    
    
		cout << (*it).first << " ";
		++it;
	}

The result of running the code is as follows:

Insert image description here
If it meets our expectations, this means that the content of the current map iterator implementation is correct, but there is a problem in the implementation here. When the iterator reaches the end position, the internal _node pointer points to null. At this time, if we If you want to let him go back through - - it will go wrong, so this is a bug in the iterator above. If you want to modify this bug, you have to insert a head node into the tree like a library, so here we Without modification, let’s look at the implementation of const iterator.

const iterator

With the previous experience of list iterators, we know that if we want to use an iterator template to encapsulate const iterators and ordinary iterators, the iterator template needs three parameters, such as the following code:

template<class V,class Ref,class Ptr>

The first parameter represents the data type pointed to by the current iterator, the second parameter represents the data type of the reference, and the third parameter represents the data type of the pointer. After the template parameters of the iterator are modified, we have to Modify the iterator type renaming in the red-black tree, then the modified code here is as follows:

class RBTree
{
    
    
public:
//....
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
//....
}

Then the return value of the function must be modified in the iterator class. The * operator overload returns a reference to the node, so the return value of this function is modified to Ref, and the -> operator overload function returns the address of the internal data, so Modify its return value to Ptr, then the modified code is as follows:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
    
    
	typedef RBTreeNode<V> Node;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{
    
    }
	Ref operator++(){
    
    //...}
	Ref operator--(){
    
    //...}
	Ref operator*(){
    
    //...}
	Ptr operator->(){
    
    //...}
	bool operator !=(const self& s){
    
    //...}
};

Now that the internal iterator has changed, do the external set and map have to be modified a little bit? First, the set class has to add the const version of the begin function and end function, such as the following code:

template<class K>
class set
{
    
    
//...
public:
	typedef typename RBTree<K, K, SetofKey>::iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin(){
    
    return tree.begin();}
	iterator end(){
    
    return tree.end();}
	const_iterator begin() const{
    
    return tree.begin();}
	const_iterator end() const{
    
    return tree.end();}
private:
	RBTree<K, K, SetofKey> tree;
};

But there is a problem with this implementation. We know that the data inside the set has a very close relationship. If you modify the value of the internal data at will, it will cause an error in the relationship and make it no longer a search binary tree. For example, the following code:

int main()
{
    
    
	set<int> s;
	s.insert(1);
	s.insert(2);
	s.insert(3);
	s.insert(4);
	set<int>::iterator it = s.begin();
	cout << *it << endl;
	*it = 10;
	cout << *it << endl;
	return 0;
}

The running result of this code is as follows:
Insert image description here
Right, whether it is a const iterator or an ordinary iterator, the internal data of the set cannot be modified, so in essence, there should be no ordinary version of the iterator, but there are in the library. We There are also ordinary iterators in daily usage habits, so how to modify them here? However, the ordinary iterator of set is encapsulated with a const iterator. On the surface, it is an ordinary iterator but in fact it is a const iterator, but this modification will cause another problem. The begin and end functions of the previous ordinary version are both All returned are ordinary iterators of red-black trees, but after the above modification, the type of the return value has become a const iterator, so there will be a problem here that ordinary iterators cannot be converted into const iterators, so how What's the solution? But just remove the begin and end functions of the ordinary version, so that only the begin and end functions of the const version exist in the class, so that when calling, all red-black tree iterators of the const version are returned, then the code here is as follows :

template<class K>
class set
{
    
    
//...
public:
	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const{
    
    return tree.begin();}
	iterator end() const{
    
    return tree.end();}
	bool insert(const K& key){
    
    return tree.insert(key);}
	bool find(const K& key){
    
    return tree.find(key);}
	bool check(){
    
    return tree.IsBalance();}
private:
	RBTree<K, K, SetofKey> tree;
};

After such modification, the above test code cannot run normally:
Insert image description here
the map container cannot encapsulate both types of iterators into const iterators as above, because map allows users to modify the V value in each data, so in map The const iterator is the const iterator in the red-black tree, and the ordinary iterator is the ordinary iterator in the red-black tree, and two different versions of the begin and end functions must be provided, so the code here is as follows:

template<class K, class V>
class map
{
    
    
//....
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin(){
    
    return tree.begin();}
	iterator end(){
    
    return tree.end();}
	const_iterator begin() const{
    
    return tree.begin();}
	const_iterator end() const{
    
    return tree.end();}
	bool insert(const pair<K,V>& data) {
    
    return tree.insert(data);}
	bool find(const K& key)	{
    
    return tree.find(key);}
private:
	RBTree<K, pair<const K, V>, MapofKey> tree;
};

We can use the following code to test:

int main()
{
    
    
	map<string, int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::iterator it = m.begin();
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

The result of running this code is as follows:
Insert image description here

Implementation of square brackets

To implement square brackets, we have to modify the return value of the insert function so that it returns pair. Then the insert functions in the external map and set containers are as follows:

pair<iterator,bool> insert(const pair<K,V>& data)
{
    
    
	return tree.insert(data);
}

Then modify the insert function in the red-black tree, because the iterator in the pair points to the address of inserted data or the address of an existing element, and because the cur variable in the insert function will change with the red-black tree. Changes occur due to adjustment, so after we find the insertion position, we can create a variable to record the current position, so that when the value is returned, it can be returned. If the current element does not exist, the value of the second element will be true. If If the currently inserted value exists, the value of the second element will be false. Then the modified code here is as follows:

	pair<iterator,bool> insert(const V& _data)
	{
    
    
		if (root == nullptr)
		{
    
    
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
    
    
			if (kot(_cur->data) > kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
    
    
				return make_pair(iterator(_cur),false);
			}
		}
		_cur = new Node(_data);
		Node* newnode = _cur;
		//节点的调整
		//.....
		return make_pair(iterator(newnode)true);
	}

After this modification, we can implement the square bracket overloading function. First, create a pair to receive the return value of the insert function, and then return the second element of the first element in the pair. Then the code here as follows:

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

But this change will cause problems because the pair returned by the insert function of the set is a const iterator, and the pair returned by the insert function of the red-black tree is an ordinary iterator. The ordinary iterator cannot be converted into a const iterator, so it will appear here problem, then the solution in the library is to first receive the return value of the insertion function of the red-black tree. For example, the following code:

pair<iterator,bool> insert(const K& key)
{
    
    
	pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
}

Then we have to implement a function. The function of this function is to use an ordinary iterator to construct a const iterator. The creation method here is as follows:

template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
    
    
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;
	_RBTreeIterator( Node* node)
		:_node(node)
	{
    
    }
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{
    
    }
//....
};

We created a new type iterator, which is a rename of the iterator, but the parameter of this iterator is V, which means iterator is always an ordinary iterator. We also added a constructor, but this constructor The parameter is an ordinary iterator. If the current iterator is an ordinary iterator, this function is a copy constructor. If the current iterator is a const iterator, the meaning of this function is to use an ordinary iterator to construct a const iterator. Then we can use the following code to verify:

int main()
{
    
    
	map<string,  int> m;
	m.insert(make_pair("a", 1));
	m.insert(make_pair("b", 2));
	m.insert(make_pair("c", 3));
	m.insert(make_pair("d", 4));
	map<string, int>::const_iterator it = m.begin();//编译器优化
	cout << it->second << endl;
	it->second = 10;
	cout << it->second << endl;
	return 0;
}

Here, the ordinary iterator is assigned to a const iterator, and then the const iterator wants to modify the value. The result of this code is as follows: Obviously this code is
Insert image description here
wrong, but the reason for the error is that the const iterator cannot If there is an error in making a modification instead of a normal iterator to construct a const iterator, then this means that our code implementation is correct, and finally use the following code to test:

int main()
{
    
    
	string arr[] = {
    
     "苹果","苹果", "苹果", "苹果", "苹果",
				  "香蕉","香蕉", "香蕉", "香蕉", "香蕉",
					"西瓜","西瓜", "西瓜", "梨子", "梨子" };
	map<string, int> m;
	for (auto ch : arr)
	{
    
    
		m[ch]++;
	}
	for (auto ch : m)
	{
    
    
		cout << ch.first << ":" << ch.second << endl;
	}
	return 0;
}

The results of running the code are as follows:
Insert image description here
in line with our expectations, which means that there is no problem with the implementation of our code. The complete code is as follows:
The code of the RBTree.h file is as follows:

#pragma once
#include<iostream>
using namespace std;
enum colour
{
    
    
	RED,
	BLACK,
};
template<class T>
struct RBTreeNode
{
    
    
	RBTreeNode(const T& _data)
		:parent(nullptr)
		, right(nullptr)
		, left(nullptr)
		, data(_data)
		, _col(RED)
	{
    
    }
	RBTreeNode<T>* parent;
	RBTreeNode<T>* right;
	RBTreeNode<T>* left;
	T data;
	colour _col;
};
template<class V,class Ref,class Ptr>
struct _RBTreeIterator
{
    
    
	typedef RBTreeNode<V> Node;
	typedef _RBTreeIterator<V, Ref, Ptr> self;
	typedef _RBTreeIterator<V, V&, V*> iterator;
	Node* _node;

	_RBTreeIterator( Node* node)
		:_node(node)
	{
    
    }
	_RBTreeIterator(const iterator& s)
		:_node(s._node)
	{
    
    }
	self& operator++()
	{
    
    
		if (_node->right)
		{
    
    
			Node* mine = _node->right;
			while (mine->left)
			{
    
    
				mine = mine->left;
			}
			_node = mine;
		}
		else
		{
    
    
			Node* parent = _node->parent;
			Node* cur = _node;
			while (parent != nullptr && parent->right == cur)
			{
    
    
				cur = parent;
				parent = parent->parent;
			}
			_node = parent;
		}
		return *this;
	}
	Ref operator--()
	{
    
    
		if (_node->left)//左边不为空
		{
    
    
			Node* mine = _node->left;
			while (mine->right)
			{
    
    
				mine = mine->right;
			}
			_node = mine;
		}
		else//左边为空
		{
    
    
			Node* _parent = _node->parent;
			Node* cur = _node;
			while (_parent != nullptr&&_parent->left == cur )
			{
    
    
				cur = _parent;
				_parent = _parent->parent;
			}
			_node = _parent;
		}
		return *this;
	}
	Ref operator*()
	{
    
    
		return _node->data;
	}
	Ptr operator->()
	{
    
    
		return &_node->data;
	}
	bool operator !=(const self& s)
	{
    
    
		return _node != s._node;
	}
};
template<class K, class V, class KeyofT>
class RBTree
{
    
    

public:
	typedef RBTreeNode<V> Node;

	typedef _RBTreeIterator<V, V&, V*> iterator;
	typedef _RBTreeIterator<V,const V&,const V*> const_iterator;
	RBTree()
		:root(nullptr)
	{
    
    }
	iterator begin()
	{
    
    
		Node* cur = root;
		while (cur->left)
		{
    
    
			cur = cur->left;
		}
		return iterator(cur);
	}
	iterator end()
	{
    
    
		return iterator(nullptr);
	}
	pair<iterator,bool> insert(const V& _data)
	{
    
    
		if (root == nullptr)
		{
    
    
			root = new Node(_data);
			root->_col = BLACK;
			return make_pair(iterator(root), true);
		}
		KeyofT kot;
		Node* _cur = root;
		Node* _parent = nullptr;
		while (_cur != nullptr)
		{
    
    
			if (kot(_cur->data) > kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->left;
			}
			else if (kot(_cur->data) < kot(_data))
			{
    
    
				_parent = _cur;
				_cur = _cur->right;
			}
			else
			{
    
    
				return make_pair(iterator(_cur),false);
			}
		}
		_cur = new Node(_data);
		Node* newnode = _cur;
		if (kot(_data) > kot(_parent->data))
			//如果插入的数据比parent的数据大
		{
    
    
			_parent->right = _cur;
			_cur->parent = _parent;
		}
		else
			//如果插入的数据比parent的数据笑
		{
    
    
			_parent->left = _cur;
			_cur->parent = _parent;
		}
		while (_parent != nullptr && _parent->_col == RED)
		{
    
    
			Node* _grandparent = _parent->parent;
			if (_grandparent->left == _parent)
				//祖先节点的左边要调整
			{
    
    
				Node* uncle = _grandparent->right;//parent在左边所以uncle在右边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
    
    
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
    
    
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
    
    
					//情况二
					if (_cur == _parent->left)
					{
    
    
						RototalR(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
    
    
						RototalL(_parent);
						RototalR(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
			else
				//祖先节点的右边要调整
			{
    
    
				Node* uncle = _grandparent->left;//parent在右边所以uncle在左边
				//情况一
				if (uncle != nullptr && uncle->_col == RED)
				{
    
    
					uncle->_col = _parent->_col = BLACK;
					if (_grandparent->parent != nullptr)
					{
    
    
						_grandparent->_col = RED;
					}
					_cur = _grandparent;
					_parent = _grandparent->parent;
				}
				else
				{
    
    
					//情况二
					if (_cur == _parent->right)
					{
    
    
						RototalL(_grandparent);
						_parent->_col = BLACK;
						_grandparent->_col = RED;
					}
					else//情况三
					{
    
    
						RototalR(_parent);
						RototalL(_grandparent);
						_cur->_col = BLACK;
						_grandparent->_col = RED;
					}
					break;
				}
			}
		}
		return make_pair(iterator(newnode),true);
	}
	void inorder()
	{
    
    
		_inorder(root);
	}
	bool find(const K& val)
	{
    
    
		Node* cur = root;
		KeyofT kot;
		while (cur)
		{
    
    
			if ( kot(cur->data) == val)
			{
    
    
				return true;
			}
			else if (kot(cur->data) > val)
			{
    
    
				cur = cur->left;
			}
			else
			{
    
    
				cur = cur->right;
			}
		}
		return false;
	}
	bool check(Node* root, int BlackNums, int ref)
	{
    
    
		if (root == nullptr)
		{
    
    
			if (ref != BlackNums)
			{
    
    
				cout << "违反规则" << endl;
				return false;
			}
			return true;
		}
		if (root->_col == RED && root->parent->_col == RED)
		{
    
    
			cout << "出现了连续红色节点" << endl;
		}
		if (root->_col == BLACK)
		{
    
    
			BlackNums++;
		}
		return check(root->left, BlackNums, ref) && check(root->right, BlackNums, ref);
	}
	bool IsBalance()
	{
    
    
		if (root == nullptr)
		{
    
    
			return true;
		}
		if (root->_col != BLACK)
		{
    
    
			return false;
		}
		int ref = 0;
		Node* _left = root;
		while (_left)
		{
    
    
			if (_left->_col == BLACK)
			{
    
    
				ref++;
			}
			_left = _left->left;
		}
		return check(root, 0, ref);

	}
private:
	void _inorder(const Node* root)
	{
    
    
		if (root == nullptr)
		{
    
    
			return;
		}
		_inorder(root->left);
		cout << root->key << " ";
		_inorder(root->right);
	}
	void RototalL(Node* _parent)
	{
    
    
		Node* subR = _parent->right;//右孩子的节点
		Node* subRL = subR->left;//右孩子的左节点
		Node* ppNode = _parent->parent;//祖父节点
		//把subRL放到_parent的右
			_parent->right = subRL;
		if (subRL)
		{
    
    
			//如果subRL不为空则修改父节点的指向
				subRL->parent = _parent;
		}
		//把_parent放到subR的左
			subR->left = _parent;
		//修改_parent的parent的指向
			_parent->parent = subR;
		if (ppNode)//如果祖父不为空,则要改变祖父的指向
		{
    
    
			if (ppNode->right == _parent)
			{
    
    
				ppNode->right = subR;
				subR->parent = ppNode;
			}
			else//如果_parent是祖父的左
			{
    
    
				ppNode->left = subR;
				subR->parent = ppNode;
			}
		}
		else//祖父为空节点说明当前调整的是根节点
		{
    
    
			root = subR;
			subR->parent = nullptr;
		}
	}
	//右旋转
	void RototalR(Node* _parent)
	{
    
    
		Node* subL = _parent->left;
		Node* subLR = subL->right;
		Node* ppNode = _parent->parent;
		_parent->left = subLR;
		if (subLR)
		{
    
    
			subLR->parent = _parent;
		}
		subL->right = _parent;
		_parent->parent = subL;
		if (ppNode != nullptr)
		{
    
    
			if (ppNode->right == _parent)
			{
    
    
				ppNode->right = subL;
				subL->parent = ppNode;
			}
			else
			{
    
    
				ppNode->left = subL;
				subL->parent = ppNode;
			}
		}
		else
		{
    
    
			root = subL;
			subL->parent = nullptr;
		}
	}

	Node* root;
};

The code of the set.h file is as follows:

#pragma once
#include"RBTree.h"
template<class K>
class set
{
    
    
	struct SetofKey
	{
    
    
		const K& operator()(const K& key)
		{
    
    
			return key;
		}
	};
public:

	typedef typename RBTree<K, K, SetofKey>::const_iterator iterator;
	typedef typename RBTree<K, K, SetofKey>::const_iterator const_iterator;
	iterator begin() const
	{
    
    
		return tree.begin();
	}
	iterator end() const
	{
    
    
		return tree.end();
	}
	pair<iterator,bool> insert(const K& key)
	{
    
    
		pair<typename RBTree<K, K, SetofKey>::iterator, bool> ret = tree.insert(key);
	}
	bool find(const K& key)
	{
    
    
		return tree.find(key);
	}
	bool check()
	{
    
    
		return tree.IsBalance();
	}
private:
	RBTree<K, K, SetofKey> tree;
};

The large code of the map.h file is as follows:

#pragma once
#include"RBTree.h"
template<class K, class V>
class map
{
    
    
	struct MapofKey
	{
    
    
		const K& operator()(const pair<const K, V>& kv)
		{
    
    
			return kv.first;
		}
	};
public:
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::iterator iterator;
	typedef typename RBTree<K, pair<const K, V>, MapofKey>::const_iterator const_iterator;
	iterator begin()
	{
    
    
		return tree.begin();
	}
	iterator end()
	{
    
    
		return tree.end();
	}
	const_iterator begin() const
	{
    
    
		return tree.begin();
	}
	const_iterator end() const
	{
    
    
		return tree.end();
	}
	pair<iterator,bool> insert(const pair<K,V>& data)
	{
    
    
		return tree.insert(data);
	}
	bool find(const K& key)
	{
    
    
		return tree.find(key);
	}
	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>, MapofKey> tree;
};

Guess you like

Origin blog.csdn.net/qq_68695298/article/details/131277255