Binary tree (implementation of interface function)

What we are going to share today is the binary tree. Without further ado, let’s just look at the following interface functions. Then we implement them and we will master half of the binary tree. (Today we have more than a thousand fans, which is really a bit happy. ).

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode* root);
// 二叉树节点个数
int BinaryTreeSize(BTNode* root);
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root);
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k);
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);
// 层序遍历
void BinaryTreeLevelOrder(BTNode* root);
// 判断二叉树是否是完全二叉树
int BinaryTreeComplete(BTNode* root);

Let’s take a look at the creation of the first interface function. It is a somewhat difficult interface. If we want to implement this number, the characters would be"ABD##E# H##CF##G##"  Then if we want to create such a tree, we must first know whether to use pre-order traversal or in-order traversal. In fact, the three traversal methods are It can be achieved. It is easier to understand using pre-order traversal here. When we write this interface function, we have to create the node ourselves, so there is a CreateNode function that must be written. Secondly, we will observe that this function has a return value. According to The method of preorder traversal is to write the root node, left child, and right child, so our code is as follows.

BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = CreateNode(a[(*pi)++]);
	
	root->left = BinaryTreeCreate(a, n, pi);
	root->right = BinaryTreeCreate(a, n, pi);
	return root;
}

One thing to note here is that pi is passing the address, and pi requires us to dereference it. Our pi represents the subscript of the array. Then we will consider why the pointer is passed. First, it is our function stack. Frame opening and local variables are destroyed as the function stack frame is destroyed. Secondly, and most importantly, the formal parameter is a temporary copy of the actual parameter. Every time i is changed, it will only have an effect in this function stack frame.

void BinaryTreeDestory(BTNode* root)

When we create a node, we will definitely destroy the node. When we destroy the node, we can use the idea of ​​​​post-order traversal. It is actually very simple. When it encounters an empty space, we can return and we will directly give the code. However, our code will have some problems, but You can add some code to solve it.

void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	BinaryTreeDestory(root->left);
	BinaryTreeDestory(root->right);
	free(root);
	root = NULL;
}

The problem here is that the formal parameter is a temporary copy of the actual parameter, but the reason why our root can still be released is that although root is a formal parameter, it still points to this space, so what we need to pay attention to is when calling this function Just add this sentence at the end.

Of course, we can also pass the second level solution, but I don't think it is necessary.

int BinaryTreeSize(BTNode* root)

The next interface function is to count how many nodes there are in this tree. When it reaches NULL, it starts to return. The sub-problem here is how to count the number of nodes in our left subtree and right subtree. If it is not empty, it means there is. Nodes, we only need to return the number of nodes, because the sub-problem is the number of left subtrees and right subtrees, so we can write the following code here.

int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

The next interface function is to count the number of our leaf nodes. We divide the statistics of leaf nodes into sub-problems, which is to count the left and right children of this node. If the left and right children of this node are empty, then this is the leaf node, and then we carry out the left subtree and right subtree. The recursion and addition can solve the problem.

code show as below.

int BinaryTreeLeafSize(BTNode* root)
{

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

The next interface function is implemented to count the number of nodes in our K layer.

int BinaryTreeLevelKSize(BTNode* root, int k)

First of all, we have to know what our tree looks like. The picture below is a simplified version of the tree of this question. We need to count the number of nodes in layer k.

The first thing is that we divide it into sub-problems, that is, we only start counting when we reach the K level, and return when it encounters null. In fact, our code can be written directly. We recurse the left subtree and the right subtree and then add them. We can complete our code.

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

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

}

 What we need to pay attention to is that only when k == 1 is the time when we want to count, then we have to let k-- and be able to recurse to the next level, so that our code can be completed.

BTNode* BinaryTreeFind(BTNode* root, BTDataType x);

This is to find the node whose content is , but we have to search recursively in the left and right subtrees. We don’t know when we find it and what it returns. We can save this node so that we can judge again to know which one we need.

BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	if (root->data == x)
	{
		return root;
	}
	BTNode* ret1 = BinaryTreeFind(root->left, x);
	BTNode* ret2 = BinaryTreeFind(root->right, x);
	if (ret1)
	{
		return ret1;
	}
	if (ret2)
	{
		return ret2;
	}
	return NULL;
}

The next three interface functions are much simpler in comparison. Let's take a look. I will only write one here. The other two are really too simple, so I won't write them.

oid BinaryTreePrevOrder(BTNode* root);
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root);
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root);

We are going to implement pre-order, mid-order and post-order traversal. We start directly and return and print the content when it is empty.

void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("#");
		return;
	}
	printf("%c", root->data);
	BinaryTreePrevOrder(root->left);
	BinaryTreePrevOrder(root->right);
}

The following two interface functions are the most important. They use the first-in-first-out feature of our queue. Because there is no queue in C language, we need to rub one by hand. Fortunately, I was prepared. Come out and queue! ! ! !

#include"Queue.h"
 

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(Queue* pq, QueueDateType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	newnode->next = NULL;
	newnode->val = x;
 

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;

}


void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	QNode* tmp = pq->head;
	pq->head = pq->head->next;
	free(tmp);
	tmp = NULL;

	if(pq->head == NULL)
		pq->tail = NULL;
	pq->size--;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;

}

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

QueueDateType QueueFront(Queue* pq)
{
	assert(pq);

	
	
	return pq->head->val;
	
	
}

QueueDateType QueueBack(Queue* pq)
{
	assert(pq);
	return pq->tail->val;
}


void QueueDestory(Queue* pq)
{
	assert(pq);
	while (pq->head != pq->tail)
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;
		free(del);
	}
	free(pq->tail);
}

This queue can be used with confidence, so let’s take a look at the upper level of sequential traversal.

void BinaryTreeLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if(root)
		QueuePush(&q, root);
	int leafsize = 1;
	while (!QueueEmpty(&q))
	{
		while (leafsize--)
		{
			BTNode* front = QueueFront(&q);

			QueuePop(&q);
			printf("%c ", front->data);
			if (front->left)
				QueuePush(&q, front->left);

			if (front->right)
				QueuePush(&q, front->right);
		}
		printf("\n");
		leafsize = QueueSize(&q);
	}

}

The key to layer-order traversal is whether the queue is empty and how much data there is in the queue, so what we must do here is to have a leafsize for statistics. Without this, we cannot implement the recursive idea, and the most important thing is One of them is the empty judgment of the queue, which is also particularly important.

Then the idea of ​​our layer-order traversal is that we put the root node in first, and when the root node is out, we also insert the child nodes into the queue. We need a number to count how many data there are in this layer, so at this time we have leafsize. Just update leafsize and child nodes all the time.

Then let's take a look at how we judge the idea of ​​a full binary tree.

bool TreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);

	int levelSize = 1;
	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)
		{
			QueueDestroy(&q);
			return false;
		}
	}

	QueueDestroy(&q);
	return true;
}

The idea is:After encountering an empty space in the front, if there is non-empty space in the back, it is not a complete binary tree

In fact, if we insert the root node for the first time, and then bring the child in every time, the child may be empty. There are two cases of empty. One is that it is empty at the end, and the other is that the binary tree is not full at the last level. If it is later There are also nodes, and the queue is not empty. At this time, we can get our results after making a judgment.

That’s it for today’s binary tree. See you next time.

Guess you like

Origin blog.csdn.net/2301_76895050/article/details/134960417