Apprentissage de "Introduction aux algorithmes" (15) ---- Arbre de recherche binaire (langage C)


avant-propos

Cet article explique principalement l'arbre de recherche binaire, implémente toutes ses fonctions de fonctionnement de base en langage C et donne le code du langage C.


1. Arbre de recherche binaire

1. Qu'est-ce qu'un arbre de recherche binaire ?

Un arbre de recherche binaire est un arbre binaire, qui est une structure de stockage de liste chaînée et satisfait des propriétés de données spécifiques (voir ci-dessous)

Comme son nom l'indique, l'arbre de recherche binaire a de bonnes performances de recherche et a de nombreuses applications.
Ses fonctions de fonctionnement sont :

1.中序遍历(输出有序数据序列)
2.返回最大最小值
3.搜索值
4.返回某个结点的左右顺序统计量
5.插入,删除结点

2. Quels sont les avantages d’un arbre de recherche binaire ?

(1) demande

1.作为字典存储数据,提供插入,删除,搜索功能
2.输出排好序的序列
3.作为优先队列
4.等等

(2) Performance temporelle

En général, le coût en temps de toutes ses fonctions d'exploitation est :
T ( n ) = O ( h ) = O ( lgn ) où h est la hauteur attendue de l'arbre, h = O ( lgn ) T(n)=O(h ) =O(lgn) \\Où h est la hauteur attendue de l'arbre, h=O(lgn)T ( n )=O ( h )=O ( l g n )h est la hauteur attendue de l'arbre ,h=O ( l g n )

3. Caractéristiques des données de l'arbre de recherche binaire

Soit x un nœud dans un arbre de recherche binaire. Si y est un nœud dans le sous-arbre gauche de x, alors y .key ⩽ x .key y.key\leqslant x.keyoui . clé cléx . clé . _ Si y est un nœud dans le sous-arbre droit de x, alorsy .key ⩾ x .key y.key\geqslant x.keyoui . clé cléx . clé _

Il maintient essentiellement un ordre local. Cet ordre partiel ressemble un peu à un tas.

(1) La différence entre la nature des données de l'arbre de recherche binaire et du tas

1.二叉搜索树是将所有小于等于的结点放到左子树,所有大于等于的结点放到右子树
2.堆是在维护一个局部最值,即双亲结点永远要大于(最小堆是小于)孩子结点

Pour plus de détails, veuillez vous référer à l'article :
Apprentissage "Introduction aux algorithmes" (7) - Tri par tas et file d'attente prioritaire (langage C)

2. Fonction de fonctionnement de l'arbre de recherche binaire (langage C)

1. Structure des données

La structure des données se compose de trois parties :

1.存储key值
2.指向左孩子结点的指针
3.指向有孩子结点的指针
4.指向父亲结点的指针
//定义二叉搜索树的结构
typedef struct STree
{
    
    
	int key;//结点的值 
	STree *p;//指向双亲结点 
	STree *left;//指向左孩子结点 
	STree *right;//指向有孩子结点 
}ST; 

2. Parcours dans l'ordre d'un arbre de recherche binaire

Règles de parcours dans l'ordre :
1. D'un point de vue horizontal :Traversez d’abord le sous-arbre de gauche, puis traversez le nœud, puis traversez le sous-arbre de droite
2. D’un point de vue vertical :D'abord le nœud feuille inférieur droit, puis jusqu'au nœud racine, puis jusqu'au nœud feuille assis.

L'arbre de recherche binaire est parcouru dans l'ordre et le résultat est une séquence ordonnée de petit à grand

(1) Parcours dans l'ordre

//中序遍历二叉搜索树
void inorder_tree_walk(ST *x)
{
    
    
	//由于二叉搜索树的特点
	//使用中序遍历的结果是从大到小的有序排列 
	if(x!=NULL)
	{
    
    
		//先遍历左子树 
		inorder_tree_walk(x->left);
		//遍历本结点 
		printf("%5d",x->key);
		//遍历右子树 
		inorder_tree_walk(x->right);
	}
	else
	{
    
    
		return;
	}
}

(2) Imprimer les résultats du parcours

//打印二叉搜索树
void print_tree(ST *x)
{
    
    
	inorder_tree_walk(x);
	printf("\n");	
} 

3. Rechercher des données

Lorsque le nœud est trouvé, ses informations d'adresse sont renvoyées.
Renvoie NULL s'il n'est pas trouvé

(1) Fonction de recherche de données

//在二叉搜索树中查找数据
ST *tree_search(ST *x,int k)
{
    
    
	//循环结束条件就是
	//1.结点指针为空
	//2.结点指针指向k结点 
	while(x!=NULL&&x->key!=k)
	{
    
    
		//如果k小于本结点,那么向左子树搜索 
		if(k<x->key)
		{
    
    
			x=x->left;
		}
		//如果k大于等于本结点,那么向右子树搜索
		else
		{
    
    
			x=x->right;
		}
	}
	return x;
}

(2) Imprimer les informations de recherche

//打印查找信息
void print_search(ST *x)
{
    
    
	if(x==NULL)
	{
    
    
		printf("there is no data in tree\n");
	}
	else
	{
    
    
		printf("data is %5d,the address is 0x%x\n",x->key,x);
	}
}

4. Obtenez la valeur maximale

Renvoie l'adresse de la valeur maximale
Selon la nature de l'arbre de recherche binaire, la valeur maximale est le nœud feuille le plus à droite et en bas

//得到最大值
ST *tree_max(ST *x)
{
    
    
	//最右最下的那个叶子结点就是最大值 
	while(x->right!=NULL)
	{
    
    
		x=x->right;
	}
	return x;
} 

5. Obtenez la valeur minimale

Renvoie l'adresse de la valeur minimale
Selon la nature de l'arbre de recherche binaire, la valeur minimale est le nœud feuille le plus à gauche et en bas

//得到最小值
ST *tree_min(ST *x)
{
    
    
	//最左最下的那个叶子结点就是最小值 
	while(x->left!=NULL)
	{
    
    
		x=x->left;
	}
	return x;
} 

6. Trouvez le nœud successeur

Définition du nœud successeur :
Le nœud successeur est le plus petit de tous les nœuds d'arbre plus grands que ce nœud

//得到指定结点x的后继结点
//后继结点就是所有大于该结点树节点中最小的那个
ST *tree_successor(ST *x)
{
    
    
	//如果该结点有右子树,那么后继就是右子树的最小值 
	if(x->right!=NULL)
	{
    
    
		return tree_min(x->right);
	}
	ST *y=x->p;
	//如果没有右子树,那么后继结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的左子树中的结点
	//那么后继就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->right)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}

7. Recherchez le nœud prédécesseur

Définition du nœud prédécesseur :
Le nœud prédécesseur est le plus grand de tous les nœuds de l'arbre, plus petit que ce nœud.

//得到指定结点x的前驱结点
//前驱结点就是所有小于该结点树节点中最大的那个
ST *tree_precursor(ST *x)
{
    
    
	//如果该结点有左子树,那么前驱就是左子树的最大值 
	if(x->left!=NULL)
	{
    
    
		return tree_max(x->left);
	}
	ST *y=x->p;
	//如果没有左子树,那么前驱结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的右子树中的结点
	//那么前驱就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->left)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}

8. Insérez des éléments dans l'arborescence

//给树中插入函数
ST *tree_insert(ST *x,int k)
{
    
    
	ST *y=NULL;
	ST *head=x;
	//如果是空结点,那么直接连接 
	if(x==NULL)
	{
    
    
		//动态分配空间 
		y=(ST *)malloc(sizeof(ST));
		y->key=k;
		y->left=NULL;
		y->p=NULL;
		y->right=NULL;
		x=y;
		return x;
	}
	//如果树不为空
	//那么按照搜索树的规则,找到要插入对象的位置
	//该位置是一个叶子结点 
	while(x!=NULL)
	{
    
    
		y=x;
		if(x->key>k)
		{
    
    
			x=x->left;
		}
		else
		{
    
    
			x=x->right;
		}
	}
	//分配空间 
	x=(ST *)malloc(sizeof(ST));
	x->key=k;
	x->p=y;
	x->left=NULL;
	x->right=NULL;
	if(y->key>k)
	{
    
    
		y->left=x;
	}
	else
	{
    
    
		y->right=x;
	}
	return head;
}

9. Remplacez un sous-arbre par un autre

Cette fonction est préparée pour la fonction de suppression, qui nécessite un grand nombre d'appels
Dans la fonction delete, sa fonction est de séparer le nœud u de l'arbre et de remplacer la position du nœud u par le sous-arbre enraciné au nœud v.

//将一颗子树替换为另一颗子树
//u是被替换子树根结点
//v是替换子树根结点
void ctree_change(ST *x,ST *u,ST *v)
{
    
    
	if(x==u)
	{
    
    
		x=v;
	}
	else if(u==u->p->left)
	{
    
    
		u->p->left=v;
	}
	else
	{
    
    
		u->p->right=v;
	}
	if(v!=NULL)
	{
    
    
		v->p=u->p;
	}
}

10. Supprimer le nœud

//删除一个结点
void tree_delet(ST *x,int k)
{
    
    
	ST *k_p;
	k_p=tree_search(x,k);
	if(k_p==NULL)
	{
    
    
		printf("there is no k in tree\n");
	}
	else
	{
    
    
		//如果没有左子树
		//那么直接用右子树替换该子树 
		if(k_p->left==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->right);
			free(k_p);
		}
		//如果没有右子树
		//那么直接用左子树替换该子树
		else if(k_p->right==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->left);
			free(k_p);
		}
		else
		{
    
    
			ST *temp=NULL;
			//找到后继结点 
			temp=tree_min(k_p->right);
			if(temp->p!=k_p)
			{
    
    
				//将后继结点提取出来 
				ctree_change(x,temp,temp->right);
				//将后继结点与被删除结点的右子树相连 
				temp->right=k_p->right;
				temp->right->p=temp;
			}
			//提取被删除结点 
			ctree_change(x,k_p,temp);
			//将后继结点与被删除结点的左子树连接 
			temp->left=k_p->left;
			temp->left->p=temp;
			free(k_p);
		}
	}
} 

3. Exemple d'arbre de recherche binaire en langage C

1. Résultats réels de l'exécution

insérer la description de l'image ici

2. Code de langue C

#include<stdio.h>
#include<stdlib.h>
#include<time.h>



//输入的规模 
#define SIZE 10
//随机数的范围是0~LIM-1 
#define LIM 100 



//定义二叉搜索树的结构
typedef struct STree
{
    
    
	int key;//结点的值 
	STree *p;//指向双亲结点 
	STree *left;//指向左孩子结点 
	STree *right;//指向有孩子结点 
}ST; 



//中序遍历二叉搜索树
void inorder_tree_walk(ST *x)
{
    
    
	//由于二叉搜索树的特点
	//使用中序遍历的结果是从大到小的有序排列 
	if(x!=NULL)
	{
    
    
		//先遍历左子树 
		inorder_tree_walk(x->left);
		//遍历本结点 
		printf("%5d",x->key);
		//遍历右子树 
		inorder_tree_walk(x->right);
	}
	else
	{
    
    
		return;
	}
}



//打印二叉搜索树
void print_tree(ST *x)
{
    
    
	inorder_tree_walk(x);
	printf("\n");	
} 



//在二叉搜索树中查找数据
ST *tree_search(ST *x,int k)
{
    
    
	//循环结束条件就是
	//1.结点指针为空
	//2.结点指针指向k结点 
	while(x!=NULL&&x->key!=k)
	{
    
    
		//如果k小于本结点,那么向左子树搜索 
		if(k<x->key)
		{
    
    
			x=x->left;
		}
		//如果k大于等于本结点,那么向右子树搜索
		else
		{
    
    
			x=x->right;
		}
	}
	return x;
}



//打印查找信息
void print_search(ST *x)
{
    
    
	if(x==NULL)
	{
    
    
		printf("there is no data in tree\n");
	}
	else
	{
    
    
		printf("data is %5d,the address is 0x%x\n",x->key,x);
	}
}


 
//得到最大值
ST *tree_max(ST *x)
{
    
    
	//最右最下的那个叶子结点就是最大值 
	while(x->right!=NULL)
	{
    
    
		x=x->right;
	}
	return x;
} 



//得到最小值
ST *tree_min(ST *x)
{
    
    
	//最左最下的那个叶子结点就是最小值 
	while(x->left!=NULL)
	{
    
    
		x=x->left;
	}
	return x;
} 



//得到指定结点x的后继结点
//后继结点就是所有大于该结点树节点中最小的那个
ST *tree_successor(ST *x)
{
    
    
	//如果该结点有右子树,那么后继就是右子树的最小值 
	if(x->right!=NULL)
	{
    
    
		return tree_min(x->right);
	}
	ST *y=x->p;
	//如果没有右子树,那么后继结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的左子树中的结点
	//那么后继就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->right)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
}



//得到指定结点x的前驱结点
//前驱结点就是所有小于该结点树节点中最大的那个
ST *tree_precursor(ST *x)
{
    
    
	//如果该结点有左子树,那么前驱就是左子树的最大值 
	if(x->left!=NULL)
	{
    
    
		return tree_max(x->left);
	}
	ST *y=x->p;
	//如果没有左子树,那么前驱结点就是该结点的一个上层结点
	//而且该结点一定是这个上层结点的右子树中的结点
	//那么前驱就是第一个满足条件的祖先结点 
	while(y!=NULL&&x==y->left)
	{
    
    
		x=y;
		y=y->p;
	}
	return x; 
} 



//给树中插入函数
ST *tree_insert(ST *x,int k)
{
    
    
	ST *y=NULL;
	ST *head=x;
	//如果是空结点,那么直接连接 
	if(x==NULL)
	{
    
    
		//动态分配空间 
		y=(ST *)malloc(sizeof(ST));
		y->key=k;
		y->left=NULL;
		y->p=NULL;
		y->right=NULL;
		x=y;
		return x;
	}
	//如果树不为空
	//那么按照搜索树的规则,找到要插入对象的位置
	//该位置是一个叶子结点 
	while(x!=NULL)
	{
    
    
		y=x;
		if(x->key>k)
		{
    
    
			x=x->left;
		}
		else
		{
    
    
			x=x->right;
		}
	}
	//分配空间 
	x=(ST *)malloc(sizeof(ST));
	x->key=k;
	x->p=y;
	x->left=NULL;
	x->right=NULL;
	if(y->key>k)
	{
    
    
		y->left=x;
	}
	else
	{
    
    
		y->right=x;
	}
	return head;
} 



//将一颗子树替换为另一颗子树
void ctree_change(ST *x,ST *u,ST *v)
{
    
    
	if(x==u)
	{
    
    
		x=v;
	}
	else if(u==u->p->left)
	{
    
    
		u->p->left=v;
	}
	else
	{
    
    
		u->p->right=v;
	}
	if(v!=NULL)
	{
    
    
		v->p=u->p;
	}
} 



//删除一个结点
void tree_delet(ST *x,int k)
{
    
    
	ST *k_p;
	k_p=tree_search(x,k);
	if(k_p==NULL)
	{
    
    
		printf("there is no k in tree\n");
	}
	else
	{
    
    
		//如果没有左子树
		//那么直接用右子树替换该子树 
		if(k_p->left==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->right);
			free(k_p);
		}
		//如果没有右子树
		//那么直接用左子树替换该子树
		else if(k_p->right==NULL)
		{
    
    
			ctree_change(x,k_p,k_p->left);
			free(k_p);
		}
		else
		{
    
    
			ST *temp=NULL;
			//找到后继结点 
			temp=tree_min(k_p->right);
			if(temp->p!=k_p)
			{
    
    
				//将后继结点提取出来 
				ctree_change(x,temp,temp->right);
				//将后继结点与被删除结点的右子树相连 
				temp->right=k_p->right;
				temp->right->p=temp;
			}
			//提取被删除结点 
			ctree_change(x,k_p,temp);
			//将后继结点与被删除结点的左子树连接 
			temp->left=k_p->left;
			temp->left->p=temp;
			free(k_p);
		}
	}
} 



//主测试函数 
int main()
{
    
    
	ST *t=NULL;//搜索二叉树 
	ST *temp;//中间存储结点 
	int a[SIZE];
	int i=0;
	//生成原始随机数据 
	srand((unsigned)time(NULL));
	for(i=0;i<SIZE;i++)
	{
    
    
		a[i]=rand()%LIM;
	}
	//打印原始数据
	for(i=0;i<SIZE;i++)
	{
    
    
		printf("%5d",a[i]);
	}
	printf("\n"); 
	//生成搜索二叉树
	for(i=0;i<SIZE;i++)
	{
    
    
		t=tree_insert(t,a[i]);
	}
	//用中序遍历打印生成的搜索二叉树
	//结果是排好序的,从小到大 
	print_tree(t); 
	//查找值
	//存在的值 
	temp=tree_search(t,a[5]);
	print_search(temp);
	//不存在的值 
	temp=tree_search(t,LIM);
	print_search(temp);  
	//返回最大值,最小值
	//最小值 
	temp=tree_min(t);
	printf("min is %5d\n",temp->key);
	//最大值 
	temp=tree_max(t);
	printf("max is %5d\n",temp->key);
	//返回前驱值和后继
	//前驱值
	temp=tree_precursor(t);
	printf("the precursor of root data %5d is %5d\n",t->key,temp->key);
	//后继值 
	temp=tree_successor(t);
	printf("the successor of root data %5d is %5d\n",t->key,temp->key);
	//删除值
	tree_delet(t,a[5]);
	print_tree(t); 
	return 0;
} 

Résumer

Les lecteurs sont invités à corriger toute anomalie dans cet article

Je suppose que tu aimes

Origine blog.csdn.net/weixin_52042488/article/details/126914477
conseillé
Classement