C++ grammar - encapsulation principle of map and set

Table of contents

1. Data type encapsulation

(1). Encapsulation method

(2). How to compare keys after encapsulation

 2. Iterator encapsulation

(1). Bottom iterator (in red-black tree)

①Iterator++

②Iterator-- 

(one).begin&end&const


1. Data type encapsulation

(1). Encapsulation method

We know that both map and set use red-black trees, but map is a key&value model, and set is a key model.

Map needs to pass two types and set only needs to pass one type. How can this be compatible in the underlying red-black tree?

Through the SGI version of stl_tree.h, we can see that the red-black tree has two template parameters representing key and value respectively. Here comes the point:

For map, key and pair<key, value> need to be passed in to the key and value of the red-black tree respectively.

The set needs to pass the key and the key to the key and value of the red-black tree.

Look at the source code of map and set again (partially intercepted):

 If it's homemade, it's roughly like this:

template<class K, class V>
class Map
{
	typedef rb_tree<K, pair<K, V>, MapOfV<K, pair<K, V>>> tree;
public:
	//...

private:
	tree _t;
};
template<class K>
class Set
{
	typedef rb_tree<K, K, SetOfV<K>> tree;
public:
	//...

private:
	tree _t;
};

(2). How to compare keys after encapsulation

But there is a problem with this kind of encapsulation, that is, in the underlying red-black tree, the value is directly compared, so what should I do?

Use the functor to get the key value in the value to solve.

For map, the functor is passed to the red-black tree, and the functor is called to get the key value in the value (actually pair<key, value>) .

For set, the values ​​in key and value are actually keys , and you can also pass the functor to get it.

Therefore, for the above code we see that map and set pass in the functors MapOfV and SetOfV respectively.

Functors are all implemented in the upper layer (map/set) and passed to the underlying red-black tree.

The code implementation is as follows (simple):

template<class K, class V>
//传入分别是key和pair<key, value>
//typedef rb_tree<K, pair<K, V>, MapOfV<K, pair<K, V>>> tree;
class MapOfV
{
public:
	K operator()(V kv) const
	{
		return kv.first;
	}
};
template<class K>
//typedef rb_tree<K, K, SetOfV<K>> tree;
class SetOfV
{
public:
	K operator()(K k) const
	{
		return k;
	}
};

 2. Iterator encapsulation

Because the bottom layers of map and set are red-black trees, the iterators are implemented in the red-black tree (bottom layer), and map and set just call the iterators of the red-black tree .

(1). Bottom iterator (in red-black tree)

Because the red-black tree is also composed of nodes, it is easy to think of the iterator of the list. The list iterator is encapsulated by a class, and a pointer is defined inside the iterator to point to the node. The bottom layer of the operation on the iterator is the operation on the internal pointer of the iterator.

The iterator of the red-black tree is similar. It also uses a class to represent the iterator, and there is a pointer to the tree node inside. As long as you know the list iterator, you can figure it out. If you don’t know it, you can read this article: Why is the iterator of List a class template?

The encapsulation of dereference * and arrow -> is the same as that of list, which is directly converted into the operation of node pointer.

The key lies in ++ and --. 

①Iterator++

The red-black tree is also a binary tree, so let's take the binary tree as an example:


 There are two situations here: 

 The first type: the node has a right subtree, and at this time it is necessary to go down.

Take node 1 as an example, because there is a right subtree, after ++ is node 6? --wrong!

Binary tree iteration is the way of pre-order traversal . After ++, it should be node 5, which is the leftmost node of the right subtree!

The second type: the node does not have a right subtree, and at this time it is necessary to go up.

Comparing nodes 3 and 5, they are both leaf nodes, but after node 3++ is node 1, and after node 5++ is node 6.

Conclusion: If the current node is the left subtree of the parent node, then ++ is the parent node . If it is the right subtree, then you need to search up until the current node is the left subtree of the parent node , at this time the parent node is the node after ++.

If it is node 7, then ++ will always judge to the root node 4, therefore, the iterator end() can be set to root's parent node (null).

The principle is very simple. If it is the right child node, it means that for the parent node, you will find the node after ++, but for yourself, it is impossible to be the parent node after ++.

code show as below:

Self operator++()
{
	goBack();
	return *this;
}
void goBack()
{
	if (_node->right)//如果右子节点不为空
	{
		Node* right = _node->right;
		while (right->left)//直到找到右子树最左节点
		{
			right = right->left;
		}
		_node = right;
	}
	else//右子树为空
	{
		Node* parent = _node->parent, * cur = _node;
		while (parent && cur == parent->right)//直到cur是parent的左子节点为止
		{
			cur = parent;
			parent = cur->parent;
		}
		_node = parent;
	}
}

②Iterator-- 

With the understanding of ++, -- the operation is simple.

Still take this tree as an example:
 there are two situations:
the first one: there is a left subtree, go down.

Take the root node 4 as an example, after -- it is not node 2 but node 3. For a node with a left subtree, the node after -- is the rightmost child node of the left subtree.

The second type: there is no left subtree, go up.

Comparing node 3 and node 5, node 3--followed by node 2, node 5--followed by node 4. It can be seen that if the node is the right child node of the parent node, then -- is the parent node after that . But if it is a left child node, then it needs to judge upwards until it is the right child node of the parent node . Or, like node 1, judge until the root node.

The principle is that if it is the left child node of the parent node, then the parent node is behind itself, and if it is the right child node, it is behind the parent node.

code show as below:

Self operator--(int)
{
	Self it(_node);
	goHead();
	return it;
}
void goHead()
{
	if (_node->left)//有左子树
	{
		Node* right = _node->left;
		while (right->right)//直到左子树的最右子节点
		{
			right = right->right;
		}
		_node = right;
	}
	else//无左子树
	{
		Node* parent = _node->parent, * cur = _node;
		while (parent&& cur = parent->left)//直到cur是parent的右子节点
		{
			cur = parent;
			parent = cur->parent;
		}
		_node = parent;
	}
}

(one).begin&end&const

The begin and end of the iterator can only be implemented in rb_tree. For begin, it returns the iterator of the leftmost node of the red-black tree, and end returns the iterator of the parent node of the root (empty node iterator).

For const, first of all, we should know that neither map nor set supports modification of key .

But map and set are very different in implementation .

Map adopts const key when it is passed in, while set adopts iterators to use the underlying const iterator .

A few hours of planning can save weeks of programming - Unnamed


Please correct me if there is any mistake

Guess you like

Origin blog.csdn.net/weixin_61857742/article/details/127975541