C++——Utilisez une arborescence rouge-noir pour encapsuler la carte et définir

Table des matières

1. Introduction

2. Contrôle des paramètres du modèle d'arbre rouge-noir

3. Ajout de foncteurs dans les paramètres du modèle

4. Implémentation de l'itérateur d'arbre rouge-noir

5. start() et end() de l'arbre rouge-noir

6. Trouver la fonction de l'arbre rouge-noir

7. L'arbre rouge-noir encapsule la carte et définit le code source

7.1 map.h

7.2 set.h

7.3 test.cpp

1. Introduction

Nous savons tous que set est le conteneur du modèle K et map est le conteneur du modèle KV , mais les couches inférieures des deux sont implémentées à l'aide d'arbres rouge-noir . Dans le billet de blog précédent, un arbre rouge-noir a été simulé. et implémenté. Ensuite, nous allons l'implémenter, le transformer, puis utiliser un arbre rouge-noir pour encapsuler parfaitement la carte et l'ensemble. Pour parler franchement, la carte et l'ensemble ne sont que des commandants raffinés. Leurs principales fonctions internes sont toutes des arbres rouge-noir prêts à l'emploi, avec seulement de légères modifications.

2. Contrôle des paramètres du modèle d'arbre rouge-noir

 Puisque set est un modèle K et map est un modèle KV, tout comme map et set dans la bibliothèque stl, comme le montre la figure :

 L'arbre rouge-noir implémenté précédemment est un modèle KV, faut-il donc réécrire un arbre rouge-noir afin de s'adapter à l'ensemble ? En fait, ce n'est pas nécessaire. Il vous suffit de modifier les paramètres du modèle. Voici la solution consistant à modifier les paramètres du modèle dans l'arborescence rouge-noir de la bibliothèque :

 De là, nous pouvons clairement voir que le type de stockage des nœuds dans la bibliothèque est déterminé en fonction du deuxième paramètre de modèle set et map. Ce qui est stocké dans le deuxième paramètre, quel type est stocké dans le nœud , puis l'ensemble peut être satisfait. et les exigences de la carte, et maintenant cela peut causer un nouveau problème :

  • Puisque le type de données examine le deuxième paramètre du modèle, à quoi sert le premier paramètre du modèle ? 

Parce que dans un arbre rouge-noir, il est inévitable d'implémenter des fonctions qui nécessitent K, comme find, car la fonction find recherche principalement via Key. Si le premier paramètre du modèle est omis, alors map ne peut pas effectuer l'opération de recherche.

Ensuite, nous apportons un ajustement à ce que nous avons écrit en fonction de l'arbre rouge-noir dans Curry :

//节点类
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)
		, _col(Red)
	{}
};
// 红黑树的类
// T决定红黑树中存储的什么数据
// set RBTree<K, K>
// map RBTree<K, pair<K, V>>
template <class K, class T>
class RBTree
{
	typedef RBTreeNode<T> Node;//T决定节点存储类型的数据
public:
    //……
private:
	Node* _root = nullptr;
};

Après avoir modifié les paramètres du modèle de l'arbre rouge-noir, la carte (modèle KV) et l'ensemble (modèle K) peuvent naturellement s'adapter :

  • ensemble:
namespace cpp
{
	template<class K>
	class set
	{
    public:
        //……
	private:
		RBTree<K, K> _t;
	};
}
  • carte:
namespace cpp
{
	template<class K, class V>
	class map
	{
    public:
        //……
	private:
		RBTree<K, pair<K, V>> _t;
	};
}

3. Ajout de foncteurs dans les paramètres du modèle

Puisque le type de nœud actuel de l'arbre rouge-noir est T, lorsque le conteneur est défini, T est la valeur clé Key et peut être comparé directement. Lorsque le conteneur est une carte, T est une paire <Clé, Valeur> et une comparaison directe ne peut pas être effectué pour le moment. Il est nécessaire d'extraire la clé de cette paire clé-valeur, puis de comparer la taille de la clé.

Afin de résoudre ce problème, nous pouvons ajouter une couche de foncteurs aux paramètres du modèle de l'arbre rouge-noir. Ce paramètre est spécialement utilisé pour obtenir le type de clé. L'implémentation de ce foncteur est encapsulée dans map and set. Le spécifique le fonctionnement est le suivant :

  • carte:
template<class K, class V>
class map
{
    //仿函数
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;//返回键值key
		}
	};
public:
    //……
private:
	RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
  • ensemble:
template<class K>
class set
{
    //仿函数
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
    //……
private:
	RBTree<K, K, SetKeyOfT> _t;
};

Le dessin suivant illustre la situation d'appel spécifique :

4. Implémentation de l'itérateur d'arbre rouge-noir

L'itérateur avant de l'arbre rouge-noir encapsule en fait le pointeur de nœud, il n'y a donc en fait qu'une seule variable membre dans l'itérateur avant, qui est le pointeur du nœud encapsulé par l'itérateur avant.

//迭代器的类
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __RBTreeIterator<T, Ref, Ptr> Self;//把迭代器typedef,简化后续使用
    //……
	Node* _node;
};

À l’intérieur, nous devons effectuer les opérations suivantes :

  • 1. Constructeur
  • 2. * et -> surcharge des opérateurs
  • 3. != et == surcharge des opérateurs
  • 4. Surcharge de l'opérateur ++
  • 5. --Surcharge de l'opérateur

Ensuite, nous démontrerons en détail :

  • 1. Constructeur

Dans le constructeur, nous pouvons construire un itérateur direct directement via le pointeur d'un nœud.

//构造函数
__RBTreeIterator(Node* node)
	:_node(node)
{}
  • 2. * et -> surcharge des opérateurs

L'opérateur * est un déréférencement, qui renvoie directement la référence aux données du nœud correspondant. L'opérateur -> renvoie un pointeur vers les données du nœud correspondant.

//*运算符重载
Ref operator*()
{
	return _node->_data;//返回_data数据本身
}
//->运算符重载
Ptr operator->()
{
	return &_node->_data;//返回_data数据的地址
}
  • 3. != et == surcharge des opérateurs

L'opérateur != renvoie directement si les deux nœuds sont différents, tandis que l'opérateur == renvoie directement si les deux nœuds sont identiques.

//!=
bool operator!=(const Self& s)
{
	return _node != s._node;
}
//==
bool operator==(const Self& s)
{
	return _node == s._node;
}
  • 4. Surcharge de l'opérateur ++

L'opérateur ++ est divisé en préfixe ++ et suffixe ++.

  • Préfixe++ :

Tout d'abord, la valeur après ++ dans l'itérateur d'arbre rouge-noir ici devrait être le prochain parcours dans l'ordre à partir de cette position. La valeur du nœud suivant doit être supérieure à celle d'origine. Si vous souhaitez trouver cette position, combinée avec les propriétés de l'arbre de recherche binaire, vous devez rechercher dans le sous-arbre de droite, et cela dépend si le sous-arbre de droite est vide. .L'opération spécifique comme suit :

  • 1. Le sous-arbre droit n'est pas vide : il suffit de parcourir directement pour trouver le nœud le plus à gauche du sous-arbre droit ;
  • 2. Le sous-arbre de droite est vide : trouver le nœud ancêtre dont l'enfant est à gauche du père parmi les ancêtres ;
  • 3. Lorsque le parent passe à vide, ++ se termine ;
  • 4. Notez que le préfixe ++ renvoie la valeur après ++.
//前置++
Self& operator++()
{
	if (_node->_right == nullptr)//右子树为空
	{
		//找祖先里面,孩子是父亲左的那个
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_right == cur)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	else//右子树不为空
	{
		//右子树的最左节点
		Node* subLeft = _node->_right;
		while (subLeft->_left)
		{
			subLeft = subLeft->_left;
		}
		_node = subLeft;
	}
	return *this;
}
  • Message++ :

La seule différence entre post++ et prefix ++ est que post++ renvoie la valeur avant ++. Ici, il vous suffit de sauvegarder le nœud actuel au début en fonction du préfixe ++ et d'appeler directement le préfixe. ++, et enfin de renvoyer le nœud enregistré. valeur.

//后置++
Self& operator++(int)
{
	Self tmp(*this);
	++(*this);//复用前置++
	return tmp;
}
  • 5. --Surcharge de l'opérateur

L'opérateur -- est divisé en préfixe -- et suffixe --, qui sont décrits ci-dessous :

  • Préfixe --:

L'opérateur -- est l'opposé de l'opérateur ++. L'opérateur -- consiste à trouver le nœud qui est le deuxième plus petit que la position actuelle, et ce nœud doit être recherché en premier dans le sous-arbre de gauche, et le sous-arbre de gauche est divisé en les deux types suivants Condition :

  • Si le sous-arbre de gauche est vide, recherchez le nœud dans l'ancêtre dont l'enfant est à droite du père ;
  • Si le sous-arbre de gauche n'est pas vide, recherchez le nœud le plus à droite dans le sous-arbre de gauche.
//前置--
Self& operator--()
{
	if (_node->_left == nullptr)//左子树为空
	{
		//找孩子是父亲右的那个
		Node* cur = _node;
		Node* parent = cur->_parent;
		while (parent && parent->_left == cur)
		{
			cur = cur->_parent;
			parent = parent->_parent;
		}
		_node = parent;
	}
	else//左子树不为空
	{
		//找左子树的最右节点
		Node* subRight = _node->_left;
		while (subRight->_right)
		{
			subRight = subRight->_right;
		}
		_node = subRight;
	}
	return *this;
}
  • Suffixe -- :

Notez que le post--- renvoie la valeur avant --, alors définissez d'abord tmp pour enregistrer *this, puis appliquez la fonction pre--- pour décrémenter, et enfin retournez tmp.

//后置--
Self& operator--(int)
{
	Self tmp(*this);
	--(*this);
	return tmp;
}

5. start() et end() de l'arbre rouge-noir

Une fois l'itérateur implémenté, nous devons taper le type d'itérateur dans l'implémentation de l'arbre rouge-noir. Il convient de noter que afin de permettre l'utilisation externe de l'itérateur de type itérateur direct après typedef, nous devons typedef dans la zone publique de l'arbre rouge-noir.

template <class K, class T, class KeyOfT>
class RBTree
{
    //……
public:
	typedef __RBTreeIterator<T, T&, T*> iterator;//普通迭代器
	typedef __RBTreeIterator<T, const T&, const T*> const_iterator;//const迭代器
    //……
}

En fait, la couche inférieure de l'arbre rouge-noir dans STL a un nœud principal sentinelle, comme indiqué ci-dessous :

STL stipule clairement que begin() et end() représentent un intervalle fermé au début et ouvert à la fin. Après un parcours dans l'ordre de l'arbre rouge-noir, une séquence ordonnée peut être obtenue. Par conséquent : begin() peut être placé dans l'arbre rouge-noir. La position du plus petit nœud (c'est-à-dire dans)le nœud le plus à gauche , end() est placé à la position suivante du plus grand nœud (le nœud le plus à droite ) .La clé est où se trouve la prochaine position du plus grand nœud ? Peut-il être défini sur nullptr ? La réponse n'est pas réalisable, car pour effectuer l'opération -- sur l'itérateur à la position end(), vous devez être capable de trouver le dernier élément, ce qui ne fonctionne pas ici, donc la meilleure façon est de mettre end() à le nœud principal. .

Bien que l'arbre rouge-noir de la bibliothèque soit implémenté avec un nœud principal sentinelle, après tout, il s'agit d'une simulation (mais seulement approximative). En résumé, les directions de start() et end() sont résumées comme suit :

  • Begin() : pointe vers la position du plus petit nœud (c'est-à-dire le nœud le plus à gauche) dans l'arborescence rouge-noir .
  • end() : pointe vers la position suivante du plus grand nœud (nœud le plus à droite) dans l'arbre rouge-noir , qui est nullptr.
//begin()
iterator Begin()//找最左节点
{
	Node* subLeft = _root;
	while (subLeft && subLeft->_left)
	{
		subLeft = subLeft->_left;
	}
	return iterator(subLeft);//调用迭代器的构造函数
}
//end()
iterator End()//返回最右节点的下一个
{
	return iterator(nullptr);
}

Bien sûr, il est préférable d'implémenter ici une version const de begin() et end( ) afin que les itérateurs ordinaires et les itérateurs const puissent être utilisés. En fait, la raison principale est que l'itérateur défini ne peut pas être modifié, qu'il soit un itérateur ordinaire ou un itérateur const. Les itérateurs sont encapsulés en interne avec des itérateurs const, donc une version const de start() et end() doit être implémentée.

//const版本
//begin() 
const_iterator Begin() const//找最左节点
{
	Node* subLeft = _root;
	while (subLeft && subLeft->_left)
	{
		subLeft = subLeft->_left;
	}
	return const_iterator(subLeft);//调用迭代器的构造函数
}
//end()
const_iterator End() const//返回最右节点的下一个
{
	return const_iterator(nullptr);
}

6. Trouver la fonction de l'arbre rouge-noir

 Les règles de recherche sont très simples, il suffit de parcourir les nœuds. Les règles spécifiques sont les suivantes :

  1. Si la valeur de la requête > la valeur actuelle du nœud, parcourez vers le sous-arbre droit pour interroger
  2. Si la valeur de la requête < la valeur actuelle du nœud, accédez au sous-arbre de gauche pour interroger
  3. Si la valeur de la requête = la valeur actuelle du nœud, renvoie l'itérateur de la position actuelle.
  4. Si la boucle se termine, cela signifie qu'aucune requête n'a été trouvée et End() est renvoyé.
//Find查找函数
iterator Find(const K& key)
{
	Node* cur = _root;
	KeyOfT kot;
	while (cur)
	{
		if (kot(cur->_data) < key)
		{
			cur = cur->_right;//查询的值 > 节点值,-》右子树
		}
		else if (kot(cur->_data) > key)
		{
			cur = cur->_left;//查询的值 < 节点值,-》左子树
		}
		else
		{
			return iterator(cur);
		}
	}
	return End();
}

7. L'arbre rouge-noir encapsule la carte et définit le code source

7.1 map.h

#pragma once
#include"RBTree.h"
namespace cpp
{
	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;
		typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;
		//begin()
		iterator begin() 
		{
			return _t.Begin();
		}
		//end()
		iterator end() 
		{
			return _t.End();
		}
		//insert
		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}
		//Find
		iterator find(const K& key)
		{
			return _t.Find();
		}
		//operator[]
		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;
	};
}

7.2 set.h

#pragma once
#include"RBTree.h"
namespace cpp
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;
		typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;
		//begin()
		iterator begin() const
		{
			return _t.Begin();
		}
		//end()
		iterator end() const
		{
			return _t.End();
		}
		//insert
		pair<iterator, bool> insert(const K& key)
		{
			//pair<typename RBTree<K, K, SetKeyOfT>::iterator, bool> ret = _t.Insert(key);
			auto ret = _t.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node), ret.second);
		}
		//Find
		iterator find(const K& key)
		{
			return _t.Find();
		}
	private:
		RBTree<K, K, SetKeyOfT> _t;
	};
}

7.3 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<string>
#include"map.h"
#include"set.h"
void test_set1()
{
	cpp::set<int> s;
	s.insert(8);
	s.insert(6);
	s.insert(11);
	s.insert(5);
	s.insert(6);
	s.insert(7);
	s.insert(10);
	s.insert(13);
	s.insert(12);
	s.insert(15);
	cpp::set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}
void test_map1()
{
	cpp::map<string, int> m;
	m.insert(make_pair("111", 1));
	m.insert(make_pair("555", 5));
	m.insert(make_pair("333", 3));
	m.insert(make_pair("222", 2));
	cpp::map<string, int>::iterator it = m.begin();
	while (it != m.end())
	{
		cout << it->first << ":" << it->second << endl;
		it++;
	}
	cout << endl;
	for (auto& kv : m)
	{
		cout << kv.first << ":" << kv.second << endl;
	}
	cout << endl;
}
void test_map2()
{
	string arr[] = { "ƻ", "", "ƻ", "", "ƻ", "ƻ", "", "ƻ", "㽶", "ƻ", "㽶" };
	cpp::map<string, int> countMap;
	for (auto& str : arr)
	{
		countMap[str]++;
	}

	for (const auto& kv : countMap)
		cout << kv.first << ":" << kv.second << endl;
}
void test_map3()
{
	cpp::map<string, string> dict;
	dict["insert"];
	dict["insert"] = "";
	dict["left"] = "";
}
int main()
{
	//test_set1();
	//test_map1();
	//test_map2();
	test_map3();
	return 0;
}

 

Je suppose que tu aimes

Origine blog.csdn.net/m0_49687898/article/details/131344408
conseillé
Classement