Structure de données C ++ arbre de tri binaire / arbre de recherche binaire


Préface

Quand j'apprenais à connaître les arbres rouge-noir aujourd'hui, j'ai vu un exemple d'arbre de tri binaire dans la classe d'introduction. Je pensais que je n'avais pas écrit le code d'un arbre de tri binaire auparavant, alors je l'ai écrit cette fois. Voici la nature de l'arbre de tri binaire et Partagez les problèmes survenus lors de l'écriture du code.

concept

introduction

Avant d'écrire, nous devons d'abord comprendre ce qu'est un arbre de tri binaire

Arbre de recherche binaire, il a un nœud racine, et chaque nœud ne peut avoir que deux nœuds enfants au plus, la valeur du nœud enfant gauche est inférieure à son nœud parent et la valeur du nœud enfant droit est supérieure à son nœud parent .

Nous insérons les données une par une: 43,98,2,4,0,5 La
figure suivante montre à quoi ressemble l'arbre de recherche binaire une fois l'insertion terminée

Insérez la description de l'image ici
La manipulation des données n'est rien d'autre que l' ajout, la suppression, la modification et la vérification.
Pour une arborescence de tri binaire, l'
ajout est un nœud d'insertion, la
suppression est un nœud de suppression et la
vérification consiste à savoir si la valeur de requête x existe dans l'arborescence.

Réflexions sur l'existence et la signification de l'arbre de tri binaire

A ce moment, quelqu'un peut demander: "N'est-il pas bon d'utiliser des tableaux pour la recherche? Une boucle for peut le faire".
Ici, nous analysons une vague de complexité temporelle
pour que la boucle traverse le tableau, le pire des cas est de traverser jusqu'au dernier élément, la complexité temporelle est O (n)
et la méthode de recherche de l'arbre de recherche binaire est similaire à la recherche binaire, tant que comme hauteur de l'arbre de recherche, nous savons tous que la complexité temporelle de la recherche de la hauteur de l'arbre est O (logn)

A ce moment, quelqu'un peut demander à nouveau: "Puisque la complexité temporelle est entièrement O (logn), alors je peux trouver directement la recherche binaire? C'est tellement compliqué de construire un arbre."
Bonne question, mais réfléchissons à ce sujet à nouveau, recherche binaire Avant, nous devons d'abord trier le tableau. Si nous utilisons la fusion de tri plus rapide ou le tri rapide , la complexité temporelle est O (nlogn) , plus la complexité temporelle de la recherche binaire O (logn) (nous n'avons pas On n'en a pas encore parlé ) La complexité temporelle de l'insertion des données dans le tableau et le problème qui peut nécessiter une expansion) , c'est-à-dire que dans le pire des cas , le temps de la première requête est plus du double de celui de l'arbre de tri binaire .

En ce moment, il y a des lecteurs qui peuvent m'appeler la solution de facilité, "Vous dites que vous voulez trier le temps, vous n'avez pas des réalisations de temps?"
Insulte bien, il faut du temps et de l'espace la complexité des réalisations que nous laissons tranquilles, nous parlons sur l' augmentation de cette opération.
Pour un tableau trié , insérez-y un élément de données sans re-tri, parcourez-le simplement une fois, et la complexité temporelle est O (n);
mais pour un arbre de tri binaire , insérez une nouvelle donnée , car elle doit être traversée avant l'insertion La longueur la plus longue est la hauteur de l'arbre, donc la complexité temporelle est O (logn) . L'effet de l'insertion d'un élément de données n'est peut-être pas évident, mais l'insertion de 10, 10 000 ou 100 millions? De toute évidence, dans une perspective à long terme, le temps passé sur un arbre de tri binaire est nettement meilleur que l'utilisation de la recherche binaire.

Cela dit, je n'ai pas encore parlé de produits secs, passons au sujet!

Fonctionnement et affichage du code

Insérer un élément

Selon les caractéristiques de l'arbre de tri binaire, on peut facilement avoir l'idée d'insérer des données. Si le nœud inséré est plus petit que le nœud de comparaison et que la branche gauche du nœud de comparaison est vide, il sera inséré sur la branche gauche du nœud de comparaison. S'il n'est pas vide, effectuez la même opération d'insertion (récursivité) sur la branche gauche du nœud de comparaison. Si le nœud d'insertion est plus grand que le nœud de comparaison, l'opération est la même, changez simplement la direction.
Une petite analyse du processus, on sait qu'il faut utiliser la récursivité, le code est le suivant

void BinSearchTree::insert(int x)
{
    
    
	/*
	这个重载是为了插入方便
	只要insert一个值就可以了
	不用自己再新建节点
	*/
	Node *root=new Node(x);
	if(this->Root==NULL)
	{
    
    
		this->Root=root;
		return;
	}
	Node *p=this->Root;
	insert(p,root);
}

void BinSearchTree::insert(Node *p,Node *root)
{
    
    
	/*
	可不可以有相同的元素要根据需求来
	如果不要相同元素,最好在用户插入的时候说明
	如果有相同,插左边还是右边都可以,但定下左右就不能改了
	这里我写的时候就简单一点,遇到相同的就不插了
	*/

	//学了点算法,感觉写起来没以前难了(滑稽)
	if(root->data==p->data) return;//遇到相同,直接不插入
	if(root->data < p->data)
	{
    
    
		if(p->lChild==NULL)
		{
    
    
			p->lChild=root;
			return;
		}
		else insert(p->lChild,root);
	}
	else//root->data > p->data
	{
    
    
		if(p->rChild==NULL)
		{
    
    
			p->rChild=root;
			return;
		}
		else insert(p->rChild,root);
	}
}

Pouvez-vous avoir les mêmes éléments en fonction de vos besoins?
Si vous ne voulez pas les mêmes éléments, il vaut mieux expliquer
quand l' utilisateur les insère. S'ils sont identiques, vous pouvez les insérer à gauche ou à droite, mais vous ne peut pas les changer si
vous définissez la gauche et la droite ., Ne branchez pas la même

Rechercher un élément

J'en ai écrit deux. La
première consiste à trouver si l'élément est dans l'arborescence de recherche et l'
autre à renvoyer le nœud
qui a la valeur. Comme mon arbre est configuré pour ne pas avoir d'éléments identiques, il n'y a pas de problème pour trouver plusieurs éléments.

Rechercher si l'élément est dans l'arborescence de recherche

bool BinSearchTree::search(int x)
{
    
    
	if(x==this->Root->data) return true;
	Node *p=this->Root;
	return search(p,x);
}

bool BinSearchTree::search(Node *p,int x)
{
    
    
	if(p==NULL) return false;//找到空的地方都没有,说明真没有
	if(p->data==x) return true;
	if(x<p->data) search(p->lChild,x);
	else search(p->rChild,x);
}

Renvoie le nœud qui possède la valeur

Node * BinSearchTree::searchNode(int x)
{
    
    
	if(this->Root->data==x) return this->Root;
	Node *p=this->Root;
	return searchNode(p,x);
}

Node * BinSearchTree::searchNode(Node *p,int x)
{
    
    
	if(p->data==x) return p;
	if(x<p->data) return searchNode(p->lChild,x);
	else return searchNode(p->rChild,x);
}

Supprimer l'élément

Il existe trois cas de
suppression: 1. Le nœud supprimé a des enfants gauche et droit
2. Le nœud supprimé n'a qu'un seul enfant
3. Le nœud supprimé n'a pas d'enfants

Pour le
cas 1. Remplacez le plus petit nœud de l'enfant droit du nœud à supprimer par la valeur du nœud à supprimer.
Cela transforme le problème en Cas 2 ou Cas 3
Cas 2. Le nœud parent du nœud supprimé pointe vers le nœud enfant du nœud supprimé, puis supprimez le nœud
. 3. Supprimez le nœud directement

ps: Pourquoi devrions-nous prendre la valeur minimale du bon sous-arbre dans le premier cas? C'est en fait très intelligent.
Premièrement, le sous-arbre droit du nœud à supprimer doit être plus grand que le sous-arbre gauche du nœud à supprimer.
Deuxièmement, comme le nombre minimum de sous-arbres du sous-arbre droit du nœud à supprimer, il est supérieur à tous les nœuds de sous-arborescence droite du nœud à supprimer. Petit,
il est donc préférable de remplacer le nœud à supprimer

void BinSearchTree::removeNode(int x)
{
    
    
	/*
	删除节点一共有三种情况
	1.删除的节点左右孩子都有
	2.删除的节点只有一个孩子
	3.删除的节点没有孩子

	对于
	1.将要删除节点的右孩子中的最小节点与要删除节点的值替换
	这样就将该问题变成了情况2 或者 情况3
	2.被删除节点的父节点指向被删除节点的子节点,然后删除该节点
	3.直接删除该节点

	ps:为什么第一种情况要取右子树的最小值?这其实十分巧妙
	首先,作为待删节点的右子树,肯定比待删节点的左子树大
	其次,作为待删节点右子树的最小子树数,比待删节点所有的右子树节点小
	所以其代替待删节点最好
	*/
	Node *p=searchNode(x);//找到待删除节点
	if(p->lChild==NULL && p->rChild==NULL) withNoChild(p);
	else if(p->lChild!=NULL && p->rChild!=NULL) withTwoChild(p);
	else withOneChild(p);
	return;
}
void BinSearchTree::withTwoChild(Node *p)//因为withOneChile有毛病,所以它也有毛病<---withOneChild好像没毛病
{
    
    //应该是它本身的毛病
	//情况1,将要删除节点的右孩子中的最小节点与要删除节点的值替换
	//这样就将该问题变成了情况2 或者 情况3
	Node *min=getMinNode(p->rChild);
	std::cout<<"右子树最小值为:";
	std::cout<<min->data<<std::endl;

	int minVal=min->data;

	if(min->lChild==NULL && min->rChild==NULL) withNoChild(min);
	else withOneChild(min);//<======我知道为什么会出问题了,当改了待删节点的数值后,就会出现两个min值
	//找父节点就会起冲突
	//所以应该先删在改

	p->data=minVal;//<-----要先删再改,不然会出现问题!!!
}

void BinSearchTree::withOneChild(Node *p)//有毛病<---貌似也没有毛病
{
    
    
	Node *father=getFather(p);
	Node *child;
	if(p->lChild==NULL) child=p->rChild;
	else child=p->lChild;

	//如果待删节点是其父节点的左孩子
	if(father->lChild->data==p->data) father->lChild=child;
	//如果待删节点是其父节点的右孩子
	else father->rChild=child;
	delete p;
}

void BinSearchTree::withNoChild(Node *p)//没毛病
{
    
    
	Node *father=getFather(p);
	std::cout<<father->data<<std::endl;
	if(father->lChild->data==p->data) father->lChild=NULL;
	else father->rChild=NULL;
	delete p;
}

résultat de l'opération

Ici, nous entrons les nombres 43,98,2,4,0,5 dans l'exemple pour construire un arbre de tri binaire. Le résultat en
Insérez la description de l'image ici
largeur d'abord est conforme à la structure théorique.
Insérez la description de l'image ici
Ici, nous supprimons le nœud 2 et le
Insérez la description de l'image ici
processus de suppression est comme
Insérez la description de l'image ici
Insérez la description de l'image ici
Insérez la description de l'image ici
indiqué dans la figure ci-dessus. Après avoir supprimé l'arborescence, effectuez un parcours hiérarchique., Le résultat est égal au résultat théorique, qui peut à peu près confirmer que mon code doit être correct.

Les avantages et inconvénients des arbres de tri binaire et leurs solutions

avantage

Avantages J'ai beaucoup d'implication dans la colonne de la signification de l'arbre de tri binaire, donc je ne le répéterai pas ici.

Désavantage

Avant de parler des lacunes, jetons un œil à l'image ci-dessous.
Insérez la description de l'image ici
Avez-vous trouvé des problèmes? C'est vrai, c'est aussi une sorte binaire , juste un infirme. Lorsque les données insérées augmentent ou diminuent, l'arbre de tri binaire devient une liste chaînée et la complexité temporelle de la recherche se dégrade en O (n), ce qui n'est pas le résultat souhaité

solution

Comment résoudre ce problème? Cela implique des structures de données avancées arbre rouge-noir vers le haut

Quelqu'un pourrait demander: "Pourquoi n'en avez-vous pas parlé?" En fait, je n'ai pas encore lu l'arbre rouge-noir, alors partageons-le quand j'aurai fini d'étudier, hehe.

-------------------------------------------------- ------ Mise à jour 6.17 ------------------------------------------ --------------------
Je suis rentré de mes études, voici le partage de l'arbre rouge-noir

Je suppose que tu aimes

Origine blog.csdn.net/weixin_44062380/article/details/106779951
conseillé
Classement