[Estructura de datos elemental] Implementación y recorrido de la estructura de cadena de árbol binario

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

Página principal

repositorio de código

columna de lenguaje C

Columna de estructura de datos elemental

columna de linux

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

Tabla de contenido

Prefacio

Implementación de la estructura de cadena de árbol binario.

Recorrido de árbol binario

Recorrido de preorden, orden y postorden

recorrido de preorden

recorrido en orden

recorrido posterior al pedido

Encuentra el número de nodos

Encuentra el número total de nodos. 

Crear variables para encontrar nodos.

Crear variables estáticas modificadas

Divida los subárboles izquierdo y derecho y agregue raíces

Encuentra el número de nodos de hoja.

Encuentre el número de nodos en la capa Kth


Prefacio

Antes de aprender las operaciones básicas de un árbol binario, primero debe crear un árbol binario y luego podrá aprender las operaciones básicas relacionadas. Dado que actualmente no todos tienen una comprensión lo suficientemente profunda de la estructura del árbol binario, para reducir el costo de aprendizaje de todos, aquí creamos rápidamente un árbol binario simple manualmente y ingresamos rápidamente al aprendizaje de la operación del árbol binario.Cuando la estructura del árbol binario está casi comprendida , Volveremos a estudiar el método de creación del árbol binario real.


Implementación de la estructura de cadena de árbol binario.

Podemos dividir un árbol binario en dos subárboles izquierdo y derecho, y los dos subárboles se dividen nuevamente en dos subárboles. De esta manera, podemos desmontar un árbol binario. Si no hay hijos izquierdo/derecho, se registra como vacío.

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

Cree una estructura con punteros de estructura izquierdo y derecho apuntando a sus hijos izquierdo y derecho respectivamente. De esta manera, el árbol binario anterior se implementa usando código.

Nota: El código anterior no es una forma de crear un árbol binario. La forma real de crear un árbol binario se explicará en detalle más adelante.
Antes de ver las operaciones básicas de los árboles binarios, revisemos el concepto de árboles binarios. Los árboles binarios son:
1. Árbol vacío
2. No vacío: el nodo raíz, el subárbol izquierdo del nodo raíz y el subárbol derecho del nodo raíz.

Del concepto se puede ver que la definición de árbol binario es recursiva, por lo que las operaciones básicas de postorden se implementan básicamente de acuerdo con este concepto.


Recorrido de árbol binario

Recorrido de preorden, orden y postorden

La forma más sencilla de aprender la estructura de un árbol binario es recorrerla. El llamado recorrido del árbol binario (Traversal) consiste en realizar las operaciones correspondientes en los nodos del árbol binario en secuencia de acuerdo con ciertas reglas específicas, y cada nodo solo se opera una vez. Las operaciones realizadas al acceder a los nodos dependen del problema específico de la aplicación. El recorrido es una de las operaciones más importantes en un árbol binario y también es la base para otras operaciones en un árbol binario.

De acuerdo con las reglas, el recorrido de un árbol binario incluye: recorrido de estructura recursiva de preorden / orden medio / posorden:
1. Recorrido de preorden (también conocido como recorrido de preorden): la operación de acceder al nodo raíz ocurre atravesando sus subárboles izquierdo y derecho Antes.
2. Recorrido en orden (Inorder Traversal): la operación de acceder al nodo raíz ocurre durante el recorrido de sus subárboles izquierdo y derecho (en el medio).
3. Recorrido posterior al pedido: la operación de acceder al nodo raíz se produce después de atravesar sus subárboles izquierdo y derecho. 

Para hacerlo mas simple:

Recorrido de pedido anticipado: visite primero la raíz, luego visite el subárbol izquierdo y finalmente visite el subárbol derecho.

Recorrido en orden: primero visite el subárbol izquierdo, luego visite la raíz y finalmente visite el subárbol derecho.

Recorrido posterior al pedido: primero visite el subárbol izquierdo, luego visite el subárbol derecho y finalmente visite la raíz.

Dado que el nodo visitado debe ser la raíz de un determinado subárbol, N (nodo), L (subárbol izquierdo) y R (subárbol derecho) pueden interpretarse como la raíz, el subárbol izquierdo de la raíz y el subárbol derecho de la raíz. NLR, LNR y LRN también se denominan primer recorrido de raíz, recorrido de raíz media y último recorrido de raíz, respectivamente.

La N aquí representa NULL.

recorrido de preorden

El recorrido del árbol binario se implementa mediante la recursividad de funciones. Comenzando desde la raíz, se divide en subárboles izquierdo y derecho y se recorre hacia abajo. Si los subárboles izquierdo y derecho no existen, estará vacío (NULL).

Diagrama transversal de pedidos anticipados

 implementar código

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

Primero, determine si el nodo raíz está vacío. Si está vacío, significa un árbol vacío y devuelve NULL; el recorrido de preorden es la raíz visitada primero, por lo que los datos se imprimen directamente y luego ingresan a los subárboles izquierdo y derecho en secuencia . Es difícil explicarlo claramente con palabras aquí, así que vayamos directamente al gráfico de llamadas recursivas de funciones.

recorrido en orden

No te daré un diagrama aquí, puedes intentar dibujarlo tú mismo.

implementar código 

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

Primero determine si el nodo raíz está vacío. Si está vacío, significa un árbol vacío y devuelve NULL; el recorrido en orden es el subárbol izquierdo visitado primero, por lo que se atraviesa hasta que el subárbol izquierdo señalado por la última raíz esté vacío , En este momento se imprime la raíz y los datos ingresan en los subárboles izquierdo y derecho en secuencia.

recorrido posterior al pedido

implementar código

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

Primero, determine si el nodo raíz está vacío. Si está vacío, significa un árbol vacío y devuelve NULL; el recorrido posterior al orden también accede primero al subárbol izquierdo, por lo que atraviesa hasta que el subárbol izquierdo señalado por la última raíz esté vacío e imprime los datos raíz en este momento, imprime los datos del subárbol derecho señalado por su padre y finalmente imprime los datos del padre de los subárboles izquierdo y derecho.


Encuentra el número de nodos

Encuentra el número total de nodos. 

Crear variables para encontrar nodos.

Muchos amigos pensarán en crear una función variable para representar un nodo de forma recursiva y agregarla una vez por cada recursión.

No, porque las variables se crean en el área de la pila cada vez que se llama a la función y se destruyen cuando se sale de la función. Este método simplemente no funciona.

Crear variables estáticas modificadas

El uso de variables puede destruirse, así que crearé una variable estática para cambiar su ciclo de vida, así que eso es todo. Está bien encontrar el número total de nodos en un árbol binario, pero ¿qué pasa si tenemos varios árboles binarios? También se puede resolver: simplemente establezca la variable en cero antes de calcular el número de puntos resumidos cada vez, este es un método factible.

PD: ¡Las variables estáticas solo se crearán una vez en la recursividad de la función! ! !

Divida los subárboles izquierdo y derecho y agregue raíces

La idea más importante de un árbol binario es dividir el árbol binario. Aquí dividimos el árbol binario en subárboles izquierdo y derecho, y usamos la recursividad para encontrar el número de nodos de los subárboles izquierdo y derecho, más el nodo raíz de cada uno. tiempo ¿Es el número total de nodos?

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

Encuentra el número de nodos de hoja.

Caso 1: cuando el árbol está vacío, los nodos de hoja están vacíos;

Caso 2: cuando los subárboles izquierdo y derecho están vacíos, el nodo hoja es 1;

Caso 3: todavía use el método de división para dividir un árbol binario y use una solución 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);
}

Encuentre el número de nodos en la capa Kth

Supongamos que buscamos el tercer nivel, o utilizamos el método de división recursiva del árbol, dividiéndolo en el segundo nivel (k-1) del subárbol izquierdo más el segundo nivel (k-1) del subárbol derecho.

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


Resumen: El recorrido del árbol binario se implementa mediante la recursividad de funciones, que de hecho es difícil de entender. No solo lo hacemos en el nivel de implementación del código, sino que también dibujamos el diagrama de expansión recursiva basado en la recursividad de funciones para que podamos comprender y profundizar nuestra impresión. Se necesitan algunos dibujos más.

Supongo que te gusta

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