[C++] Rechercher l'implémentation sous-jacente de l'arbre binaire

Table des matières

1. Concept

2. Mettre en œuvre l'analyse

1. Insérer

(1.) Version non récursive 

 (2.) Version récursive

 2. Imprimer l'arbre binaire de recherche

3. Rechercher une fonction

(1.) Version non récursive

(2.) Version récursive

4. Supprimer des fonctions (importantes et difficiles) 

Analyse des erreurs courantes pour vous assurer d’apprendre

(1.) Supprimer la cible sans enfants gauche ou droit

(2.) Supprimer la cible et n'avoir qu'un seul enfant

(3.) Supprimer la cible, avoir deux enfants

code

(1.) Version non récursive 

(2.) Version récursive

5. Destructeur

6.Construction de copie 

 Troisièmement, candidature

 4. Défauts et optimisation de la recherche des arbres binaires

5. Résumé du code

Conclusion


1. Concept

L'arbre de recherche binaire est également appelé arbre de tri binaire. Il s'agit soit d'un arbre vide , soit d'un arbre binaire avec les propriétés suivantes :
Si son sous-arbre gauche n'est pas vide, les valeurs de tous les nœuds du sous-arbre gauche sont inférieures à la valeur du nœud racine
Si son sous-arbre droit n'est pas vide, les valeurs de tous les nœuds du sous-arbre droit sont supérieures à la valeur du nœud racine
Ses sous-arbres gauche et droit sont également respectivement des arbres de recherche binaires.

Pourquoi est-il aussi appelé arbre de tri binaire ? Lorsque l'arbre est parcouru par ordre de niveau, il est par ordre croissant.

2. Mettre en œuvre l'analyse

Exemple expérimental :

int a[] = {8, 3, 1, 10, 6, 4, 5, 7, 14, 13} ; 

1. Insérer

(1.) Version non récursive 

a. Commencez à comparer et à rechercher à partir de la racine. Si elle est plus grande que la racine, recherchez vers la droite. Si elle est plus petite que la racine, recherchez vers la gauche.
b. Recherchez la hauteur la plupart du temps. Si elle arrive à vide, elle n'a pas encore été trouvée. Cette valeur n'existe pas.

 C'est relativement simple, il suffit de mettre le code directement ici :

template <class K>
class BinarySeaTree_node
{
	typedef BinarySeaTree_node<K> BST_node;
public:
	BinarySeaTree_node(const K& val)
		: _val(val)
		,_left(nullptr)
		,_right(nullptr)
	{}

	K _val;
	BST_node* _left;
	BST_node* _right;
};


template <class T>
class BSTree
{
	typedef BinarySeaTree_node<T> BST_node;
private:
	BST_node* root = nullptr;

public:
	bool Insert(const T& val)
	{
		BST_node* key = new BST_node(val);
		BST_node* cur = root;
		BST_node* parent = nullptr;
		while (cur)
		{
			if (key->_val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key->_val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return 0;
			}
		}

		// 查询好位置后,建立链接
		if (!root)
		{
			root = key;
			return 0;
		}

		if (key->_val > parent->_val)
		{
			parent->_right = key;
		}
		else
		{
			parent->_left = key;
		}
		return 1;
	}
};

 (2.) Version récursive

Il se passe beaucoup de choses ici , tout le monde, faites attention !!!

bool Re_Insert(const T& val){  return Re_Insert_table(root, val);}

	bool Re_Insert_table(BST_node*& node, const T& val)
	{
		if (node == nullptr)
		{
			node = new BST_node(val);
			return 1;
		}

		if (val < node->_left)
		{
			return Re_Insert_table(node->_left, val);
		}
		else if (val > node->_right)
		{ 
			return Re_Insert_table(node->_right, val);
		}
		else
		{
			return 0;
		}
	}

Pour faciliter votre compréhension, je vais vous donner un diagramme d'expansion récursive.

 2. Imprimer l'arbre binaire de recherche

 

Le processus spécifique d’insertion est le suivant :
a. Si l'arborescence est vide, ajoutez directement un nœud et attribuez-le au pointeur racine.
b. L'arbre n'est pas vide. Trouvez la position d'insertion en fonction des propriétés de l'arbre de recherche binaire et insérez le nouveau nœud.

Ici, nous partageons uniquement le code : 

void Print_table() { Re_Print(root); }

	void Re_Print(const BST_node* node)
	{
		if (node == nullptr)
			return;
		Re_Print(node->_left);
		cout << node->_val << " ";
		Re_Print(node->_right);
	}

3. Rechercher une fonction

Idée : En fait, il n'y a aucune idée. Si le nœud est plus petit que le nœud parent, cherchez le côté gauche, sinon cherchez le côté droit. 

(1.) Version non récursive

BST_node* Find(const T& val)
	{
		//直接跟寻找位置一样
		BST_node* parent = nullptr;
		BST_node* cur = root; // 以返回cur的方式返回

		while (cur)   // 非递归版本
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				return cur;
			}
		}
		return cur;
	}

(2.) Version récursive

BST_node* Re_Find(const T& val){   return Re_Find_table(root, val); }
	
	BST_node* Re_Find_table(BST_node* node, const T& val)
	{
		if (node == nullptr)
			return nullptr;

		if (val < node->_val)
		{
			return Re_Find_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Find_table(node->_right, val);
		}
		else
		{
			return node;
		}
	}

4. Supprimer des fonctions (importantes et difficiles) 

Nous avons brièvement recherché des idées, comme suit :

Mais ces idées ne sont que des orientations générales, et elles cachent de nombreux pièges. Ensuite, je vous emmènerai analyser ces points sujets aux erreurs .

Tout d’abord, interrogez la cible :

Ceci est relativement simple et ne sera pas expliqué ici. 

       //首先寻找到目标,并且记录到parent
		BST_node* parent = nullptr;
		BST_node* cur = root;
		while (cur)
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				break;
			}
		}
		if (!cur)
		{
			return 0;
		}

Analyse des erreurs courantes pour vous assurer d’apprendre

(1.) Supprimer la cible sans enfants gauche ou droit

 

(2.) Supprimer la cible et n'avoir qu'un seul enfant

Idée générale: 

 Mais il y a une faille !

promesse:

(3.) Supprimer la cible, avoir deux enfants

 Bon, maintenant que les entrées sont servies, jetons un œil au plat principal de cette fois-ci.

code

(1.) Version non récursive 

bool Erase(const T& val)
	{
		//首先寻找到指定值,并且记录到parent
		BST_node* parent = nullptr;
		BST_node* cur = root;
		while (cur)
		{
			if (val < cur->_val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (val > cur->_val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				break;
			}
		}
		if (!cur)
		{
			return 0;
		}

		// 查询成功,开始删除
		if (!cur->_left && !cur->_right) // cur没有左右孩子
		{   // 当要删除目标是根
			if (cur == root)
			{
				root = nullptr;
				delete cur;
			}
			// 判断cur是左右孩子
			else if (cur->_val < parent->_val)
			{
				parent->_left = nullptr;
				delete cur;
			}
			else
			{
				parent->_right = nullptr;
				delete cur;
			}
			return 1;
		}
		else if (!cur->_left || !cur->_right)  // 只有一个孩子
		{
			if (!parent)  // 判断是否是目标是根
			{
				root = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
			// 判断cur为啥孩子
			else if (cur->_val < parent->_val) // 左侧
			{
				parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
			else                          // 右侧
			{
				parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
				delete cur;
			}
		}
		else   // 有2个孩子
		{  // 使用左侧最大的孩子来领养
			// 寻找左侧最大
			BST_node* maxnode = cur->_left;
			BST_node* max_parent = cur;
			while (maxnode->_right)
			{
				max_parent = maxnode;
				maxnode = maxnode->_right;
			}
			
			// 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
			if (max_parent == cur)
			{
				max_parent->_left = maxnode->_left;
			}
			else
			{
				max_parent->_right = maxnode->_left;
			}
			// 值转移
			cur->_val = maxnode->_val;
			delete maxnode;
		}
		return 1;
	}

(2.) Version récursive

bool Re_Erease( const T& val)
	{
		return Re_Erease_table(root, val);
	}

	bool Re_Erease_table(BST_node*& node, const T& val)
	{
		// 首先我们先找到值
		if (node == nullptr)
		{
			return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
		}

		if (val < node->_val)
		{
			return Re_Erease_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Erease_table(node->_right, val);
		}
		else
		{
			// 开始删除目标数据。方法如下;
			// 1. 就按照非递归的思路,不用改多少代码 
			// 2. 使用递归方法,优势就是代码简洁
			// 这里使用方法2
			BST_node* del = node;  // 保存每次访问的对象,如果是目标,就备份好了
			if (node->_left == nullptr)
			{
				node = node->_right;
			}
			else if (node->_right == nullptr)
			{
				node = node->_left;
			}
			else
			{
				//处理左右都有孩子的目标
				// 左侧查找最大值,右侧查找最小值
				BST_node* max_node = node->_left;
				while (max_node->_right)
				{
					max_node = max_node->_right;
				}
				// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
				// 再次递归删除替换数据。
				swap(max_node->_val, node->_val);
				return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
			}			
			//处理前两种情况
			delete del;
		}
	}

5. Destructeur

Idée:

~BSTree()
	{  
		Distroy_Re(root);
		root = nullptr;   
	}
void Distroy_Re(BST_node*& node) // 我们采用递归删除
	{
		if (node == nullptr)
			return ;
		// 先处理左右孩子
		Distroy_Re(node->_left);
		Distroy_Re(node->_right);
		delete node;
		node = nullptr;
	}

6.Construction de copie 

    // 我们实现了拷贝构造,默认构造函数则不会生成 
	// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
	BSTree()                 
	{
	}
	 // BSTree() = default

     BSTree(const BSTree& Tree) // 拷贝构造
	{
		root = copy(Tree.root);
	}

	BST_node* copy(BST_node* root)
	{
		if (root == nullptr)
			return nullptr;

		BST_node* new_node = new BST_node(root->_val);
		new_node->_left = copy(root->_left);
		new_node->_right = copy(root->_right);
		return new_node;
	}

 Troisièmement, candidature

1. Modèle K : Le modèle K n'a que la clé comme code clé. Seule la clé doit être stockée dans la structure. Le code clé est ce qui doit être recherché.
valeur .
     Par exemple : étant donné un mot, déterminez si le mot est correctement orthographié . La méthode spécifique est la suivante : utilisez chaque mot de la collection de tous les mots du vocabulaire comme clé, construisez un arbre de recherche binaire et récupérez si le mot est correctement orthographié dans l'arbre de recherche binaire. S'il existe, il est correctement orthographié, s'il n'existe pas, il est mal orthographié.
2. Modèle KV : chaque clé clé a une valeur correspondante Value, c'est-à-dire une paire clé-valeur de <Key, Value> . Cette méthode est très courante dans la vraie vie :
     Par exemple, le dictionnaire anglais-chinois est la correspondance entre l'anglais et le chinois . Vous pouvez trouver rapidement le chinois correspondant via l'anglais. Les mots anglais et leur <mot, chinois> chinois correspondant forment une paire clé-valeur ;
Un autre exemple consiste à compter le nombre de mots . Une fois le comptage réussi, le nombre d'occurrences d'un mot donné peut être rapidement trouvé. Le mot et son nombre d'occurrences sont <mot, nombre>, formant une paire clé-valeur (ceci est relativement simple, il suffit de le modifier)

 4. Défauts et optimisation de la recherche des arbres binaires

Pour un arbre de recherche binaire à n nœuds, si la probabilité de rechercher chaque élément est égale, alors la longueur de recherche moyenne de l'arbre de recherche binaire est fonction de la profondeur du nœud dans l'arbre de recherche binaire, c'est-à-dire plus le nœud, plus plus de fois.
Cependant, pour un même jeu de clés, si l'ordre d'insertion de chaque clé est différent, des arbres de recherche binaires avec des structures différentes peuvent être obtenus :

Pire des cas : N

Cas moyen : O(logN)

Problème : s'il dégénère en un seul arbre, les performances de l'arbre de recherche binaire sont perdues. Peut-il être amélioré afin que les performances de l'arbre de recherche binaire puissent être optimisées quel que soit l'ordre dans lequel les codes clés sont insérés ? Ensuite, l'arbre AVL et l'arbre rouge-noir que nous apprendrons dans les chapitres suivants peuvent être utilisés.

5. Résumé du code

namespace key
{
template <class K>
class BinarySeaTree_node
{
	typedef BinarySeaTree_node<K> BST_node;
public:
	BinarySeaTree_node(const K& val)
		: _val(val)
		,_left(nullptr)
		,_right(nullptr)
	{}

	K _val;
	BST_node* _left;
	BST_node* _right;
};

template <class T>
class BSTree
{
public:
	typedef BinarySeaTree_node<T> BST_node;

	// 我们实现了拷贝构造,默认构造函数则不会生成 
	// 解决方案:1.实现构造函数 2.使用default关键字,强制生成默认构造
	BSTree()
	{
	}

	// BSTree() = default
   BSTree(const BSTree& Tree) // 拷贝构造
   {
	   root = copy(Tree.root);
   }

   BSTree<T>& operator=(BSTree<T> t)
   {
	   swap(root, t.root);
	   return *this;
   }

   BST_node* copy(BST_node* root)
   {
	   if (root == nullptr)
		   return nullptr;

	   BST_node* new_node = new BST_node(root->_val);
	   new_node->_left = copy(root->_left);
	   new_node->_right = copy(root->_right);
	   return new_node;
   }

   bool Re_Insert(const T& val) { return Re_Insert_table(root, val); }
   void Re_Print() { Re_Print_table(root); }
   bool Re_Erease(const T& val) { return Re_Erease_table(root, val); }
   BST_node* Re_Find(const T& val) { return Re_Find_table(root, val); }

   bool Insert(const T& val)
   {
	   BST_node* key = new BST_node(val);
	   BST_node* cur = root;
	   BST_node* parent = nullptr;
	   while (cur)
	   {
		   if (key->_val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (key->_val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   return 0;
		   }
	   }

	   // 查询好位置后,建立链接
	   if (!root)
	   {
		   root = key;
		   return 0;
	   }

	   if (key->_val > parent->_val)
	   {
		   parent->_right = key;
	   }
	   else
	   {
		   parent->_left = key;
	   }
	   return 1;
   }

   BST_node* Find(const T& val)
   {
	   //直接跟寻找位置一样
	   BST_node* parent = nullptr;
	   BST_node* cur = root; // 以返回cur的方式返回

	   while (cur)   // 非递归版本
	   {
		   if (val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   return cur;
		   }
	   }
	   return cur;
   }

   bool Erase(const T& val)
   {
	   //首先寻找到指定值,并且记录到parent
	   BST_node* parent = nullptr;
	   BST_node* cur = root;
	   while (cur)
	   {
		   if (val < cur->_val)
		   {
			   parent = cur;
			   cur = cur->_left;
		   }
		   else if (val > cur->_val)
		   {
			   parent = cur;
			   cur = cur->_right;
		   }
		   else
		   {
			   break;
		   }
	   }
	   if (!cur)
	   {
		   return 0;
	   }

	   // 查询成功,开始删除
	   if (!cur->_left && !cur->_right) // cur没有左右孩子
	   {   // 当要删除目标是根
		   if (cur == root)
		   {
			   root = nullptr;
			   delete cur;
		   }
		   // 判断cur是左右孩子
		   else if (cur->_val < parent->_val)
		   {
			   parent->_left = nullptr;
			   delete cur;
		   }
		   else
		   {
			   parent->_right = nullptr;
			   delete cur;
		   }
		   return 1;
	   }
	   else if (!cur->_left || !cur->_right)  // 只有一个孩子
	   {
		   if (!parent)  // 判断是否是目标是根
		   {
			   root = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
		   // 判断cur为啥孩子
		   else if (cur->_val < parent->_val) // 左侧
		   {
			   parent->_left = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
		   else                          // 右侧
		   {
			   parent->_right = cur->_left != nullptr ? cur->_left : cur->_right;
			   delete cur;
		   }
	   }
	   else   // 有2个孩子
	   {  // 使用左侧最大的孩子来领养
		   // 寻找左侧最大
		   BST_node* maxnode = cur->_left;
		   BST_node* max_parent = cur;
		   while (maxnode->_right)
		   {
			   max_parent = maxnode;
			   maxnode = maxnode->_right;
		   }

		   // 现在又进入一种特殊情况,1.max_parent就没进入循环,2.进入了循环
		   if (max_parent == cur)
		   {
			   max_parent->_left = maxnode->_left;
		   }
		   else
		   {
			   max_parent->_right = maxnode->_left;
		   }
		   // 值转移
		   cur->_val = maxnode->_val;
		   delete maxnode;
	   }
	   return 1;
   }

   ~BSTree()
   {
	   Distroy_Re(root);
	   root = nullptr;
   }

protected:
	bool Re_Insert_table(BST_node*& node, const T& val)
	{
		if (node == nullptr)
		{
			node = new BST_node(val);
			return 1;
		}

		if (val < node->_val)
		{
			return Re_Insert_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Insert_table(node->_right, val);
		}
		else
		{
			return 0;
		}
	}

	void Re_Print_table(const BST_node* node)
	{
		if (node == nullptr)
			return;
		Re_Print_table(node->_left);
		cout << node->_val << " ";
		Re_Print_table(node->_right);
	}

	BST_node* Re_Find_table(BST_node* node, const T& val)
	{
		if (node == nullptr)
			return nullptr;

		if (val < node->_val)
		{
			return Re_Find_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Find_table(node->_right, val);
		}
		else
		{
			return node;
		}
	}

	bool Re_Erease_table(BST_node*& node, const T& val)
	{
		// 首先我们先找到值
		if (node == nullptr)
		{
			return 0; // 如果访问到了空,则说明删除失败,原因是:不存在
		}

		if (val < node->_val)
		{
			return Re_Erease_table(node->_left, val);
		}
		else if (val > node->_val)
		{
			return Re_Erease_table(node->_right, val);
		}
		else
		{
			// 开始删除目标数据。方法如下;
			// 1. 就按照非递归的思路,不用改多少代码 
			// 2. 使用递归方法,优势就是代码简洁

			// 这里使用方法2
			BST_node* del = node;  // 保存每次访问的对象,如果是目标,就备份好了
			if (node->_left == nullptr)
			{
				node = node->_right;
			}
			else if (node->_right == nullptr)
			{
				node = node->_left;
			}
			else
			{
				//处理左右都有孩子的目标
				// 左侧查找最大值,右侧查找最小值
				BST_node* max_node = node->_left;
				while (max_node->_right)
				{
					max_node = max_node->_right;
				}
				// 完成循环后,max_node最多有左孩子,然后数据交换,我们以目标左侧树为起点
				// 再次递归删除替换数据。
				swap(max_node->_val, node->_val);
				return Re_Erease_table(node->_left, val); //已经完成删除,就直接退出,以免触发删除delete
			}
			// 查找到删除数据
			delete del;
		}
	}

	void Distroy_Re(BST_node*& node) // 我们采用递归删除
	{
		if (node == nullptr)
			return;
		// 先处理左右孩子
		Distroy_Re(node->_left);
		Distroy_Re(node->_right);
		delete node;
		node = nullptr;
	}
private:
	BST_node* root = nullptr;
 };
}

Conclusion

   C'est tout pour cette section. Merci d'avoir parcouru. Si vous avez des suggestions, laissez-les dans la zone de commentaires. Si vous apportez quelque chose à vos amis, laissez un j'aime. Vos likes et votre attention deviendront un article de blog. Motivation principale pour la création

Je suppose que tu aimes

Origine blog.csdn.net/qq_72112924/article/details/132890262
conseillé
Classement