El lenguaje C implementa un árbol binario (estructura de cadena)


prefacio

Cuando usamos la estructura secuencial para implementar el almacenamiento del árbol binario, el siguiente paso es usar la estructura en cadena para implementar el almacenamiento del árbol binario.

Primero, el recorrido del árbol binario.

La forma más sencilla de aprender la estructura del árbol binario es recorrerla. El llamado recorrido del árbol binario (Traversal) consiste en realizar las operaciones correspondientes en los nodos del árbol binario por turnos de acuerdo con ciertas reglas específicas, y cada nodo solo se opera una vez. Las operaciones realizadas por los nodos de acceso 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.

1. Recorrido de orden de nivel del árbol binario

El recorrido del orden de las capas del árbol binario se utilizó cuando se introdujo anteriormente el almacenamiento de la estructura secuencial del árbol binario, es decir, el orden de almacenamiento del árbol binario completo en la matriz es el orden transversal del orden de las capas del árbol binario.
inserte la descripción de la imagen aquí

2. Recorrido previo al pedido del árbol binario

Recorrido de preorden (Preorder Traversal, también conocido como recorrido de preorden): la operación de visitar el nodo raíz ocurre antes de atravesar sus subárboles izquierdo y derecho. El signo # indica la ubicación de un árbol vacío.
inserte la descripción de la imagen aquí

3. Recorrido en orden de un árbol binario

Inorder Traversal (Inorder Traversal): la operación de visitar el nodo raíz se produce mientras se atraviesan sus subárboles izquierdo y derecho (en el medio). El signo # indica la ubicación de un árbol vacío.

inserte la descripción de la imagen aquí

4. Recorrido posterior al orden del árbol binario

Recorrido posterior al pedido : la operación de visitar el nodo raíz se produce después de atravesar sus subárboles izquierdo y derecho. El signo # indica la ubicación de un árbol vacío.
inserte la descripción de la imagen aquí

5. Implementación del código

Después de conocer las reglas del recorrido previo, medio y posterior de un árbol binario, podemos implementar un árbol binario y luego implementar estos métodos transversales.
Definición de árbol binario

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

Crear un nodo de árbol binario

//创建一个新的结点,并且将该结点返回
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;
}

crear un árbol binario

//创建一棵二叉树,并且返回该二叉树的根结点
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;
}

Implementar recorrido de pedido anticipado

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

Es decir, la secuencia de llamadas recursivas a funciones se muestra en la figura.
inserte la descripción de la imagen aquí

Implementar recorrido en orden

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

Implementar recorrido posterior al pedido

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

En segundo lugar, la realización de algunas operaciones del árbol binario.

Cuando hayamos implementado el recorrido del árbol binario, podemos implementar algunas otras operaciones del árbol binario.

1. Encuentre la cantidad de nodos en el árbol binario.

Cuando solicitamos nodos de árbol binario, el primer método que pensamos es definir un recuento de variables globales y luego visitar los nodos del árbol binario a su vez. Si el nodo no está vacío, entonces cuenta ++ y el valor final del recuento es el nodo en el número del árbol binario. Cabe señalar que cuando llamamos a este método por segunda vez, debido a que el recuento es una variable global, ya no es 0 en este momento, por lo que el valor del recuento debe restablecerse a 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);
}

No es seguro utilizar variables globales para obtener el número de nodos del árbol en el método anterior, y las variables globales se pueden modificar y, cada vez que se llama a la función, es necesario restablecer las variables globales. Entonces podemos usar el segundo método para encontrar el número de nodos del árbol binario.

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

Gráficos de recursividad y retroceso para funciones.
inserte la descripción de la imagen aquí

2. Encuentre la cantidad de nodos de hoja en el árbol binario.

//二叉树叶子结点个数
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. Encuentre el número de nodos en la k-ésima capa del árbol binario.

//二叉树第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. Encuentra la profundidad del árbol binario.

//求二叉树深度
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. Encuentra el nodo cuyo valor es x en el árbol binario.

//二叉树查找值为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. Destrucción del árbol binario.

La destrucción del árbol binario es para liberar todo el espacio solicitado por cada nodo del árbol binario por turno, y es necesario atravesar el árbol binario una vez, aquí necesitamos usar el recorrido posterior al orden, es decir, primero liberar los nodos del subárbol izquierdo del nodo raíz, y luego libera el nodo raíz El nodo del subárbol derecho del nodo, y finalmente libera el espacio del nodo raíz. Porque si el espacio del nodo raíz se libera primero mediante el recorrido de preorden, no se encontrarán los subárboles izquierdo y derecho del nodo raíz.

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

3. Implementación del recorrido de orden a nivel de árbol binario

1. Recorrido de secuencia de capas

La realización del recorrido de orden de nivel del árbol binario necesita la ayuda de la cola. Por ejemplo, existe el siguiente árbol binario. Primero coloque el nodo raíz del árbol binario en la cola.
inserte la descripción de la imagen aquí
Luego, el encabezado de la cola se retira de la cola y los hijos izquierdo y derecho del nodo principal se colocan en la cola.
inserte la descripción de la imagen aquí
Luego repita la operación anterior. Coloque los nodos restantes en la cola uno por uno.
inserte la descripción de la imagen aquí
hasta que la cola esté vacía. El orden en que se retiran los nodos de la cola es el orden en que se recorre el árbol binario.
inserte la descripción de la imagen aquí

2. Implementación del código transversal de secuencia de capas

El recorrido jerárquico requiere una cola auxiliar, primero poner en cola el nodo raíz del árbol binario, luego sacar de la cola el nodo raíz, poner en cola los hijos izquierdo y derecho del nodo raíz, luego sacar de la cola los hijos izquierdo y derecho, y poner en cola los hijos izquierdo y derecho de los hijos izquierdo y derecho. El bucle continúa hasta que la cola está vacía.

//二叉树的层序遍历
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. Aplicación del recorrido del orden de las capas: juzgar si un árbol binario es un árbol binario completo

Cuando hay un árbol binario que necesita juzgar si es un árbol binario completo, en este momento se puede tomar prestado el recorrido de orden de nivel del árbol binario.
Por ejemplo, hay un árbol binario completo de la siguiente manera :
inserte la descripción de la imagen aquí
El árbol binario se recorre en orden de niveles: primero, el nodo raíz ingresa a la cola, luego el nodo principal sale de la cola y los hijos izquierdo y derecho del nodo se almacenan en el nodo principal ingresa a la cola. Si los niños de la izquierda y de la derecha están vacíos, también se unirán al equipo.
inserte la descripción de la imagen aquí
Cuando los datos almacenados en el nodo principal de la cola sean NULL, detenga la operación anterior. En este momento, los datos almacenados en los nodos de la cola son todos NULL, por lo que se puede concluir que el árbol binario es un árbol binario completo.
inserte la descripción de la imagen aquí

Por ejemplo, hay un árbol binario incompleto de la siguiente manera :
inserte la descripción de la imagen aquí
El árbol binario sigue los pasos del recorrido del orden de las capas: primero, el nodo raíz ingresa a la cola, luego el nodo principal de la cola sale de la cola y los hijos izquierdo y derecho del nodo almacenado en el nodo principal de la cola ingresa a la cola. Si los niños de la izquierda y de la derecha están vacíos, también se unirán al equipo.
inserte la descripción de la imagen aquí
Cuando los datos almacenados en el nodo principal de la cola sean NULL, detenga la operación anterior. En este momento, algunos de los datos almacenados en los nodos de la cola son NULL y otros no son NULL, por lo que se puede concluir que el árbol binario no es un árbol binario completo.
inserte la descripción de la imagen aquí
Según las condiciones anteriores, podemos modificar cierta lógica del recorrido del orden de niveles para que pueda juzgar si el árbol binario es un árbol binario completo.

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

Supongo que te gusta

Origin blog.csdn.net/dong132697/article/details/132562478
Recomendado
Clasificación