[Structure de données] Implémentation de la structure de la chaîne arborescente binaire et de ses opérations communes

Table des matières

1. Arbre binaire frottant à la main

2. Parcours d'arbre binaire

2.1 Parcours pré-ordre, in-ordre et post-ordre

2.2 Parcours d'ordre de niveau d'arbre binaire

3. Opérations courantes sur les arbres binaires

3.1 Trouver le nombre de nœuds de l'arbre binaire

3.2 Trouver le nombre de nœuds feuilles de l'arbre binaire

3.3 Trouver le nombre de nœuds dans la kième couche de l'arbre binaire

3.3 Trouver la profondeur de l'arbre binaire

3.4 Trouver le nœud dont la valeur est x dans l'arbre binaire

4. Destruction de l'arbre binaire


1. Arbre binaire frottant à la main

Avant d'apprendre les opérations de base d'un arbre binaire, vous devez créer un arbre binaire avant de pouvoir apprendre ses opérations de base associées. Comme nous n'avons pas une compréhension approfondie de la structure de l'arbre binaire, afin de réduire le coût d'apprentissage, voici un manuel rapide pour créer un arbre binaire simple et entrer rapidement dans l'apprentissage de l'opération de l'arbre binaire. presque compris, nous allons faire demi-tour et étudier la véritable création de l'arbre binaire.

 Définissez les nœuds d'un arbre binaire :

typedef int BTDataType;
typedef struct BinaryTreeNode
{
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
	BTDataType data;
}BTNode;

Arbre binaire manuscrit selon l'arborescence binaire ci-dessus :

BTNode* BuyNode(BTDataType data)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	assert(node);
	node->data = data;
	node->left = node->right = NULL;
	return node;
}

BTNode* CreatBinaryTree()
{
	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;
}

De cette façon, nous avons écrit un arbre binaire simple.

2. Parcours d'arbre binaire

2.1 Parcours pré-ordre, in-ordre et post-ordre

La façon la plus simple d'apprendre la structure de l'arbre binaire est de la parcourir. Le soi-disant parcours d'arbre binaire (Traversal) consiste à effectuer des opérations correspondantes sur les nœuds de l'arbre binaire à tour de rôle 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 aussi la base d'autres opérations sur un arbre binaire.

 Selon les règles, la traversée de l'arbre binaire comprend : la traversée de structure récursive préordre/inordre/postordre :

  1. Traversée de pré-ordre (Preorder Traversal, également appelée traversée de pré-ordre) - l'opération de visite du nœud racine se produit avant de traverser ses sous-arbres gauche et droit.
  2. 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).
  3. Postorder Traversal - L'opération de visite du nœud racine se produit après avoir traversé ses sous-arbres gauche et droit.

Étant donné que le nœud visité doit être la racine d'un certain sous-arbre, N (nœud), L (sous-arbre gauche) et R (sous-arbre droit) peuvent être interprétés comme la racine, le sous-arbre gauche de la racine et le sous-arbre droit de la racine . NLR, LNR et LRN sont également appelés respectivement traversée de la première racine, traversée de la racine intermédiaire et traversée de la racine arrière.

Pour le parcours d'arbre binaire, le code est très simple à écrire, mais pour les débutants, il est un peu difficile à comprendre. Voici trois codes de parcours, vous pouvez d'abord y jeter un œil :

traversée de précommande

void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	printf("%d ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}

Parcours dans l'ordre

void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	InOrder(root->left);
	printf("%d ", root->data);
	InOrder(root->right);
}

traversée post-commande

void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("# ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->data);
}

Après avoir lu le code, pensez-vous que ces trois parcours sont très similaires ? Exécutons le code des trois parcours sur le compilateur :

int main()
{
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
	printf("\n");
	InOrder(root);
	printf("\n");
	PostOrder(root);
	return 0;
}

résultat de l'opération :

 Graphe d'expansion récursive de parcours de préordre :

 Le parcours dans l'ordre et le parcours ultérieur sont similaires à cette image.

2.2 Parcours d'ordre de niveau d'arbre binaire

La traversée d'ordre de niveau d'un arbre binaire est une méthode de recherche en largeur d'abord (BFS). Il traverse l'arbre binaire couche par couche dans l'ordre hiérarchique, c'est-à-dire qu'en partant du nœud racine, il traverse d'abord les nœuds de la première couche, puis traverse les nœuds de la deuxième couche, et ainsi de suite, jusqu'à ce que toutes les couches soient traversées.

Une manière courante d'implémenter la traversée d'ordre de niveau consiste à utiliser une file d'attente. Les idées spécifiques sont les suivantes :

  1. Créez une file d'attente vide et mettez en file d'attente le nœud racine.
  2. Parcourez les étapes suivantes jusqu'à ce que la file d'attente soit vide :
  • Retirer un nœud de la file d'attente, en stockant sa valeur dans la liste des résultats.
  • Si le nœud a un enfant gauche, placez l'enfant gauche dans la file d'attente.
  • Si le nœud a un enfant droit, placez l'enfant droit dans la file d'attente.

De cette manière, lorsque la file d'attente est vide, le processus de parcours est terminé et le résultat du parcours hiérarchique est stocké dans la liste de résultats.

 Le code suivant utilise la file d'attente dans C++ STL pour éviter les problèmes d'écriture manuscrite dans la file d'attente :

void LevelOrder(BTNode* root)
{
	assert(root);
	queue<BTNode*> a;
	a.push(root);
	while (!a.empty())
	{
		BTNode* front = a.front();
		a.pop();
		printf("%d ", front->data);
		if (front->left)
		{
			a.push(front->left);
		}
		if (front->right)
		{
			a.push(front->right);
		}
	}
}

int main()
{
	BTNode* root = CreatBinaryTree();
	LevelOrder(root);
	return 0;
}

Résultat de sortie :

3. Opérations courantes sur les arbres binaires

3.1 Trouver le nombre de nœuds de l'arbre binaire

première méthode :

Définissez un nombre de variables globales, puis traversez chaque nœud, chaque fois qu'un nœud est traversé, le nombre augmentera de 1

code:

int count = 0;
void TreeSize1(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	count++;
	TreeSize1(root->left);
	TreeSize1(root->right);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	count = 0;
	TreeSize1(root);
	printf("%d\n", count);
	return 0;
}

Deuxième méthode :

int TreeSize2(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize2(root->left) + TreeSize2(root->right) + 1;
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeSize2: %d\n", TreeSize2(root));
	return 0;
}

Vérifiez d'abord si le nœud racine est vide, s'il est vide, cela signifie qu'il s'agit d'un arbre vide, et retournez 0 directement. Si le nœud racine n'est pas vide, appelez-le de manière récursive pour compter le nombre de nœuds dans les sous-arbres gauche et droit, puis ajoutez le nombre de nœuds dans le sous-arbre gauche, le nombre de nœuds dans le sous-arbre droit et le numéro de la racine node lui-même (1 node) , et renvoie enfin le résultat.

De cette manière récursive, la fonction peut calculer le nombre total de tous les nœuds de l'arbre binaire.

3.2 Trouver le nombre de nœuds feuilles de l'arbre binaire

Idées spécifiques :

Vérifiez d'abord si le nœud racine est vide, s'il est vide, cela signifie qu'il s'agit d'un arbre vide, et retournez 0 directement. Ensuite, déterminez si le nœud actuel est un nœud feuille en jugeant si le sous-arbre gauche et le sous-arbre droit du nœud racine sont tous les deux vides . S'il s'agit d'un nœud feuille, renvoie 1. S'il ne s'agit pas d'un nœud feuille, appelez-le de manière récursive pour calculer le nombre de nœuds feuilles dans le sous-arbre gauche et le sous-arbre droit, et ajoutez-les comme résultat et retour.

int TreeLeafSize(BTNode* root)
{
	if (root == NULL) return 0;
	if (root->left == NULL && root->right == NULL) return 1;
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeLeafSize: %d\n", TreeLeafSize(root));
	return 0;
}

3.3 Trouver le nombre de nœuds dans la kième couche de l'arbre binaire

Idées :

Vérifiez d'abord si le nœud racine est vide, s'il est vide, cela signifie qu'il s'agit d'un arbre vide, et retournez 0 directement. Si k est égal à 1, cela signifie que le calque courant est le calque cible et renvoie 1. Si k est supérieur à 1, appelez-le de manière récursive pour compter le nombre de nœuds au niveau k-1 dans les sous-arbres gauche et droit, et additionnez-les et retournez comme résultat.

De cette manière récursive, la fonction peut calculer le nombre total de nœuds de niveau k dans l'arbre binaire.

int TreeKLevel(BTNode* root, int k)
{
	assert(k >= 1);
	if (root == NULL) return 0;
	if (k == 1) return 1;
	return TreeKLevel(root->left, k - 1) + TreeKLevel(root->right, k - 1);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeKLevel: %d\n", TreeKLevel(root, 2));//第2层节点数量
	printf("TreeKLevel: %d\n", TreeKLevel(root, 3));//第3层节点数量
	printf("TreeKLevel: %d\n", TreeKLevel(root, 4));//第4层节点数量
	return 0;
}

Le graphe d'expansion récursif de la fonction :

3.3 Trouver la profondeur de l'arbre binaire

int TreeDepth(BTNode* root)
{
	if (root == NULL) return 0;
	int l = TreeDepth(root->left); //左子树的深度
	int r = TreeDepth(root->right); //右子树的深度
	return (l > r ? l : r) + 1; //返回左右子树深度的较大值加自身的深度1
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeDepth: %d\n", TreeDepth(root));
	return 0;
}

Vérifiez d'abord si le nœud racine est vide, s'il est vide, cela signifie qu'il s'agit d'un arbre vide, et revenez directement à la profondeur 0. Ensuite, la fonction calcule les profondeurs des sous-arbres gauche et droit en s'appelant elle-même de manière récursive, en stockant les résultats dans les variables l et r, respectivement. Ensuite, en comparant la taille de l et r, sélectionnez la valeur la plus grande et ajoutez-y 1 (représentant la profondeur du nœud actuel) comme profondeur de l'arbre binaire entier.

De cette manière récursive, la fonction peut calculer la profondeur (hauteur) de l'arbre binaire.

3.4 Trouver le nœud dont la valeur est x dans l'arbre binaire

Lorsque vous recherchez un nœud avec une valeur de x dans un arbre binaire, essayez d'utiliser le parcours de préordre pour améliorer l'efficacité.

BTNode* TreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL) return NULL;
	if (root->data == x) return root;
	BTNode* ret1 = TreeFind(root->left, x);
	if (ret1)
	{
		return ret1;
	}
	BTNode* ret2 = TreeFind(root->right, x);
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}

int main()
{
	BTNode* root = CreatBinaryTree();
	BTNode* ret = TreeFind(root, 3);
	if (ret)
	{
		printf("找到了:%d\n", ret->data);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

Vérifiez d'abord si le nœud racine est vide, s'il est vide, cela signifie qu'il s'agit d'un arbre vide, et renvoyez NULL directement. Ensuite, la fonction vérifie si les données du nœud courant sont égales à la valeur cible X. Si elle est égale, cela signifie que le nœud cible a été trouvé et renvoie un pointeur vers le nœud courant. S'il n'est pas égal, appelez récursivement la fonction pour trouver la valeur cible x respectivement dans le sous-arbre gauche et le sous-arbre droit. Si le pointeur renvoyé n'est pas vide, cela signifie que le nœud cible se trouve dans le sous-arbre et renvoie le pointeur directement. . Si le nœud cible n'est pas trouvé dans les sous-arborescences gauche et droite, NULL est renvoyé.

Avec cette récursivité, une fonction peut rechercher un nœud d'une valeur particulière dans un arbre binaire et renvoyer un pointeur vers ce nœud. Renvoie NULL si la valeur cible est introuvable.

4. Destruction de l'arbre binaire

Pour la destruction de l'arbre binaire, nous ne pouvons pas utiliser le parcours de préordre, car si nous utilisons le parcours de préordre, le nœud racine de l'arbre binaire sera détruit en premier, de sorte que les sous-arbres gauche et droit du nœud racine ne peuvent pas être trouvés. doit utiliser le premier Dans le parcours de l'ordre, le sous-arbre gauche et le sous-arbre droit du nœud doivent être enregistrés en premier. Mais si vous utilisez le parcours post-commande, vous pouvez facilement le résoudre.

Utilisez le parcours post-ordre pour détruire un arbre binaire dans l'ordre de l'enfant gauche, de l'enfant droit et du nœud racine.

void TreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	TreeDestory(root->left);
	TreeDestory(root->right);
	free(root);
}
int main()
{
	BTNode* root = CreatBinaryTree();
	PreOrder(root);
	TreeDestory(root);
	root = NULL;
}

Je suppose que tu aimes

Origine blog.csdn.net/m0_73648729/article/details/132289236
conseillé
Classement