[Estrutura de dados elementares] Implementação e passagem da estrutura da cadeia de árvore binária

 ================================================= =======================

Pagina inicial

repositório de código

Coluna da linguagem C

Coluna de estrutura de dados elementar

Coluna Linux

 ================================================= =======================

Índice

Prefácio

Implementação da estrutura da cadeia de árvore binária

Travessia de árvore binária

Travessia de pré-pedido, pedido e pós-pedido

Passagem de pré-encomenda

travessia em ordem

Travessia pós-ordem

Encontre o número de nós

Encontre o número total de nós 

Crie variáveis ​​para encontrar nós

Crie variáveis ​​estáticas modificadas

Divida as subárvores esquerda e direita e adicione raízes

Encontre o número de nós folha

Encontre o número de nós na camada K


Prefácio

Antes de aprender as operações básicas de uma árvore binária, você precisa primeiro criar uma árvore binária e, em seguida, aprender suas operações básicas relacionadas. Como atualmente nem todos têm um conhecimento profundo o suficiente da estrutura da árvore binária, a fim de reduzir o custo de aprendizado de todos, aqui criamos rapidamente uma árvore binária simples manualmente e entramos rapidamente no aprendizado da operação da árvore binária. Quando a estrutura da árvore binária está quase compreendida , voltaremos a estudar a árvore binária real. Método de criação.


Implementação da estrutura da cadeia de árvore binária

Podemos dividir uma árvore binária em duas subárvores esquerda e direita, e as duas subárvores são divididas em duas subárvores novamente. Desta forma, uma árvore binária pode ser desmontada por nós. Se não houver filhos esquerdo/direito, ela será registrada como vazio.

Código

typedef struct BinaryTreeNode 
{
	struct BinaryTreeNode * left;
	struct BinaryTreeNode* right;
	int  val;
}BTNode;
BTNode* BuyBTNode(int x)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	tmp->val = x;
	tmp->left = NULL;
	tmp->right = NULL;
	return tmp;
}
int main()
{
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;
	return 0;
}

Crie uma estrutura com ponteiros de estrutura esquerdo e direito apontando para seus filhos esquerdo e direito respectivamente. Assim, a árvore binária acima é implementada usando código.

Nota: O código acima não é uma forma de criar uma árvore binária. A maneira real de criar uma árvore binária será explicada em detalhes posteriormente.
Antes de examinar as operações básicas das árvores binárias, vamos revisar o conceito de árvores binárias. As árvores binárias são:
1. Árvore vazia
2. Não vazia: o nó raiz, a subárvore esquerda do nó raiz e a subárvore direita do nó raiz. nó raiz.

Pode-se perceber pelo conceito que a definição de árvore binária é recursiva, portanto as operações básicas de pós-ordem são basicamente implementadas de acordo com este conceito.


Travessia de árvore binária

Travessia de pré-pedido, pedido e pós-pedido

A maneira mais simples de aprender uma estrutura de árvore binária é atravessá-la. A chamada travessia de árvore binária (Traversal) consiste em realizar operações correspondentes nos nós da árvore binária em sequência de acordo com certas regras específicas, e cada nó é operado apenas uma vez. As operações realizadas ao acessar os nós dependem do problema específico da aplicação. Traversal é uma das operações mais importantes em uma árvore binária e também é a base para outras operações em uma árvore binária.

De acordo com as regras, a travessia de uma árvore binária inclui: travessia de estrutura recursiva de pré-ordem/ordem média/pós-ordem:
1. travessia de pré-ordem (também conhecida como travessia de pré-ordem) - a operação de acesso ao nó raiz ocorre atravessando suas subárvores esquerda e direita Antes.
2. Inorder Traversal (Traversal Inorder) - A operação de acesso ao nó raiz ocorre durante o percurso de suas subárvores esquerda e direita (intermediárias).
3. Traversal pós-ordem - A operação de acesso ao nó raiz ocorre após percorrer suas subárvores esquerda e direita. 

Para simplificar:

Travessia de pré-ordem: visite primeiro a raiz, depois visite a subárvore esquerda e, finalmente, visite a subárvore direita.

Percurso em ordem: primeiro visite a subárvore esquerda, depois visite a raiz e, finalmente, visite a subárvore direita.

Percurso pós-ordem: primeiro visite a subárvore esquerda, depois visite a subárvore direita e, finalmente, visite a raiz.

Como o nó visitado deve ser a raiz de uma determinada subárvore, N (nó), L (subárvore esquerda) e R (subárvore direita) podem ser interpretados como a raiz, a subárvore esquerda da raiz e a subárvore direita da raiz. NLR, LNR e LRN também são chamados de primeiro percurso de raiz, percurso de raiz intermediária e último percurso de raiz, respectivamente.

O N aqui representa NULL.

Passagem de pré-encomenda

A travessia da árvore binária é implementada usando recursão de função. A partir da raiz, ela é dividida em subárvores esquerda e direita e percorrida para baixo. Se as subárvores esquerda e direita não existirem, ela estará vazia (NULL).

Diagrama de passagem de pré-encomenda

 Implementar código

void PrevOrder(BTNode* node)
{
	if (node == NULL)
	{
		printf("NULL ");
		return ;
	}
	printf("%d ", node->val);
	PrevOrder(node->left);
	PrevOrder(node->right);
}

Primeiro, determine se o nó raiz está vazio. Se estiver vazio, significa uma árvore vazia e retorna NULL; a travessia de pré-ordem é a raiz visitada primeiro, então os dados são impressos diretamente e depois entram nas subárvores esquerda e direita em sequência . É difícil explicar claramente em palavras aqui, então vamos diretamente para o gráfico de chamada recursiva de função.

travessia em ordem

Não vou dar um diagrama aqui, você pode tentar desenhá-lo sozinho.

Implementar código 

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

Primeiro determine se o nó raiz está vazio. Se estiver vazio, significa uma árvore vazia e retorna NULL; a travessia em ordem é a subárvore esquerda visitada primeiro, então é percorrida até que a subárvore esquerda apontada pela última raiz esteja vazia , e a raiz neste momento é impressa.Os dados entram nas subárvores esquerda e direita em sequência.

Travessia pós-ordem

Implementar código

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

Primeiro, determine se o nó raiz está vazio. Se estiver vazio, significa uma árvore vazia e retorna NULL; a travessia pós-ordem também acessa primeiro a subárvore esquerda, então percorre até que a subárvore esquerda apontada pela última raiz esteja vazia e imprime os dados raiz neste momento. Imprime os dados da subárvore direita apontada por seu pai e, finalmente, imprime os dados do pai das subárvores esquerda e direita.


Encontre o número de nós

Encontre o número total de nós 

Crie variáveis ​​para encontrar nós

Muitos amigos pensarão em criar uma função variável para representar um nó recursivamente e adicioná-la uma vez para cada recursão.

Não, porque as variáveis ​​são criadas na área da pilha toda vez que a função é chamada e são destruídas quando a função é encerrada. Este método simplesmente não funciona.

Crie variáveis ​​estáticas modificadas

O uso de variáveis ​​​​pode ser destruído, então vou criar uma variável estática para alterar seu ciclo de vida, e é isso. Não há problema em encontrar o número total de nós em uma árvore binária, mas e se tivermos várias árvores binárias? Também pode ser resolvido: basta definir a variável como zero antes de calcular o número de pontos de resumo de cada vez. Este é um método viável.

ps: Variáveis ​​estáticas só serão criadas uma vez na recursão de função! ! !

Divida as subárvores esquerda e direita e adicione raízes

A ideia mais importante de uma árvore binária é dividir a árvore binária. Aqui dividimos a árvore binária em subárvores esquerda e direita e usamos recursão para encontrar o número de nós das subárvores esquerda e direita, mais o nó raiz de cada tempo. É o número total de nós?

Implementar código

int size = 0;
int TreeSize(BTNode* root)
{
	//静态区的变量只初始化一次
	// int size=0;
    //直接使用变量不推荐
	//static int size = 0;
	//if (root == NULL)
	//{
	//	return 0;
	//}
	//else
	//{
	//	++size;
	//}
	//TreeSize(root->left);
	//TreeSize(root->right);
	//return size;

	//优化
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}

Encontre o número de nós folha

Caso 1: Quando a árvore está vazia, os nós folhas estão vazios;

Caso 2: Quando as subárvores esquerda e direita estão vazias, o nó folha é 1;

Caso 3: Ainda use o método de divisão para dividir uma árvore binária e use solução recursiva; 

Implementar código

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);
}

Encontre o número de nós na camada K

Suponha que busquemos o terceiro nível, ou usemos o método recursivo de divisão de árvore, dividido no segundo nível (k-1) da subárvore esquerda mais o segundo nível (k-1) da subárvore direita.

Implementar código

int TreeKlevel(BTNode* root,int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}
	return TreeKlevel(root->left, k - 1) + TreeKlevel(root->right, k - 1);
}

Código completo

#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef struct BinaryTreeNode 
{
	struct BinaryTreeNode * left;
	struct BinaryTreeNode* right;
	int  val;
}BTNode;
BTNode* BuyBTNode(int x)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	tmp->val = x;
	tmp->left = NULL;
	tmp->right = NULL;
	return tmp;
}
void PrevOrder(BTNode* node)
{
	if (node == NULL)
	{
		printf("NULL ");
		return ;
	}
	printf("%d ", node->val);
	PrevOrder(node->left);
	PrevOrder(node->right);
}
void InOrder(BTNode* node)
{
	if (node == NULL)
	{
		printf("NULL ");
		return ;
	}
	InOrder(node->left);
	printf("%d ", node->val);
	InOrder(node->right);
}
void PostOrder(BTNode* node)
{
	if (node == NULL)
	{
		printf("NULL ");
		return ;
	}
	PostOrder(node->left);
	PostOrder(node->right);
	printf("%d ", node->val);
}
//求结点个数
int size = 0;
int TreeSize(BTNode* root)
{
	//静态区的变量只初始化一次
	// int size=0;
	//static int size = 0;
	//if (root == NULL)
	//{
	//	return 0;
	//}
	//else
	//{
	//	++size;
	//}
	//TreeSize(root->left);
	//TreeSize(root->right);
	//return size;

	//优化
	return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
//求叶子结点个数
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);
}
//求第K层的结点个数
int TreeKlevel(BTNode* root,int k)
{
	assert(k > 0);
	if (root == NULL)
	{
		return 0;
	}

	if (k == 1)
	{
		return 1;
	}
	return TreeKlevel(root->left, k - 1) + TreeKlevel(root->right, k - 1);
}
int main()
{
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	node1->left = node2;
	node1->right = node4;
	node2->left = node3;
	node4->left = node5;
	node4->right = node6;

	//前序遍历
	PrevOrder(node1);

	printf("\n");
	//中序遍历
	InOrder(node1);
	printf("\n");
	//后序遍历
	PostOrder(node1);
	printf("\n");
	printf("%d \n", TreeSize(node1));
	printf("%d\n", TreeLeafSize(node1));
	printf("%d\n", TreeKlevel(node1, 3));
	return 0;
}


Resumo: A travessia da árvore binária é implementada usando recursão de função, o que é realmente difícil de entender. Não fazemos isso apenas no nível de implementação do código, mas também desenhamos o diagrama de expansão recursiva baseado na recursão de função para que possamos entender e aprofundar nossa impressão. São necessários mais alguns desenhos.

Acho que você gosta

Origin blog.csdn.net/qq_55119554/article/details/133188520
Recomendado
Clasificación