Le langage C implémente un arbre binaire (structure en chaîne)


avant-propos

Lorsque nous utilisons la structure séquentielle pour réaliser le stockage de l'arbre binaire, l'étape suivante consiste à utiliser la structure en chaîne pour réaliser le stockage de l'arbre binaire.

Tout d'abord, la traversée de l'arbre binaire

Le moyen le plus simple d’apprendre la structure arborescente binaire est de la parcourir. La soi-disant traversée d'arbre binaire (Traversal) consiste à effectuer tour à tour les opérations correspondantes sur les nœuds de l'arbre binaire selon certaines règles spécifiques, et chaque nœud n'est opéré qu'une seule fois. Les opérations effectuées par les nœuds d'accès dépendent du problème d'application spécifique. Le parcours est l'une des opérations les plus importantes sur un arbre binaire, et c'est également la base d'autres opérations sur un arbre binaire.

1. Parcours de l'ordre de niveau de l'arbre binaire

Le parcours par ordre de couche de l'arbre binaire a été utilisé lorsque le stockage de structure séquentielle de l'arbre binaire a été introduit plus tôt, c'est-à-dire que l'ordre de stockage de l'arbre binaire complet dans le tableau est l'ordre de parcours par ordre de couche de l'arbre binaire.
insérer la description de l'image ici

2. Parcours de précommande de l'arbre binaire

Traversée de précommande (Preorder Traversal, également connue sous le nom de traversée de précommande) - l'opération de visite du nœud racine se produit avant de parcourir ses sous-arbres gauche et droit. Le signe # indique l'emplacement d'une arborescence vide.
insérer la description de l'image ici

3. Parcours dans l'ordre d'un arbre binaire

Inorder Traversal (Inorder Traversal) - l'opération de visite du nœud racine se produit lors de la traversée de ses sous-arbres gauche et droit (au milieu). Le signe # indique l'emplacement d'une arborescence vide.

insérer la description de l'image ici

4. Traversée post-commande de l'arbre binaire

Traversée post-ordre — L'opération de visite du nœud racine se produit après avoir traversé ses sous-arbres gauche et droit. Le signe # indique l'emplacement d'une arborescence vide.
insérer la description de l'image ici

5. Mise en œuvre du code

Après avoir connu les règles de parcours pré-, intermédiaire et post-ordre d'un arbre binaire, nous pouvons implémenter un arbre binaire, puis implémenter ces méthodes de parcours.
Définition de l'arbre binaire

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>

typedef int BTDataType;

typedef struct BinaryTreeNode
{
    
    
	//存储该结点的左子树的根结点地址
	struct BinaryTreeNode* left;
	//存储该结点的右子树的根结点地址
	struct BinaryTreeNode* right;
	//存储该结点的数据
	BTDataType data;
}BTNode;

Créer un nœud d'arbre binaire

//创建一个新的结点,并且将该结点返回
BTNode* BuyNode(BTDataType x)
{
    
    
	BTNode* newNode = (BTNode*)malloc(sizeof(BTNode));
	if (newNode == NULL)
	{
    
    
		perror("malloc fail");
		exit(-1);
	}
	newNode->left = NULL;
	newNode->right = NULL;
	newNode->data = x;
	return newNode;
}

créer un arbre binaire

//创建一棵二叉树,并且返回该二叉树的根结点
BTNode* CreateBinaryTree()
{
    
    
	//建立二叉树的结点
	BTNode* node1 = BuyNode(1);
	BTNode* node2 = BuyNode(2);
	BTNode* node3 = BuyNode(3);
	BTNode* node4 = BuyNode(4);
	BTNode* node5 = BuyNode(5);
	BTNode* node6 = BuyNode(6);

	//建立二叉树结点之间的关系
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	//返回该二叉树的根结点
	return node1;
}

Implémenter le parcours des précommandes

//先序遍历
void PreOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//如果访问结点不为NULL,就将该结点的数据打印出来
	printf("%d ", root->data);
	//因为为先序遍历,所以先访问根节点,然后再访问左子树
	PreOrder(root->left);
	//当左子树访问完再访问右子树
	PreOrder(root->right);
}

Autrement dit, la séquence d'appels récursifs de fonctions est illustrée dans la figure.
insérer la description de l'image ici

Implémenter le parcours dans l'ordre

//中序遍历
void PreOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//因为为中序遍历,所以先访问左子树,然后再访问根节点
	PreOrder(root->left);
	//左子树访问完后再打印根结点数据
	printf("%d ", root->data);
	//当根结点访问完再访问右子树
	PreOrder(root->right);
}

Implémenter le parcours post-commande

//后序遍历
void PostOrder(BTNode* root)
{
    
    
	//如果访问的结点为NULL,就打印#
	if (root == NULL)
	{
    
    
		printf("# ");
		return;
	}
	//因为为后序遍历,所以先访问左子树,然后再访问右子树
	PostOrder(root->left);
	//当左子树结点访问完毕后,访问右子树结点
	PostOrder(root->right);
	//当左右子树结点都访问完后,再访问根节点数据
	printf("%d ", root->data);
}

Deuxièmement, la réalisation de certaines opérations de l'arbre binaire

Lorsque nous avons implémenté le parcours de l'arbre binaire, nous pouvons implémenter d'autres opérations de l'arbre binaire

1. Trouvez le nombre de nœuds dans l'arbre binaire

Lorsqu'on demande des nœuds d'arbre binaire, la première méthode à laquelle nous pensons est de définir une variable globale count, puis de visiter tour à tour les nœuds de l'arbre binaire. Si le nœud n'est pas vide, alors count++, et la valeur finale de count est le nœud dans le numéro de l'arbre binaire. Il convient de noter que lorsque nous appelons cette méthode pour la deuxième fois, comme count est une variable globale, elle n'est plus 0 à ce moment-là, donc la valeur de count doit être réinitialisée à 0.

int count = 0;
void BinaryTreeSize(BTNode* root)
{
    
    
	//如果访问的结点为NULL,则count不进行+1
	if (root == NULL)
	{
    
    
		return;
	}
	//每访问到一个不为NULL结点就让count+1
	++count;
	//然后再去访问该结点的左子树和右子树
	BinaryTreeSize(root->left);
	BinaryTreeSize(root->right);
}

Il n'est pas sûr d'utiliser des variables globales pour obtenir le nombre de nœuds d'arborescence dans la méthode ci-dessus, et les variables globales peuvent être modifiées, et chaque fois que la fonction est appelée, les variables globales doivent être réinitialisées. Nous pouvons donc utiliser la deuxième méthode pour trouver le nombre de nœuds de l’arbre binaire.

int TreeSize2(BTNode* root)
{
    
    
	//当root结点为NULL时,就返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//当root结点不为NULL,就返回root结点的数量,然后加上root结点左子树和右子树的结点的数量。
	return 1 + TreeSize2(root->left) + TreeSize2(root->right);
}

Graphiques de récursion et de repli pour les fonctions.
insérer la description de l'image ici

2. Trouvez le nombre de nœuds feuilles dans l'arbre binaire

//二叉树叶子结点个数
int BinaryTreeLeafSize(BTNode* root)
{
    
    
	//如果该结点为NULL,则返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//如果该结点为叶子结点,则返回1
	if (root->left == NULL && root->right == NULL)
	{
    
    
		return 1;
	}
	//如果该结点不为NULL,也不为叶子结点,就返回该节点的左子树和右子树中的叶子结点数
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

3. Trouvez le nombre de nœuds dans la kième couche de l'arbre binaire

//二叉树第k层结点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
    
    
	assert(k >= 1);
	//如果该结点为NULL,就返回0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//k==1,说明要求的就是这一层的结点数,返回1
	if (k == 1)
	{
    
    
		return 1;
	}
	//如果该结点不为NULL,且k!=1,说明求的不是该层的结点数,让k-1,然后求该结点的左子树和右子树
	return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);
}

4. Trouvez la profondeur de l'arbre binaire

//求二叉树深度
int BinaryTreeDepth(BTNode* root)
{
    
    
	//如果该结点为NULL,则深度为0
	if (root == NULL)
	{
    
    
		return 0;
	}
	//然后求出该结点的左子树和右子树的深度
	int leftDepth = BinaryTreeDepth(root->left);
	int rightDepth = BinaryTreeDepth(root->right);
	//如果该结点的左子树深度大于右子树深度
	if (leftDepth > rightDepth)
	{
    
    
		//就返回该结点左子树的深度加这一层的深度
		return leftDepth + 1;
	}
	//如果该结点的左子树深度小于等于右子树深度
	else
	{
    
    
		//就返回右子树的深度加这一层的深度
		return rightDepth + 1;
	}
}

5. Trouvez le nœud dont la valeur est x dans l'arbre binaire

//二叉树查找值为x的结点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    
    
	//当结点为NULL时,返回NULL
	if (root == NULL)
	{
    
    
		return NULL;
	}
	//当该结点数据为x时,返回该结点
	if (root->data == x)
	{
    
    
		return root;
	}
	//当该结点数据不为x时,先遍历该结点的左子树
	BTNode* left = BinaryTreeFind(root->left, x);
	//如果该结点的左子树返回的结点不为NULL,则说明在左子树中找到了存储x的结点,则此时left就存储该结点的地址。直接将left返回即可
	if (left!=NULL)
	{
    
    
		return left;
	}

	//如果该结点的左子树也没有查到就去遍历该结点的右子树,
	BTNode* right = BinaryTreeFind(root->right, x);
	//当该结点的右子树返回的结点不为NULL,则说明在右子树中找到了存储x的结点,此时right就存储该结点的地址。直接将right返回即可
	if (right!=NULL)
	{
    
    
		return right;
	}
	
	//当该结点的数据和该结点的左子树和右子树的结点中都没有该数据,则二叉树中没有该数据,此时返回NULL
	return NULL;
}

6. Destruction de l'arbre binaire

La destruction de l'arbre binaire consiste à libérer tour à tour tout l'espace demandé par chaque nœud de l'arbre binaire, et il est nécessaire de parcourir l'arbre binaire une fois. Ici, nous devons utiliser le parcours post-ordre, c'est-à-dire la première libération les nœuds du sous-arbre gauche du nœud racine, puis libérer le nœud racine. Le nœud du sous-arbre droit du nœud, et enfin libérer l'espace du nœud racine. Parce que si l'espace du nœud racine est d'abord libéré par parcours de pré-commande, les sous-arbres gauche et droit du nœud racine ne seront pas trouvés.

//二叉树的销毁
void BinaryTreeDestroy(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return;
	}
	//采用后序遍历释放二叉树结点
	BinaryTreeDestroy(root->left);
	BinaryTreeDestroy(root->right);
	free(root);
}

3. Implémentation du parcours d'ordre au niveau de l'arbre binaire

1. Parcours de l'ordre des couches

La réalisation du parcours par ordre de niveau de l'arbre binaire nécessite l'assistance d'une file d'attente. Par exemple, il y a l'arbre binaire suivant, placez d'abord le nœud racine de l'arbre binaire dans la file d'attente.
insérer la description de l'image ici
Ensuite, le chef de file d'attente est retiré de la file d'attente et les enfants gauche et droit du nœud principal sont placés dans la file d'attente.
insérer la description de l'image ici
Répétez ensuite l'opération ci-dessus. Mettez les nœuds restants dans la file d'attente un par un.
insérer la description de l'image ici
jusqu'à ce que la file d'attente soit vide. L'ordre dans lequel les nœuds sont retirés de la file d'attente est l'ordre dans lequel l'arbre binaire est parcouru.
insérer la description de l'image ici

2. Implémentation du code de traversée de séquence de couches

Le parcours hiérarchique nécessite une file d'attente auxiliaire, mettez d'abord en file d'attente le nœud racine de l'arbre binaire, puis retirez le nœud racine, mettez en file d'attente les enfants gauche et droit du nœud racine, puis retirez les enfants gauche et droit de la file d'attente et mettez en file d'attente les enfants gauche et droit de les enfants gauche et droit. La boucle continue jusqu'à ce que la file d'attente soit vide.

//二叉树的层序遍历
void LevelOrder(BTNode* root)
{
    
    
	//创建一个辅助队列
	Queue qt;
	QueueInit(&qt);
	//如果二叉树为空,就退出函数
	if (root == NULL)
	{
    
    
		return;
	}
	//将根节点的地址入队
	QueuePush(&qt, root);
	//如果队列中不为空,就进入循环
	while (!QueueEmpty(&qt))
	{
    
    
		//创建一个BTNode*类型的指针接收队列中队头结点存的地址
		BTNode* head = QueueFront(&qt);
		//将队头结点出队。
		QueuePop(&qt);
		//打印出队结点的数据
		printf("%d ", head->data);
		//如果出队结点的左孩子不为空,就将结点左孩子入队
		if (head->left != NULL)
		{
    
    
			QueuePush(&qt, head->left);
		}
		//如果出队结点的右孩子不为空,就将结点右孩子入队
		if (head->right != NULL)
		{
    
    
			QueuePush(&qt, head->right);
		}
	}
	printf("\n");
	QueueDestroy(&qt);

}

3. Application du parcours d'ordre des couches - juger si un arbre binaire est un arbre binaire complet

Lorsqu'il y a un arbre binaire dont il faut juger s'il s'agit d'un arbre binaire complet, le parcours par ordre de niveau de l'arbre binaire peut être emprunté à ce moment-là.
Par exemple, il existe un arbre binaire complet comme suit.L'arbre
insérer la description de l'image ici
binaire est parcouru par ordre de niveaux.D'abord, le noeud racine entre dans la file d'attente, puis le noeud principal quitte la file d'attente et les enfants gauche et droit du noeud sont stockés dans le nœud principal entre dans la file d’attente. Si les enfants de gauche et de droite sont vides, ils rejoindront également l'équipe.
insérer la description de l'image ici
Lorsque les données stockées dans le nœud principal de la file d'attente sont NULL, arrêtez l'opération ci-dessus. À l'heure actuelle, les données stockées dans les nœuds de la file d'attente sont toutes NULL, on peut donc conclure que l'arbre binaire est un arbre binaire complet.
insérer la description de l'image ici

Par exemple, il existe un arbre binaire incomplet comme suit.L'arbre
insérer la description de l'image ici
binaire suit les étapes de parcours de l'ordre des couches.D'abord, le noeud racine entre dans la file d'attente, puis le noeud principal de la file d'attente sort de la file d'attente, et les enfants gauche et droit du noeud stockés dans le nœud de tête de file d’attente entrent dans la file d’attente. Si les enfants de gauche et de droite sont vides, ils rejoindront également l'équipe.
insérer la description de l'image ici
Lorsque les données stockées dans le nœud principal de la file d'attente sont NULL, arrêtez l'opération ci-dessus. À l'heure actuelle, certaines des données stockées dans les nœuds de la file d'attente sont NULL, et d'autres ne le sont pas, on peut donc en conclure que l'arbre binaire n'est pas un arbre binaire complet.
insérer la description de l'image ici
Sur la base des conditions ci-dessus, nous pouvons modifier une certaine logique de parcours d'ordre de niveau afin qu'elle puisse juger si l'arbre binaire est un arbre binaire complet.

int BinaryTreeComplete(BTNode* root)
{
    
    
	//创建一个辅助队列
	Queue qt;
	QueueInit(&qt);
	
	//如果二叉树根节点不为空,就将根节点的地址入队列
	if (root != NULL)
	{
    
    
		QueuePush(&qt, root);
	}
	
	//如果队列中不为空,就进入循环
	while (!QueueEmpty(&qt))
	{
    
    
		//将队列的队头结点存的数据返回
		BTNode* head = QueueFront(&qt);
		//将队头结点出队
		QueuePop(&qt);
		//如果该队头结点存的数据不为NULL,就将该结点的左右孩子入队,左右孩子为NULL也入队
		if (head != NULL)
		{
    
    
			QueuePush(&qt, head->left);
			QueuePush(&qt, head->right);
		}
		//如果该队头结点存的数据为空,说明已经到达队列中第一个NULL,此时跳出循环
		else
		{
    
    
			break;
		}
	}
	//此时如果队列不为空,就继续遍历队列中的元素
	while (!QueueEmpty(&qt))
	{
    
    
		//将队头结点存的数据返回
		BTNode* head = QueueFront(&qt);
		//将队头结点出队
		QueuePop(&qt);
		//如果队列中第一个NULL后还有队列结点存的数据不为NULL,则说明该二叉树不为完全二叉树
		if (head != NULL)
		{
    
    
			//将队列销毁
			QueueDestroy(&qt);
			//返回0则代表该二叉树不是完全二叉树
			return 0;
		}
	}
	//如果当队列中结点全部遍历完,并且存的数据都为NULL,说明该二叉树为完全二叉树
	//销毁队列
	QueueDestroy(&qt);
	//返回1代表为完全二叉树
	return 1;
}

Je suppose que tu aimes

Origine blog.csdn.net/dong132697/article/details/132562478
conseillé
Classement