Partage de technologie

[Structure des données] 09. Arbre et arbre binaire

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. Concept et structure de l'arbre

1.1 La notion d'arbre

l'arbre est unnon linéaire Une structure de données, qui est un ensemble de relations hiérarchiques composées de n (n>=0) nœuds limités.PaquetOn l'appelle un arbre parce qu'il ressemble à un arbre à l'envers, ce qui signifie qu'il a les racines pointées vers le haut et les feuilles pointées vers le bas.

  • Nœud racine : le nœud racine n’a pas de nœud prédécesseur.
  • À l'exception du nœud racine, les nœuds restants sont divisés en sous-arbres avec une structure similaire à celle d'un arbre. Le nœud racine de chaque sous-arbre a un et un seul prédécesseur et peut avoir 0 ou plusieurs successeurs.
  • L’arbre est doncdéfinition récursivede.

Insérer la description de l'image ici

1.2 Concepts associés aux arbres

Insérer la description de l'image ici

  • Degré de nœud: Le nombre de sous-arbres contenus dans un nœud est appelé le degré du nœud, comme le montre la figure ci-dessus : A est 4 ;
  • Nœud feuille ou nœud terminal: Les nœuds de degré 0 sont appelés nœuds feuilles ; comme le montre la figure ci-dessus : les nœuds B, F, G, D et H sont des nœuds feuilles.
  • Nœud non terminal ou nœud de branche: Un nœud dont le degré n'est pas 0 Comme le montre la figure ci-dessus : les nœuds A, C et E sont des nœuds de branche.
  • Nœud parent ou nœud parent: Si un nœud contient des nœuds enfants, alors ce nœud est appelé le nœud parent de son nœud enfant. Comme le montre la figure ci-dessus : A est le nœud parent de B ;
  • nœud enfant ou nœud enfant: Le nœud racine du sous-arbre contenu par un nœud est appelé nœud enfant du nœud. Comme le montre la figure ci-dessus : B est le nœud enfant de A ;
  • Nœud frère: Les nœuds avec le même nœud parent sont appelés nœuds frères, comme indiqué ci-dessus : B et C sont des nœuds frères.
  • degré d'arbre: Dans un arbre, le degré du plus grand nœud est appelé degré de l'arbre comme indiqué ci-dessus : le degré de l'arbre est 4 ;
  • Niveau du nœud: A partir de la définition de la racine, la racine est le 1er niveau, les nœuds enfants de la racine sont le 2ème niveau, et ainsi de suite ;
  • hauteur ou profondeur de l'arbre: Le niveau maximum de nœuds dans l'arbre comme indiqué ci-dessus : la hauteur de l'arbre est de 3 ;
  • noeud cousin: Les nœuds dont les parents sont au même niveau sont cousins ​​l'un de l'autre ; comme le montre la figure ci-dessus : F et G sont des nœuds frères l'un de l'autre.
  • Ancêtre du nœud: Tous les nœuds sur les branches de la racine au nœud comme le montre la figure ci-dessus : A est l'ancêtre de tous les nœuds ;
  • descendance : Tout nœud du sous-arbre enraciné à un certain nœud est appelé descendant de ce nœud.Comme indiqué ci-dessus : tous les nœuds sont des descendants de A
  • forêt: Une collection de m arbres disjoints est appelée une forêt ;

1.3 Représentation arborescente

La structure arborescente est plus compliquée que le tableau linéaire, et il est plus difficile de la stocker et de la représenter.Il est nécessaire de sauvegarder à la fois la plage de valeurs et la relation entre les nœuds et les nœuds., en fait, il existe de nombreuses façons de représenter les arbres, telles que :Notation parent, notation enfant, notation parent enfant et notation frère enfantattendez.

Dans le processus d'introduction de la structure de stockage suivante, nous prenons l'arbre suivant comme exemple.Insérer la description de l'image ici

1.3.1 Représentation des parents

Nous supposons que les nœuds de l'arbre sont stockés dans un ensemble d'espaces continus, et en même tempsDans chaque nœud, un indicateur est attaché pour indiquer la position de son nœud parent dans la liste chaînée. . Autrement dit, en plus de savoir de qui il s’agit, chaque nœud sait également où se trouvent ses parents.
Insérer la description de l'image ici
Parmi eux, data est le champ de données, qui stocke les informations de données du nœud. Et parent est un champ de pointeur qui stocke les indices des parents du nœud dans le tableau.
Ce qui suit est le code de définition de la structure des nœuds pour notre représentation parent.

/*树的双亲表示法结点结构定义*/
#define MAX_TREE_SIZE 100
typedef int TElemType;	//树结点的数据类型,目前暂定为整型
/*结点结构*/
typedef struct TreeNode
{
	TElemType data;	//结点数据
	int parent;	//双亲位置
}TreeNode;
/*树结构*/
typedef struct
{
	TreeNode nodes[MAX_TREE_SIZE];	//结点数组
	int r, n;	//根的位置和结点数
}PTree;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

Avec une telle structure de stockage, on peut facilement retrouver ses nœuds parents en fonction du pointeur parent du nœud utilisé.La complexité temporelle est 0(1) , jusqu'à ce que parent soit -1, indiquant que la racine du nœud de l'arborescence a été trouvée. Mais si nous voulons savoir quels sont les enfants du nœud, désolé, veuillez parcourir toute la structure.

1.3.2 Représentation des enfants frères

Nous venons d'étudier la structure de stockage de l'arbre du point de vue des parents et du point de vue de l'enfant. Et si nous regardions les frères des nœuds de l'arbre ? Bien sûr, pour une structure hiérarchique comme un arbre, nous étudions uniquement les frères ? frères des nœuds. Ce n'est pas possible. Après observation, nous avons constaté que pour tout arbre, le premier enfant de son nœud est unique s'il existe, et son frère droit est également unique s'il existe. Par conséquent, nous définissons deux pointeurs, pointant vers le premier enfant du nœud et le frère droit du nœud.

Insérer la description de l'image ici

/*树的孩子兄弟表示法结构定义*/
typedef struct TreeNode
{
	TElemtype data;
	struct TreeNode *firstchild, *rightsib;
} TreeNode,* pTreeNode;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.4 Applications pratiques des arbres

Insérer la description de l'image ici

2. Concept et structure de l'arbre binaire

2.1 Concept

Un arbre binaire est un ensemble fini de nœuds, qui est :

  • ou vide
  • Il se compose d’un nœud racine et de deux arbres binaires, également appelés sous-arbre gauche et sous-arbre droit.

Insérer la description de l'image ici

Comme on peut le voir sur la photo ci-dessus :

  1. Il n'y a aucun nœud de degré supérieur à 2 dans un arbre binaire
  2. Les sous-arbres d'un arbre binaire peuvent être divisés en sous-arbres gauche et droit, et l'ordre ne peut pas être inversé, donc l'arbre binaire estarbre commandé

Remarque : Tout arbre binaire est composé des situations suivantes :
Insérer la description de l'image ici

2.2 Arbres binaires spéciaux

  1. arbre binaire complet: un arbre binaire,Si le nombre de nœuds dans chaque couche atteint le maximum, l’arbre binaire est un arbre binaire complet. . C'est-à-dire que si le nombre de niveaux d'un arbre binaire est K et que le nombre total de nœuds est 2^k-1, alors c'est un arbre binaire complet.
  2. arbre binaire complet: Un arbre binaire complet est une structure de données très efficace.Les arbres binaires complets sont dérivés d'arbres binaires complets . Pour un arbre binaire à n nœuds de profondeur K, on ​​parle d'arbre binaire complet si et seulement si chaque nœud correspond biunivoque aux nœuds numérotés de 1 à n dans l'arbre binaire complet de profondeur K. Faire attention àUn arbre binaire complet est un type particulier d’arbre binaire complet.
    Insérer la description de l'image ici

2.3 Propriétés des arbres binaires

  1. Si le nombre de niveaux du nœud racine est spécifié comme étant 1, alors leIl y en a au plus 2^(i-1) sur la i-ème couche nœuds
  2. Si le nombre de niveaux du nœud racine est spécifié comme étant 1, alorsLe nombre maximum de nœuds d'un arbre binaire de profondeur h est 2^h-1
  3. Pour tout arbre binaire, siLe nombre de nœuds feuilles de degré 0 est n0 et le nombre de nœuds de branche de degré 2 est n2, alors n0=n2 +1
  4. Si le nombre de niveaux du nœud racine est spécifié à 1,La profondeur d'un arbre binaire complet avec n nœuds, h=log (n+1) (ps : log est en base 2, n+1 est le logarithme)
  5. Pour un arbre binaire complet à n nœuds, si tous les nœuds sont numérotés à partir de 0 dans l'ordre du tableau de haut en bas, de gauche à droite, alors pour le nœud de numéro de série i :
    • Si i>0, le numéro parent du nœud à la position i : (i-1)/2 i=0 ;, i est le numéro du nœud racine, il n'y a pas de nœud parent
    • Si 2i+1, 2i+1>=n sinon il n'y a plus d'enfant
    • Si 2i+2, 2i+2>=n sinon il n'y a pas de bon enfant

2.4 Structure de stockage de l'arbre binaire

Les arbres binaires peuvent généralement être stockés à l'aide de deux structures :Une structure séquentielle, une structure en chaîne.

  1. stockage séquentiel
    Le stockage de structure séquentielle doit être utilisétableauPour stocker, utilisez généralement un tableauConvient uniquement pour représenter des arbres binaires complets , car ce n'est pas un arbre binaire complet et il y aura une perte d'espace. En réalité, seul le tas utilise des tableaux pour le stockage.Le stockage séquentiel des arbres binaires est physiquement un tableau et logiquement un arbre binaire.
    Insérer la description de l'image ici2. Stockage en chaîne
    La structure de stockage liée d'un arbre binaire fait référence à :Utiliser une liste chaînée pour représenter un arbre binaire , c'est-à-dire utiliser des chaînes pour indiquer la relation logique des éléments. **La méthode habituelle est que chaque nœud de la liste chaînée se compose de trois champs, le champ de données et les champs de pointeur gauche et droit. Les pointeurs gauche et droit sont utilisés pour donner les adresses de stockage des points de liaison où l'enfant gauche et. l'enfant droit du nœud est localisé respectivement. **Les structures de chaînes sont divisées en chaînes binaires et chaînes trifurquées. Actuellement, nous étudions généralement les chaînes binaires.
    Insérer la description de l'image ici

3. Structure séquentielle et mise en œuvre de l'arbre binaire

Les arbres binaires ordinaires ne conviennent pas au stockage dans des tableaux car ils peuvent gaspiller beaucoup d'espace.etLes arbres binaires complets sont plus adaptés au stockage de structures séquentielles .En réalité, nous avons habituellementUn tas (un arbre binaire) utilise un tableau de structures séquentielles pour stocker, il convient de noter que le tas ici et le tas dans l'espace d'adressage du processus virtuel du système d'exploitation sont deux choses différentes. L'une est une structure de données et l'autre est une segmentation de zone qui gère la mémoire dans le système d'exploitation.
Pour une mise en œuvre et une application spécifiques, veuillez consulter :[Structure des données] 08. Applications tas et tas

4. Structure en chaîne de l'arbre binaire et sa mise en œuvre

4.1 Parcours d'arbre binaire

Le moyen le plus simple d’apprendre une structure arborescente binaire est de la parcourir.soi-disantLa traversée d'un arbre binaire (Traversal) consiste à effectuer les opérations correspondantes sur les nœuds de l'arbre binaire en séquence selon certaines règles, et chaque nœud n'est utilisé qu'une seule fois. . Les opérations effectuées en accédant aux nœuds 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 constitue également la base d'autres opérations sur un arbre binaire.

4.1.1 Parcours des précommandes

//根 左子树 右子树
void PrevOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	printf("%d ", root->val);
	PrevOrder(root->left);
	PrevOrder(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

La figure ci-dessous montre le processus récursif :
Insérer la description de l'image ici

4.1.2 Parcours dans l'ordre

//左子树 根 右子树
void InOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	InOrder(root->left);
	printf("%d ", root->val);
	InOrder(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Voici le processus récursif :
Insérer la description de l'image ici

4.1.3 Parcours post-commande

//左子树 右子树 根
void PostOrder(pTreeNode root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}

	PostOrder(root->left);
	PostOrder(root->right);
	printf("%d ", root->val);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Voici le processus récursif :
Insérer la description de l'image ici

4.2.4 Parcours par ordre de niveau

Parcours par ordre de niveau : en plus du parcours par ordre de niveau, du parcours par ordre dans l'ordre et du parcours par ordre de niveau, le parcours par ordre de niveau peut également être effectué sur des arbres binaires. Supposons que le numéro de niveau du nœud racine de l'arbre binaire soit 1. Le parcours par ordre de niveau commence à partir du nœud racine de l'arbre binaire. Il visite d'abord le nœud racine du premier niveau, puis visite les nœuds du second. niveau de gauche à droite, puis le troisième niveau Les nœuds du calque, et ainsi de suite,Le processus de visite des nœuds de l'arborescence couche par couche de haut en bas et de gauche à droite est un parcours par ordre de couche.
Illustration:
Insérer la description de l'image ici

Ici, nous intervenons dans la file d'attente pour effectuer un parcours de pré-ordre de l'arbre binaire.

// 层序遍历
void LevelOrder(pTreeNode root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			printf("NULL ");
		}
		else
		{
			printf("%d ", front->val);
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	QueueDestory(&q);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

4.1 Création et destruction d'arbres binaires

Pour créer et détruire un arbre binaire, nous utilisonsParcours d'arbre binairePar exemple.

//二叉树的创建
struct TreeNode* Creat(char* arr,int n,int* i)
{ 
    if(*i<n&&arr[*i]=='#')
    {
        (*i)++;
        return NULL;
    }
    
    TreeNode* newnode=(TreeNode*)malloc(sizeof(TreeNode));
    newnode->left=NULL;
    newnode->right=NULL;

    newnode->val=arr[(*i)++];
    newnode->left=Creat(arr,n,i);
    newnode->right=Creat(arr,n,i);
    
    return newnode;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
//二叉树的销毁
void TreeDestroy(struct TreeNode* root)
{
    if(root==NULL)
    {
        return;
    }
    TreeDestroy(root->left);
    TreeDestroy(root->right);
    free(root);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

4.3 Autres opérations sur les arbres binaires

Les opérations suivantes sont toutes réalisées selon l'idée de parcours.

// 二叉树节点个数
int TreeSize(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	return TreeSize(root->left) + TreeSize(root->right) + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
// 二叉树叶子节点个数
int TreeLeafSize(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return TreeLeafSize(root->left) + TreeLeafSize(root->right);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
// 二叉树第k层节点个数
int TreeLevelKSize(pTreeNode root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return TreeLevelKSize(root->left, k - 1) + TreeLevelKSize(root->right, k - 1);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
// 二叉树查找值为x的节点
pTreeNode TreeFind(pTreeNode root, TreeDataType x)
{

	if (root == NULL)
	{
		return NULL;
	}
	//相等就返回
	if (root->val == x)
		return root;
	//找左子树
	pTreeNode left=TreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	//找右子树
	pTreeNode right = TreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	//都没找到
	return NULL;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
//二叉树的高度
int TreeHeight(pTreeNode root)
{
	if (root == NULL)
	{
		return 0;
	}
	int max_left = TreeHeight(root->left) ;
	int max_right = TreeHeight(root->right);
	return max_left > max_right ? max_left + 1 : max_right + 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
//判断是否是完全二叉树
bool TreeComplete(pTreeNode root)
{
	Queue q;
	QueueInit(&q);
	if (root)
	{
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		else
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	while (!QueueEmpty(&q))
	{
		pTreeNode front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestory(&q);
			return false;
		}
	}
	QueueDestory(&q);
	return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36