Data structure: hand-torn graphical binary tree (including a large number of recursive diagrams)

written in front

Almost all implementations of binary trees rely on recursion. The core idea of ​​recursion is to regard any binary tree as the root and the left and right subtrees, and the core gameplay of binary tree recursion is to regard the left and right subtrees of the binary tree as roots, and then find the left and right subtrees. A tree, then viewed as a root...

Therefore, to solve the binary tree problem, it is actually a process of converting the binary tree into subtrees one by one, finding the subtrees one by one and assembling them to form a binary tree

Creation of binary tree

The orthodox method of building a binary tree is to use recursion, here is a way of writing recursion

BTNode* BuyNode(BTDataType a)
{
    
    
	BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
	newnode->data = a;
	newnode->left = NULL;
	newnode->left = NULL;
	return newnode;
}

BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
    
    
	if (a[*pi] == '#')
	{
    
    
		(*pi)++;
		return NULL;
	}

	BTNode* root = BuyNode(a[*pi]);
	(*pi)++;

	root->left = BinaryTreeCreate(a, pi);
	root->right = BinaryTreeCreate(a, pi);

	return root;
}

If you are not familiar with recursion, I have drawn a recursive expansion diagram below to better explain the principle

insert image description here
(Part of the process of the right subtree is not fully expanded due to space reasons, but if you understand the construction process of the left subtree, it is not difficult to draw the right subtree yourself)

Note here that not every function stack frame is created in a different position. When a stack frame is destroyed, another stack frame will be created in the original position, so it needs to be considered when calculating the space complexity this problem:
Space is reusable, time is gone forever

From this picture, I believe you can understand the meaning of this code

First create a root node, the root node goes to find the left subtree and enters the recursion, and after entering the recursion, continues to search for the left subtree...until it encounters NULL, and then searches for the right subtree when it encounters emptiness, and the right subtree also finds the emptiness Return, so that the smallest tree in the binary tree is constructed, and this tree will return, as the left subtree of the root, and then continue to recursively find the right subtree of the root...

Since it is implemented with the help of a linked list, we can understand that the root is created first, and the left and right subtrees of the root are created with functions and then connected... When # is encountered, it returns, forming a small tree one by one, small trees Finally, a big tree is formed to form a binary tree

Binary tree traversal

Binary tree traversal mainly includes pre-order traversal, in-order traversal, post-order traversal, and layer-order traversal

preorder traversal

Preorder traversal refers to visiting the root first, then visiting the left subtree, and then visiting the right subtree when traversing.

The code is implemented as follows

void BinaryTreePrevOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("N ");
		return;
	}

	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

The following still draws its recursive expansion diagram

insert image description here

Inorder traversal

In-order traversal is to visit the left subtree first, then visit the root, and finally visit the right subtree

void BinaryTreeInOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("N ");
		return;
	}

	BinaryTreeInOrder(root->left);
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

The recursive graph is basically similar to the above, so I won’t draw it

post order traversal

Post-order traversal is to visit the left subtree first, then visit the right subtree, and finally visit the root

void BinaryTreePostOrder(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		printf("N ");
		return;
	}

	BinaryTreePostOrder(root->left);
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

The recursive graph is basically similar to the above, so I won’t draw it

sequence traversal

Layer order traversal refers to the traversal of the binary tree layer by layer, and each layer is traversed separately. Here, the queue is used for traversal. The basic idea is to put the root in the queue. When a certain root is going out of the queue, make the root The left subtree and right subtree of the queue are queued, so that layer-by-layer traversal can be realized, and the drawing method is as follows:

insert image description here
The code implementation is simpler than the previous ones, but it needs to introduce queues. Introduction to queues:

Data structure - hand-tear queue stack and realize each other

The following shows the related function implementation of the queue to be used

// queue.h
typedef struct BinaryTreeNode* QDataType;

typedef struct QNode
{
    
    
	QDataType data;
	struct QNode* next;
}QNode;

typedef struct Queue
{
    
    
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

// queue.c
void QueueInit(Queue* pq)
{
    
    
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq)
{
    
    
	assert(pq);

	QNode* cur = pq->phead;
	while (cur)
	{
    
    
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

QNode* BuyQnode(QDataType x)
{
    
    
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
    
    
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void QueuePush(Queue* pq, QDataType x)
{
    
    
	assert(pq);
	QNode* newnode = BuyQnode(x);
	if (pq->ptail == NULL)
	{
    
    
		assert(pq->phead == NULL);
		pq->phead = pq->ptail = newnode;
	}
	else
	{
    
    
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}

bool QueueEmpty(Queue* pq)
{
    
    
	if (pq->size == 0)
	{
    
    
		return true;
	}
	return false;
}

void QueuePop(Queue* pq)
{
    
    
	assert(pq);
	assert(!QueueEmpty(pq));

	if (pq->phead->next == NULL)
	{
    
    
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else
	{
    
    
		QNode* newhead = pq->phead->next;
		free(pq->phead);
		pq->phead = newhead;
	}

	pq->size--;
}

QDataType QueueFront(Queue* pq)
{
    
    
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

QDataType QueueBack(Queue* pq)
{
    
    
	assert(pq);
	return pq->ptail->data;
}

int QueueSize(Queue* pq)
{
    
    
	assert(pq);
	return pq->size;
}

Then with the help of the queue, we can realize the layer order traversal just now

void BinaryTreeLevelOrder(BTNode* root)
{
    
    
	Queue q;
	QueueInit(&q);
	if (root)
	{
    
    
		QueuePush(&q, root);
	}
	while (!QueueEmpty(&q))
	{
    
    
		BTNode* front = QueueFront(&q);
		printf("%c ", front->data);
		QueuePop(&q);
		if (front->left)
			QueuePush(&q, front->left);
		if(front->right)
			QueuePush(&q, front->right);
	}
	printf("\n");
	//BinaryTreeDestory(&q);
}

Destruction of the binary tree

After knowing the various ways of traversal, the destruction of the binary tree is very simple. Before determining how to implement the code, think about the question: which traversal should be used to destroy the binary tree?

The result is obvious. It is most convenient to choose post-order traversal, because the destruction requires the release of nodes. If pre-order or in-order traversal is used, the root node will be destroyed first during the traversal process, so find the left The subtree or right subtree brings inconvenience (you can define a variable storage location), so it is best to use post-order traversal to destroy all subtrees and finally destroy the root

void BinaryTreeDestory(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return;
	}

	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
}

Number of Binary Tree Nodes

The number of binary tree nodes is also converted into a subtree to solve. If it is NULL, it returns 0. In other cases, it returns the addition of left and right plus the node itself.

int BinaryTreeSize(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return 0;
	}

	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}

The number of leaf nodes in the binary tree

The calculation of the number of leaf nodes is similar to the above method, which is also to find the smallest subtree, but the difference is that the condition for finding a leaf node is that the leaf is the root, and its left and right subtrees are all empty. If this is the case, it is a leaf node, then According to this idea, it is not difficult to obtain the code:

int BinaryTreeLeafSize(BTNode* root)
{
    
    
	if (root == NULL)
	{
    
    
		return 0;
	}

	if (root->left == NULL && root->right == NULL)
	{
    
    
		return 1;
	}

	int leftleave = BinaryTreeSize(root->left);
	int rightleave = BinaryTreeSize(root->right);

	return leftleave + rightleave;
}

Binary tree find node with value x

This is relatively troublesome, first upload the expansion diagram and code

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
    
    
	if (root == NULL)
	{
    
    
		return NULL;
	}

	if (root->data == x)
	{
    
    
		return root;
	}

	BTNode* ret1 = BinaryTreeFind(root->left, x);
	if (ret1)
		return ret1;
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret2)
		return ret2;

	return NULL;
}

insert image description here
The key action here is to judge whether ret1 and ret2 are empty. If so, it means that the correct value is found, and then it will be sent out of the function all the way. On the contrary, it will always return NULL, so there is no need to worry about sending other elements.

Whether the binary tree is a complete binary tree

This idea is also more complicated. First of all, the problem to be solved is what idea to use. The basic idea is to look at it one by one. complete binary tree

So is this idea similar to layer order traversal? We continue to write with this idea

When the queue is not empty, continue to enter the queue and exit the queue for hierarchical traversal. When it encounters empty, it will jump out. After jumping out of the loop, the queue is not empty at this time, and continue to exit the queue. If an element is found to be not empty, then Prove that this is not a complete binary tree

The code is implemented as follows:

int BinaryTreeComplete(BTNode* root)
{
    
    
	assert(root);
	Queue q;
	QueueInit(&q);
	while (!QueueEmpty(&q))
	{
    
    
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
    
    
			break;
		}
		QueuePush(&q,front->left);
		QueuePush(&q,front->right);
	}
	while (!QueueEmpty(&q))
	{
    
    
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
    
    
			BinaryTreeDestory(&q);
			return 0;
		}
	}
	BinaryTreeDestory(&q);
	return 1;
}

So far, the implementation of the basic functions of the binary tree is over. Obviously, the implementation of the binary tree is not easy. It not only requires a certain depth of understanding of recursion, but also requires you to have a sufficient understanding of queues in order to solve the order traversal and judgment complete binary tree

This requires more practice, and also lays the foundation and pave the way for more complex trees later

insert image description here

Guess you like

Origin blog.csdn.net/qq_73899585/article/details/131761341