An article teaches you how to use red-black trees to encapsulate map and set

Please add image description

Add code for red-black tree iterator

First of all, we can reuse the red-black tree code written in the previous section and modify it slightly on the original basis. Here we only explain the implementation of the iterator function of map and set, but it is not fully implemented. The purpose is to deepen Learn the underlying principles of map and set

1. Map and set universal template iterator structure definition

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
    
    
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;
};

This code is an iterator defined to simultaneously implement the red-black tree structure of std::mapand . std::setIn STL, std::mapboth std::setuse red-black trees as the underlying data structure, but there are some differences between them, mainly between element types and key-value pairs.

The purpose of this iterator definition is to implement a universal iterator for red-black trees without duplicating the iterators for two different containers, so that it can be used for both std::mapand std::set. Let me explain why such a definition is needed:

  1. Universality : The definition of this iterator makes it applicable to different data types T, whether it is std::mapa key-value pair or std::setan element. This versatility is extremely valuable because it allows you to use the same iterator in different contexts.
  2. Code reuse : By using universal iterators, STL can avoid rewriting similar code between iterators in different containers. This simplifies STL implementation and reduces code redundancy.
  3. Consistency : STL pursues consistency and hopes that users can std::mapuse iterators like std::setiterators of or vice versa. This makes STL easier to understand and use.

Therefore, the purpose of this iterator definition is to achieve universality, code reuse and consistency to provide a better STL experience. In STL it is common to see generic iterators of this type to simplify the implementation of different containers.

2. Iterator copy construction

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

This constructor is __RBTreeIteratorthe constructor of the iterator, which accepts a pointer to a red-black tree node as a parameter and stores this pointer in a _nodemember variable.

The main purpose of the constructor is to initialize the state of the iterator so that it points to a specific node in the red-black tree, so that the iterator can correctly traverse the elements in the red-black tree. When the iterator is created, you can pass in a node pointer, which will become the starting position of the iterator.

The design of this constructor allows you to specify a starting position when creating the iterator so that the traversal starts from a specific node in the red-black tree. This is a common design when implementing iterators for red-black trees, since you may want to start traversing the tree from the root node, a specific node, or some other location.

3. Iterator dereference overloading

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

This operator*function is the dereference operator overload for iterators. Its purpose is to return a reference to the element pointed to by the current iterator, allowing you to access the value of the element through the iterator.

In this specific implementation, _node->_datait is the element value stored in the red-black tree node, and its type is T. operator*The function returns a reference to the element value, so you can read or modify the value of the current node through the iterator.

Normally, dereference operator overloading allows you to access the object pointed to by the iterator in a pointer-like manner. This allows you to access the data just like you access the container elements when using the iterator to traverse the container, which enhances the readability of the code. performance and ease of use. In STL and C++, the behavior of iterators often mimics that of pointers, so you can manipulate elements using pointer-like syntax.

4. Iterator arrow overloading

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

This operator->function is the iterator's arrow member access operator overload. Its purpose is to return a pointer to the element pointed to by the current iterator so that you can access the members of the element through the iterator.

In this specific implementation, _node->_datait is the element value stored in the red-black tree node, and its type is T. operator->The function returns a pointer to the element value, so you can use the arrow operator to access the element's member variables or call its member functions.

Overloads of this operator allow you to access the members of the element pointed to by the iterator in a more direct way. This is useful when you need to access an element's internal data or call its methods.

For example, if your element is an object of a custom class, you can use ->the operator to access the object's member functions as if you were manipulating the object. This provides a more natural and convenient way to access elements.

5. Iterator is not equal to overloading

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

This operator!=function is an overloaded function of the inequality operator. Its purpose is to compare whether the current iterator and another iterator sare not equal.

In this specific implementation, it compares _nodethe member variables, and if the two iterators _nodeare not equal, it returns true, indicating that they are not equal; otherwise, it returns false, indicating that they are equal.

This operator overload allows you to use the inequality operator !=to compare two iterators to determine whether they point to different locations. This is useful for looping over the elements in a container or determining whether an iterator has reached the end of a container. Typically, you use this operator to control loop termination conditions.

6. Iterator equality judgment overloading

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

This operator==function is an overloaded function of the equals operator. Its purpose is to compare the current iterator and another iterator sfor equality.

In this specific implementation, it compares _nodethe member variables, and if the two iterators _nodeare equal, it returns true, indicating that they are equal; otherwise, it returns false, indicating that they are not equal.

This operator overload allows you to use the equals operator ==to compare two iterators to determine whether they point to the same location. This is useful for checking iterator equality and determining whether the end of a container has been reached. Typically, you use this operator to perform equality checks on iterators.

7. Iterator++ overloading

Self& operator++()
{
    
    
    if (_node->_right)
    {
    
    
        // 下一个就是右子树的最左节点
        Node* left = _node->_right;
        while (left->_left)
        {
    
    
            left = left->_left;
        }

        _node = left;
    }
    else
    {
    
    
        // 找祖先里面孩子不是祖先的右的那个
        Node* parent = _node->_parent;
        Node* cur = _node;
        while (parent && cur == parent->_right)
        {
    
    
            cur = cur->_parent;
            parent = parent->_parent;
        }

        _node = parent;
    }

    return *this;
}

This operator++function is an overloaded function of the prepended increment operator, used to make the iterator point to the next element. In the iterator of the red-black tree, it implements the forward operation of the iterator.

This function works as follows:

  1. If the right subtree of the current node exists, the iterator will be moved to the leftmost node of the right subtree. This is because in a red-black tree, the leftmost node of the right subtree is the next node larger than the current node.
  2. If the current node has no right subtree, the iterator moves up to ancestor nodes until it finds an ancestor node whose right child is not equal to the current node. This is because in a red-black tree, if the current node has no right subtree, the next node is either its parent node or the left child node of one of its ancestor nodes.

The purpose of this function is to make the iterator point to the next element to achieve sequential traversal of the red-black tree. In STL, the prepended increment operator is usually used to move an iterator to the next element in a container.

8. Iterator – overloading

Self& operator--()
{
    
    
    if (_node->_left)
    {
    
    
        // 下一个是左子树的最右节点
        Node* right = _node->_left;
        while (right->_right)
        {
    
    
            right = right->_right;
        }

        _node = right;
    }
    else
    {
    
    
        // 孩子不是父亲的左的那个祖先
        Node* parent = _node->_parent;
        Node* cur = _node;
        while (parent && cur == parent->_left)
        {
    
    
            cur = cur->_parent;
            parent = parent->_parent;
        }

        _node = parent;
    }

    return *this;
}

This operator--function is an overloaded function of the preceding decrement operator, which is used to make the iterator point to the previous element. In the iterator of the red-black tree, it implements the backward operation of the iterator.

This function works operator++similarly to the function, but in the opposite direction:

  1. If a left subtree of the current node exists, the iterator will be moved to the rightmost node of the left subtree. This is because in a red-black tree, the rightmost node of the left subtree is the previous node smaller than the current node.
  2. If the current node has no left subtree, the iterator moves up to ancestor nodes until it finds an ancestor node whose left child is not equal to the current node. This is because in a red-black tree, if the current node has no left subtree, the previous node is either its parent node or the right child node of one of its ancestor nodes.

The purpose of this function is to make the iterator point to the previous element to achieve reverse order traversal of the red-black tree. In STL, the prepended decrement operator is typically used to move an iterator forward to the previous element in a container.

Modify the members and member functions of the red-black tree structure

1. Modify the member definition of the red-black tree structure

template<class K, class T, class KeyOfT>
struct RBTree
{
    
    
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;
private:
	Node* _root = nullptr;
};

Here is renaming the template iterator to iterator in the red-black tree structure

2. Add iterator begin() and end() functions

iterator begin()
{
    
    
    Node* left = _root;
    while (left && left->_left)
    {
    
    
        left = left->_left;
    }

    return iterator(left);
}

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

begin()These two functions are member functions used to create red-black tree iterators, and are usually used to implement the iterator's and functions in your red-black tree container class end().

  1. begin()The function returns an iterator pointing to the smallest element in the red-black tree. It starts from the root node and moves downward along the path of the left subtree until it finds the leftmost leaf node, which is the smallest element. It then creates an iterator object, passes the node pointer of the smallest element to the iterator's constructor, and finally returns the iterator.
  2. end()The function is used to return an iterator representing the end position of the red-black tree. Normally, the end position is a null pointer ( nullptr ). This function directly creates an iterator object, passes a null pointer to the iterator's constructor, and returns the iterator.

The implementation of these two functions allows you to use standard iterator syntax to traverse the elements in the red-black tree. For example, you could use to begin()get an iterator pointing to the first element, then use to end()get an iterator indicating the end position, and then increment the iterator in a loop to traverse the entire red-black tree. This is very similar to the iterator usage of STL containers, making it more convenient and unified when dealing with red-black trees.

3. Modify the red-black tree Insert function

pair<iterator, bool> Insert(const T& data)
{
    
    
    KeyOfT kot;

    if (_root == nullptr)
    {
    
    
        _root = new Node(data);
        _root->_col = BLACK;
        return make_pair(iterator(_root), true);
    }

    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 make_pair(iterator(cur), 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* grandfater = parent->_parent;
        assert(grandfater);
        assert(grandfater->_col == BLACK);
        // 关键看叔叔
        if (parent == grandfater->_left)
        {
    
    
            Node* uncle = grandfater->_right;
            // 情况一 : uncle存在且为红,变色+继续往上处理
            if (uncle && uncle->_col == RED)
            {
    
    
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }// 情况二+三:uncle不存在 + 存在且为黑
            else
            {
    
    
                // 情况二:右单旋+变色
                if (cur == parent->_left)
                {
    
    
                    RotateR(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
    
    
                    // 情况三:左右单旋+变色
                    //     g 
                    //   p   u
                    //     c
                    RotateL(parent);
                    RotateR(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }
        else // (parent == grandfater->_right)
        {
    
    
            Node* uncle = grandfater->_left;
            // 情况一
            if (uncle && uncle->_col == RED)
            {
    
    
                parent->_col = uncle->_col = BLACK;
                grandfater->_col = RED;
                // 继续往上处理
                cur = grandfater;
                parent = cur->_parent;
            }
            else
            {
    
    
                // 情况二:左单旋+变色
                if (cur == parent->_right)
                {
    
    
                    RotateL(grandfater);
                    parent->_col = BLACK;
                    grandfater->_col = RED;
                }
                else
                {
    
    
                    // 情况三:右左单旋+变色
                    RotateR(parent);
                    RotateL(grandfater);
                    cur->_col = BLACK;
                    grandfater->_col = RED;
                }

                break;
            }
        }

    }

    _root->_col = BLACK;
    return make_pair(iterator(newnode), true);
}

After modification, its return value is one pair, which contains an iterator and a Boolean value to indicate whether the insertion was successful.

Main steps of the function:

  1. First, check whether the red-black tree is empty. If it is empty, create a new node directly, _rootdye it black (the root node must be black), and then return an iterator containing an iterator pointing to the new node and true, pairindicating that the insertion is successful. .
  2. If the red-black tree is not empty, enter the loop of inserting elements. In the loop, the current node and the data to be inserted are first compared via KeyOfTthe object kotto determine whether the new node should be inserted into the left or right subtree, or whether the same data already exists. If the same data is found, the function returns an iterator containing an iterator pointing to the same data and false, pairindicating that the insertion failed.
  3. If the same data is not found, a new node curis created, colored red, and inserted into the appropriate location, similar to a standard binary search tree insertion operation.
  4. After the insertion is completed, the function enters the red-black tree correction loop to ensure that the properties of the red-black tree are met. In the correction loop, it first checks the colors of the current node's parent and grandparent nodes to determine which corrective action needs to be taken. Specific correction operations include color transformation and rotation operations to maintain the balance and properties of the red-black tree.
  5. Finally, _rootforce the color of the root node to black to ensure that the root node is always black. Then it returns an iterator containing an iterator pointing to the newly inserted node and true, pairindicating that the insertion is successful.

The implementation of this function follows the insertion rules and modification strategies of the red-black tree to maintain the balance and properties of the red-black tree. It returns an iterator that can be used to access the newly inserted element, and a Boolean value indicating whether the insertion was successful.

All codes of red-black tree after modification

#pragma once
#include<iostream>
#include<assert.h>
#include<time.h>
using namespace std;



enum Colour
{
    
    
	RED,
	BLACK
};

template<class T>
struct RBTreeNode
{
    
    
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;

	T _data;
	Colour _col;

	RBTreeNode(const T& data)
		:_left(nullptr)
		, _right(nullptr)
		, _parent(nullptr)
		, _data(data)
	{
    
    }
};

template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
    
    
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;
	Node* _node;

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

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

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

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

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

	Self& operator++()
	{
    
    
		if (_node->_right)
		{
    
    
			// 下一个就是右子树的最左节点
			Node* left = _node->_right;
			while (left->_left)
			{
    
    
				left = left->_left;
			}

			_node = left;
		}
		else
		{
    
    
			// 找祖先里面孩子不是祖先的右的那个
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_right)
			{
    
    
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}

	Self& operator--()
	{
    
    
		if (_node->_left)
		{
    
    
			// 下一个是左子树的最右节点
			Node* right = _node->_left;
			while (right->_right)
			{
    
    
				right = right->_right;
			}

			_node = right;
		}
		else
		{
    
    
			// 孩子不是父亲的左的那个祖先
			Node* parent = _node->_parent;
			Node* cur = _node;
			while (parent && cur == parent->_left)
			{
    
    
				cur = cur->_parent;
				parent = parent->_parent;
			}

			_node = parent;
		}

		return *this;
	}
};

template<class K, class T, class KeyOfT>
struct RBTree
{
    
    
	typedef RBTreeNode<T> Node;
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;

	iterator begin()
	{
    
    
		Node* left = _root;
		while (left && left->_left)
		{
    
    
			left = left->_left;
		}

		return iterator(left);
	}

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

	pair<iterator, bool> Insert(const T& data)
	{
    
    
		KeyOfT kot;

		if (_root == nullptr)
		{
    
    
			_root = new Node(data);
			_root->_col = BLACK;
			return make_pair(iterator(_root), true);
		}

		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 make_pair(iterator(cur), 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* grandfater = parent->_parent;
			assert(grandfater);
			assert(grandfater->_col == BLACK);
			// 关键看叔叔
			if (parent == grandfater->_left)
			{
    
    
				Node* uncle = grandfater->_right;
				// 情况一 : uncle存在且为红,变色+继续往上处理
				if (uncle && uncle->_col == RED)
				{
    
    
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}// 情况二+三:uncle不存在 + 存在且为黑
				else
				{
    
    
					// 情况二:右单旋+变色
					if (cur == parent->_left)
					{
    
    
						RotateR(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
    
    
						// 情况三:左右单旋+变色
						RotateL(parent);
						RotateR(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}
			else // (parent == grandfater->_right)
			{
    
    
				Node* uncle = grandfater->_left;
				// 情况一
				if (uncle && uncle->_col == RED)
				{
    
    
					parent->_col = uncle->_col = BLACK;
					grandfater->_col = RED;
					// 继续往上处理
					cur = grandfater;
					parent = cur->_parent;
				}
				else
				{
    
    
					// 情况二:左单旋+变色
					if (cur == parent->_right)
					{
    
    
						RotateL(grandfater);
						parent->_col = BLACK;
						grandfater->_col = RED;
					}
					else
					{
    
    
						// 情况三:右左单旋+变色
						RotateR(parent);
						RotateL(grandfater);
						cur->_col = BLACK;
						grandfater->_col = RED;
					}

					break;
				}
			}

		}

		_root->_col = BLACK;
		return make_pair(iterator(newnode), true);
	}

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

	bool IsBalance()
	{
    
    
		if (_root == nullptr)
		{
    
    
			return true;
		}

		if (_root->_col == RED)
		{
    
    
			cout << "根节点不是黑色" << endl;
			return false;
		}

		// 黑色节点数量基准值
		int benchmark = 0;

		return PrevCheck(_root, 0, benchmark);
	}

private:
	bool PrevCheck(Node* root, int blackNum, int& benchmark)
	{
    
    
		if (root == nullptr)
		{
    
    
			if (benchmark == 0)
			{
    
    
				benchmark = blackNum;
				return true;
			}

			if (blackNum != benchmark)
			{
    
    
				cout << "某条黑色节点的数量不相等" << endl;
				return false;
			}
			else
			{
    
    
				return true;
			}
		}

		if (root->_col == BLACK)
		{
    
    
			++blackNum;
		}

		if (root->_col == RED && root->_parent->_col == RED)
		{
    
    
			cout << "存在连续的红色节点" << endl;
			return false;
		}

		return PrevCheck(root->_left, blackNum, benchmark)
			&& PrevCheck(root->_right, blackNum, benchmark);
	}

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

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

	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 (_root == parent)
		{
    
    
			_root = subR;
			subR->_parent = nullptr;
		}
		else
		{
    
    
			if (ppNode->_left == parent)
			{
    
    
				ppNode->_left = subR;
			}
			else
			{
    
    
				ppNode->_right = subR;
			}

			subR->_parent = ppNode;
		}

	}

	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 (_root == parent)
		{
    
    
			_root = subL;
			subL->_parent = nullptr;
		}
		else
		{
    
    
			if (ppNode->_left == parent)
			{
    
    
				ppNode->_left = subL;
			}
			else
			{
    
    
				ppNode->_right = subL;
			}

			subL->_parent = ppNode;
		}

	}

private:
	Node* _root = nullptr;
};

Simple encapsulation map

1. map encapsulation code

#pragma once
#include "RBTree.hpp"

namespace yulao
{
    
    
	template<class K, class V>
	class map
	{
    
    
		struct MapKeyOfT
		{
    
    
			const K& operator()(const pair<K, V>& kv)
			{
    
    
				return kv.first;
			}
		};
	public:
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;

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

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

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

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

Use an underlying red-black tree _tto store key-value pairs ( pair<K, V>). The following are mapthe main features and functions of this class:

  1. Constructor: mapThe class does not implement a constructor in the provided code, but you can add it yourself.
  2. Iterator: mapThe class defines a nested iteratortype. This iterator is an iterator in a red-black tree. begin()The function returns an iterator to the smallest element in the red-black tree, while end()the function returns an iterator indicating the end position. This allows you to use iterators to iterate mapover the key-value pairs in .
  3. Insert operation: insertThe function is used to mapinsert key-value pairs into . It performs the insertion operation by calling the function _tof the underlying red-black tree Insertand returns an pairobject containing an iterator and a boolean value. The iterator points to the inserted element, and a Boolean value indicates whether the insertion was successful. The returned Boolean value is if the inserted key already exists, falseotherwise true.
  4. Access operations: operator[]Functions are used to access mapvalues ​​in by key. It first calls inserta function that attempts to insert a key-value pair, and then returns a reference to the value of the inserted element. If the key already exists, the new element will not be inserted, but a reference to the existing element will be returned.

2. Test map packaging

void test_map()
{
    
    
    string arr[] = {
    
     "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };

    map<string, int> countMap;
    for (auto& str : arr)
    {
    
    
        // 1、str不在countMap中,插入pair(str, int()),然后在对返回次数++
        // 2、str在countMap中,返回value(次数)的引用,次数++;
        countMap[str]++;
    }

    map<string, int>::iterator it = countMap.begin();
    while (it != countMap.end())
    {
    
    
        cout << it->first << ":" << it->second << endl;
        ++it;
    }

    for (auto& kv : countMap)
    {
    
    
        cout << kv.first << ":" << kv.second << endl;
    }
}

Output results

苹果:6
西瓜:3
香蕉:2
苹果:6
西瓜:3
香蕉:2

Simple package set

1. set encapsulation code

#pragma once
#include "RBTree.hpp"

namespace yulao
{
    
    
	template<class K>
	class set
	{
    
    
		struct SetKeyOfT
		{
    
    
			const K& operator()(const K& key)
			{
    
    
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator;

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

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

		pair<iterator, bool> insert(const K& key)
		{
    
    
			return _t.Insert(key);
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}
  1. Constructor: setThe class does not implement a constructor in the provided code, but you can add it yourself.
  2. Iterator: setThe class defines a nested iteratortype. This iterator is an iterator in a red-black tree. begin()The function returns an iterator to the smallest element in the red-black tree, while end()the function returns an iterator indicating the end position. This allows you to use an iterator to iterate over setthe elements in .
  3. Insertion operation: insertThe function is used to setinsert elements into . It performs the insertion operation by calling the function _tof the underlying red-black tree Insertand returns an pairobject containing an iterator and a boolean value. The iterator points to the inserted element, and a Boolean value indicates whether the insertion was successful. The returned Boolean value is if the inserted element already exists, falseotherwise it is true.

2. Test set package

void test_set()
{
    
    
    set<int> s;

    set<int>::iterator it = s.begin();
    while (it != s.end())
    {
    
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    s.insert(3);
    s.insert(2);
    s.insert(1);
    s.insert(5);
    s.insert(3);
    s.insert(6);
    s.insert(4);
    s.insert(9);
    s.insert(7);


    it = s.begin();
    while (it != s.end())
    {
    
    
        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

Output results

1 2 3 4 5 6 7 9

Summarize

Here we just made some template modifications based on the red-black tree we learned before, and simply implemented the iterator function and insertion function of map and set, but this uses the underlying iterator of the red-black tree to simulate mapand setThe implementation of the insert function has the following functions and benefits:

  1. Provides the ability to customize data structures: by using the underlying red-black tree to implement mapand set, you can define your own data structures more flexibly, instead of being limited to using the containers provided by the standard library. This allows you to design and implement data structures based on specific needs to meet the requirements of your project.
  2. Learning data structures and algorithms: This implementation method can help you deeply understand the commonly used self-balancing binary search tree data structure of red-black tree, as well as the algorithms related to it. By manually implementing functions such as insertion, deletion, and iterators in red-black trees, you can better understand the inner workings of these operations and thereby improve your programming skills.
  3. Adapt to specific needs: Sometimes the containers provided by the standard library may not necessarily meet the needs of a specific project or application. Through custom data structures, you can extend and customize it according to your needs to provide more suitable functions and performance.
  4. Educational and academic uses: This implementation can also be used for educational and academic research. It can be used as a teaching tool to help students understand and learn the principles of data structures and algorithms such as red-black trees. At the same time, it can also be used for academic research and for conducting experiments and research in related fields.

Guess you like

Origin blog.csdn.net/kingxzq/article/details/132863240