Data structure: chain structure of binary tree

Friends and guys, we meet again. In this issue, I will explain to you the relevant knowledge points of chained binary trees. If you have some inspiration after reading it, please leave your three links. I wish you all the best thoughts It's done!

Data Structure and Algorithm Column : Data Structure and Algorithm

Personal homepage : stackY,

C language column : C language: from entry to master

Table of contents

Foreword:

Pre-instructions:

1. Binary tree traversal

Code:

Recursive expansion diagram: 

1.2 The number of nodes in the binary tree

1.3 The number of leaf nodes of the binary tree

1.4 Height (depth) of binary tree

1.5 The number of nodes in the kth layer

1.6 Find the node with the value x in the binary tree

2. Level order traversal of binary tree

3. Creation and destruction of binary tree

3.1 Creation of binary tree

3.2 Destruction of binary tree 

4. Determine whether the binary tree is a complete binary tree

5. Chained binary tree all test codes  


Foreword:

Chain storage:

The linked storage structure of the binary tree means that a linked list is used to represent a binary tree, that is, a link is used to indicate the logical relationship of elements. The usual method is that each node in the linked list is composed of three fields, the data field and the left and right pointer fields , and the left and right pointers are used to give the storage addresses of the link points where the left child and right child of the node are located. The chain structure is further divided into two-fork chain and three-fork chain, and the difficulty is gradually increasing. We will learn from the simplest one first.

Pre-instructions:

Before learning the basic operations of a binary tree, you need to create a binary tree before you can learn its related basic operations. So let's first manually create the simplest binary tree, and then learn some operations about binary trees. Later, we will also take you to create a real binary tree together.
A binary tree will have an initial root and two pointers to the nodes of the left and right children respectively, so it is necessary to define a structure to quickly and conveniently get started with the binary tree:
We still divide the module to write the binary tree, create a header file Tree.h , two source files Tree.c , Test.c
Header file: Tree.h
typedef int TreeDataType;

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

//快速创建一个二叉树
BTNode* CreatBinaryTree();

Source file: Tree.c

//快速创建一个二叉树
BTNode* BuyBTNode(TreeDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}

BTNode* CreatBinaryTree()
{
	//创建节点
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	BTNode* node7 = BuyBTNode(7);

	//链接成树
	node1->left = node2;
	node1->right = node3;
	node2->left = node4;
	node2->right = node5;
	node3->left = node6;
	node3->right = node7;

	return node1;
}

Note: The above code is not the way to create a binary tree . The real way to create a binary tree will be explained in detail later.
Before looking at the basic operations of the binary tree, review the concept of the binary tree. The binary tree is:
1. Empty tree
2. Non-empty: the root node, the left subtree of the root node, and the right subtree of the root node.
It can be seen from the concept that the definition of a binary tree is recursive , so the basic operations in the subsequent order are basically implemented according to this concept.

1. Binary tree traversal

There are several ways to traverse a binary tree:

1. Preorder : visit the root node first, then visit the left subtree, and finally visit the right subtree.

2. Inorder : First visit the left subtree, then visit the root node, and finally visit the right subtree.

3. Postorder : first visit the left subtree, then visit the right subtree, and finally visit the root node.

4. Sequence : access layer by layer. (more on that later)

So what will be obtained by using the pre-, middle-, and post-order traversal access of the binary tree we created? Let's draw a picture together:

1. Preamble:

First visit the root of 1, then visit the root 2 of the left subtree of 1, then visit the root of the left subtree of 2, and then visit the root of the left subtree of 4 is empty, so it is necessary to visit the root of the right subtree of 4 The root is still 4 is empty, so it is necessary to go up one level. The root of the right subtree of 2 is 5, and then visit the left subtree of 5 is empty, and then visit the right subtree of 5 is still empty, then go again Go up one level, at this time, all the left branches of 1 have been visited, so it is necessary to visit the right subtree 3, and so on.....until all visits are completed

So the final traversal result is: 1 2 4 NN 5 NN 3 6 NN 7 NN

2. In sequence:

In-order traversal visits the left subtree first, so you have to go all the way down to visit the left subtree of 1, and then find its left subtree in the left subtree of 1, and so on until you find the empty tree. , what is found at this time is that the left subtree of 4 is empty. At this time, it is necessary to visit 4 first, and then the right subtree of 4 is also empty. At this time, go up one level, visit 2, and then visit 2 The right subtree, and then visit its left subtree in the right subtree of 2. At this time, the left subtree of 5 is empty, so it is necessary to visit 5, and then visit the right subtree of 5. At this time, it is necessary to go up one level. At this time, it is necessary to visit 1, and then continue to repeat the previous action in the right subtree of 1. …

So the final traversal result is: N 4 N 2 N 5 N 1 N 6 N 3 N 7 N

3. Postorder:

Post-order traversal is to visit the left subtree first, then visit the right subtree, and finally visit the root first visit the left subtree of 1, then visit the left subtree of the left subtree of 1, and so on, until the visit is empty, this It will return to the upper layer, then visit the right subtree, then visit the root, and so on, so when visiting the left subtree of 4, it is empty, and then visiting the right subtree of 4 is also empty, then it will visit 4, each node is the same...

So the final traversal result is: NN 4 NN 5 2 NN 6 NN 7 3 1 

Code:

According to the rules, the traversal of the binary tree includes: preorder/inorder/postorder recursive structure traversal :
1. Preorder traversal (Preorder Traversal, also known as preorder traversal) - the operation of visiting the root node occurs before traversing its left and right subtrees.
2. Inorder Traversal (Inorder Traversal) - the operation of visiting the root node occurs in traversing its left and right subtrees (between).
3. Postorder Traversal——The operation of visiting the root node occurs after traversing its left and right subtrees.

Header file: Tree.h

//前序遍历
void PrevOrder(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历
void PostOrder(BTNode* root);

Source file: Tree.c

//前序遍历
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	//先访问根节点
	printf("%d ", root->data);
	//再访问左子树
	PrevOrder(root->left);
	//最后访问右子树
	PrevOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	//先访问左子树
	InOrder(root->left);
	//再访问根
	printf("%d ", root->data);
	//在访问右子树
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	//先访问左子树
	PostOrder(root->left);
	//再访问右子树
	PostOrder(root->right); 
	//最后访问根
	printf("%d ", root->data);
}

Source file: Test.c

#include "Tree.h"

int main()
{
	BTNode* root = CreatBinaryTree();
	//前序遍历
	PrevOrder(root);
	printf("\n");

	//中序遍历
	InOrder(root);
	printf("\n");

	//后序遍历
	PostOrder(root);
	printf("\n");

	return 0;
}

Recursive expansion diagram: 

prologue:

In sequence:

Subsequent:

The editor will not draw the recursive expansion diagram of the post-sequence here. The logic of the method is exactly the same as that of the pre-sequence and the in-sequence, but the order of access is different. You can draw it yourself for practice.

1.2 The number of nodes in the binary tree

There are two ways to find the number of nodes in a binary tree:

1. Traverse usage counter statistics. (Not recommended)

2. Use recursion to calculate directly.

Why is the first method not recommended?

Because if you use a counter, you need to define the counter as a global variable, and you need to manually reset it after using it once, which is troublesome.

Here we use the second method, using recursion to perform statistics directly:

One thing to pay attention to when using recursion to directly count. If the left subtree and right subtree of a root node are both empty, the return value is not 0, but 1, because the left and right subtrees are empty, but the root node is not Empty, so you need to return the sum of the root node itself and its left and right subtree nodes.

Code:

Header file: Tree.h

//二叉树的节点个数
int BTreeSize(BTNode* root);

Source file: Tree.c

//二叉树的节点个数
int BTreeSize(BTNode* root)
{
	//为空就返回0
	if (root == NULL)
	{
		return 0;
	}

	//不为空就返回根节点和它的左右子树节点的和
	return BTreeSize(root->left)
		+ BTreeSize(root->right)
		+ 1;
}

Recursive expansion diagram:

1.3 The number of leaf nodes of the binary tree

The leaf node of a binary tree is called a leaf node when its left and right subtrees are empty, so we can judge whether it is a leaf node based on the fact that the left and right subtrees of a root node are empty, and the recursive method is still used, but if its The root node has only one of the left and right subtrees, so it is not a leaf node.

Code demo:

Header file: Tree.h

//求叶子节点的个数
int BTreeLeafSize(BTNode* root);

Source file: Tree.c

//求叶子节点的个数
int BTreeLeafSize(BTNode* root)
{
	//左右子树只存在一个或者为空树
	if (root == NULL)
	{
		return 0;
	}
	//左右子树都不存在
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//递归调用下面的子树
	return BTreeLeafSize(root->left) 
		+ BTreeLeafSize(root->right);
}

Recursive expansion diagram:

1.4 Height (depth) of binary tree

To ask for the height of the binary tree, we also use the method of recursive traversal: find the height of the left and right subtrees, and then compare which tree has the greater height and return which tree.

If the height of 1 is required, then the heights of its left and right subtrees 2 and 3 must be calculated, and the heights of their left and right subtrees must be calculated separately if the height of 2 and 3 is required... and so on, the height of the left and right subtrees of 4 is 0 , when 4 returns to 2, the returned height is 0+1 (0 means that the height of its left and right subtrees is 0, and 1 means that its own height is 1), that is to say, when returning, add its own Height, so the height of the left subtree of 2 is 1, then calculate the height of the right subtree 5 of 2, calculate the height of 5 and the height of the left and right subtrees of 5, the height of the left subtree of 5 is 1, and the height of the right subtree is 1. The height of the tree is 0, take the larger one as 1, and when 5 returns to 2, add its own height to 2, so the height of the left and right subtrees of 2 is 2, so when 2 returns to 1, add its own height The height is 3.....and so on until the entire binary tree is traversed. So the final height of the whole tree is 4.


Code demo:

Header file: Tree.h

//二叉树的高度
int BTreeHight(BTNode* root);

Source file: Tree.c

//二叉树的高度
int BTreeHight(BTNode* root)
{
	//是否为空树
	if (root == NULL)
	{
		return 0;
	}
	//保存左右子树的高度
	BTNode* LeftTreeHight = BTreeHight(root->left);
	BTNode* RightTreeHight = BTreeHight(root->right);

	return LeftTreeHight > RightTreeHight ? LeftTreeHight + 1 : RightTreeHight + 1; //加上自己本身的高度1
}

Note: There is another way of writing here: do not save the height of the left and right subtrees, directly perform trinocular operation and then recurse, which is also possible, but when there are many nodes in the tree, it will cause a lot of recursion, which will lead to great efficiency reduce.

Code demo:

//不推荐的写法
//二叉树的高度
int BTreeHight(BTNode* root)
{
	//是否为空树
	if (root == NULL)
	{
		return 0;
	}

	//直接递归
	return BTreeHight(root->left) > BTreeHight(root->right) ? 
		BTreeHight(root->left) + 1 : BTreeHight(root->right) + 1; //加上自己本身的高度1
}

1.5 The number of nodes in the kth layer

The number of nodes in the k-th layer is required. Finding the k-th layer is a key problem here, so we need to convert it into multiple sub-problems: if the k-th layer is required, then we might as well ask for the k of its left and right subtrees The number of nodes in -1 layer, then its end mark is: when k==1 and the node is not empty, it is considered a node. There is also a special case, an empty tree means that there is no node. In addition, k needs to be detected, and k is greater than 0.

We can turn this tree upside down and regard the original first layer as the third layer, then we need to find the number of nodes in the second layer of its left and right subtrees, and then it can be converted into its left and right subtrees The first layer, the number of nodes obtained at this time is the number of nodes in the third layer is 3.

Code demo:

 Header file: Tree.h

//第k层节点的个数
int  BTreeLeafKSize(BTNode* root, int k);

Source file: Tree.c

//第k层节点的个数
int  BTreeLeafKSize(BTNode* root, int k)
{
	//判断k的合理性
	assert(k > 0);
	//树为空
	if (root == NULL)
	{
		return 0;
	}
	//树不为空,且k==1
	if (k == 1 )
	{
		//算一个有效结点
		return 1;
	}
	//树既不为空,k也不为1,继续向下走
	return BTreeLeafKSize(root->left, k - 1) + 
		BTreeLeafKSize(root->right, k - 1);
}

 Recursive expansion diagram:

  

1.6 Find the node with the value x in the binary tree

Finding the position of a node can also be transformed into a sub-problem, to find a node whose value is x in its left and right subtrees, which is a preorder traversal process, first find the root, and then find the left and right subtrees, if the node If the data in the point is x, it will return the address of this node, and if it cannot find it, it will return NULL.

Preorder traversal:
first find the root, then find the left and right subtrees.
1 is not 6, then go to the left and right subtrees of 1 to find, the left subtree 2 is not 6, and then go to the left subtree of 2 to find, 4 is not 6, then go to the left and right subtrees of 4 to find that there is no left or right Subtree, go back to the upper level, go to the right tree of 2 to find, 5 is not 6, then go to the left and right subtrees of 5, and there are no left and right subtrees, then return to the upper level, go to the right child of 1 Tree search, 3 is not 6, then go to the left subtree of 3 to find, and find that 6 is exactly equal to 6, then return the address of the node of 6.

Code demo:

Header file: Tree.h

//查找值为x的结点
BTNode* BTreeFind(BTNode* root, TreeDataType x);

Source file: Tree.c

//查找值为x的结点
BTNode* BTreeFind(BTNode* root, TreeDataType x)
{
	//判断是否为空树
	if (root == NULL)
	{
		return NULL;
	}
	//找到了就返回地址
	if (root->data == x)
	{
		return root;
	}
	//先去左树找,找到了就返回
	BTNode* left = BTreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	//再去右树找,找到了就返回
	BTNode* right = BTreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	//如果左右树都没有找到就返回NULL
	return NULL;
}

Recursive expansion diagram:

2. Level order traversal of binary tree

Level-order traversal : In addition to pre-order traversal, in-order traversal, and post-order traversal, level-order traversal of binary trees can also be performed. Assuming that the root node of the binary tree is at 1 layer, the layer order traversal is to start from the root node of the binary tree where it is located, first visit the root node of the first layer, then visit the nodes on the second layer from left to right, and then the third The nodes of the layer, and so on, the process of visiting the nodes of the tree layer by layer from top to bottom and from left to right is layer order traversal.
The tool we need to use for layer order traversal is the queue we wrote before, so the queue has the characteristics of first-in-first-out, so we can first check the first layer of the binary tree into the queue, and then dequeue the first layer, and then put the first The left and right subtrees of the layer are then inserted into the queue. The simple understanding is that in this queue, if a root node goes out, its two left and right subtrees will be brought in. In this way, the layer order traversal is realized.

Code demo:

Header file: Queue.h

#pragma once

//       队列

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//链式结构:表示队列
typedef struct BinaryTreeNode* QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//队列的结构
typedef struct Queue
{
	//头指针
	QNode* phead;
	//尾指针
	QNode* ptail;
	//队列中有效元素个数
	int size;
}Queue;


//初始化队列
void QueueInit(Queue* pq);

//销毁队列
void QueueDestroy(Queue* pq);

//队尾入队列
void QueuePush(Queue* pq, QDataType x);

//检测队列是否为空
bool QueueEmpty(Queue* pq);

//对头出队列
void QueuePop(Queue* pq);

//获取队头的元素
QDataType QueueFront(Queue* pq);

//获取队尾的元素
QDataType QueueBack(Queue* pq);

//获取队列有效元素的个数
int QueueSize(Queue* pq);

Source file: Queue.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Queue.h"

//初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);

	pq->phead = NULL;
	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;
}

//队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	
	//创建新的结点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->next = NULL;
	newnode->data = 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)
{
	assert(pq);

	//1.判断头、尾指针
	/*
	return pq->phead == NULL
		&& pq->ptail == NULL;
	*/

	//2.判断有效元素个数
	return pq->size == 0;
}

//队头出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	//判空
	assert(!QueueEmpty(pq));

	//一个结点
	if (pq->phead->next == NULL)
	{
		//直接释放
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	//多个结点
	else
	{
		//记录头的下一个
		QNode* Next = pq->phead->next;
		//释放
		free(pq->phead);
		//更新头结点
		pq->phead = Next;
	}
	
	pq->size--;
}

//获取队头的元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	//先判空
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

//获取队尾的元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	//先判空
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}

//获取队列有效元素的个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}


Header file: Tree.h

//层序遍历
void LevelOrder(BTNode* root);

Source file: Tree.c

#include "Queue.h"
#include "Tree.h"

//层序遍历
void LevelOrder(BTNode* root)
{
	//创建队列
	Queue q;
	QueueInit(&q);

	//先将第一层的插入队列
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		//保存队头的数据
		BTNode* front = QueueFront(&q);
		//删除队头
		QueuePop(&q);
		printf("%d ", front->data);

		//将第二层的数据带入队列
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);

	}
	printf("\n");
	QueueDestroy(&q);
}

3. Creation and destruction of binary tree

The binary trees we used before are all created quickly, which is not the real way to create a binary tree, so let's learn how to really create a binary tree.

3.1 Creation of binary tree

Regarding the creation of a binary tree, let's first look at an OJ question:

Creation of binary tree : https://www.nowcoder.com/practice/4b91205483694f449f94c179883c1fef?tpId=60&&tqId=29483&rp=1&ru=/activity/oj&qru=/ta/tsing-kaoyan/question-ranking

The meaning of this question description is to create a binary tree by traversing the array in preorder , and then use the inorder traversal of the binary tree to print. We can analyze the effect of creating a binary tree by traversing the array in preorder. Here we use the test Use an example to demonstrate: ABC##DE#G##F### 

The preorder traversal of a binary tree is to visit the root first, then visit the left subtree, and then visit the right subtree, then as long as the left and right subtrees of a tree are not empty, then it can continue to be decomposed into left subtree and right subtree tree , then ABC##DE#G##F### is like this through preorder traversal:

 

Problem-solving ideas:

Traversing the array, if it is '#', then there is no need to create a node, just continue to access the next element of the array, if it is not '#', you can create a node, after the creation is complete, continue to access the next element of the array Yes, after creating a node, you need to continue to create its left subtree, and then create its right subtree after the left subtree is created. At this time, you need to recursively complete the creation of the left and right subtree nodes of a node, and then use the inorder Traversing (left subtree, root, right subtree) is done.

Code demo:

#include <stdio.h>
#include <stdlib.h>

typedef int TreeDataType;

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

//节点的创建
BTNode* BuyBTNode(TreeDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}

//创建二叉树
BTNode* CreatBinaryTree(char* a, int* pi)
{
    //判断是否为'#'
    if(a[*pi] == '#')
    {
        (*pi)++;
        return NULL;
    }
    //不为'#'就创建
    BTNode* root = BuyBTNode(a[*pi]);
    //继续访问下一个元素
    (*pi)++;

    //递归创建它的左子树
    root->left = CreatBinaryTree(a,pi);
    //递归创建它的右子树
    root->right = CreatBinaryTree(a,pi);
    return root;
}

//前序遍历
void InOrder(BTNode* root)
{
    if(root == NULL)
    {
        return;
    }
    InOrder(root->left);
    printf("%c ",root->data);
    InOrder(root->right);
}

int main() {
    char a[100];
    scanf("%s",a);
    int i = 0;
    //传递下标
    BTNode* root = CreatBinaryTree(a, &i);
    InOrder(root);
    return 0;
}

Then the normal binary tree creation process is similar to this OJ question, using preorder traversal to create a binary tree, so many people here will have a question: Why pass the subscript?

If you do not pass the subscript and set the subscript directly in the function, then each function call subscript will be an initial value , because each stack frame is independent when function recursion creates a function stack frame, so each stack frame There is a subscript in the function, then we operate this subscript in a stack frame, and the subscripts in other stack frames will not be affected, then we set a subscript outside the function, and pass the address of this subscript to function, then each function call will go to this address to find the subscript , and we can change the subscript by dereferencing, so that multiple stack frames can use one subscript.

Create a binary tree:

Header file: Tree.h

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* CreatBinaryTree(char* a, int* pi);

Source file: Tree.c

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//#代表空树
//创建二叉树
BTNode* CreatBinaryTree(char* a, int* pi)
{
	//判断是否为'#'
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	//不为'#'就创建
	BTNode* root = BuyBTNode(a[*pi]);
	//继续访问下一个元素
	(*pi)++;

	//递归创建它的左子树
	root->left = CreatBinaryTree(a, pi);
	//递归创建它的右子树
	root->right = CreatBinaryTree(a, pi);
	return root;
}

3.2 Destruction of binary tree 

The most suitable way to destroy a binary tree is post-order traversal to destroy the binary tree. Post-order traversal is: first visit the left subtree, then visit the right subtree, and finally visit the root, so that the entire binary tree can be destroyed very well.

Code demo:

Header file: Tree.h

//销毁二叉树
void BTDestroy(BTNode* root);

Source file: Tree.c

//销毁二叉树
void BTDestroy(BTNode* root)
{
	//后序遍历销毁
	//为空就返回
	if (root == NULL)
		return;

	//先走左树再走右树
	BTDestroy(root->left);
	BTDestroy(root->right);

	//释放
	free(root);
}

4. Determine whether the binary tree is a complete binary tree

The characteristics of a complete binary tree: Assuming that the height of the tree is h, then the first h-1 layers must be full, and the last layer can be full, but must be continuous. Then I think that many veterans here think of using the number of nodes in the binary tree to judge. This kind of thinking is possible, but what? If the last layer is discontinuous, it will be a pit, so we need to start with its continuity . A complete binary tree is a continuous structure , and a complete binary tree is a continuous structure using layer order traversal, so judge A complete binary tree requires level-order traversal:

Use layer order to traverse the binary tree. If the data at the head of the queue is found to be empty, then a judgment must be made. If the subsequent data are all empty, then it proves that the binary tree is a complete binary tree. If it is not empty, then it is not a complete binary tree. , we can verify it by drawing:

 Then we can start by traversing the layers first. When dequeuing, it will jump out first when it encounters an empty space, and then judge the remaining values ​​in the queue. If there are still non-empty values ​​after traversing the entire queue, it will be expressed as Incomplete binary tree, if the entire queue is empty after traversing, then the proof is a complete binary tree.

Code demo:

Header file: Tree.h

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root);

Source file: Tree.c

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	//创建队列进行层序遍历
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);

	//层序遍历
	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;
}

5. Chained binary tree all test codes  

The code of the queue is not shown here, and those who need it can go to the article to get the  data structure by themselves: stack and queue

Header file: Tree.h

#pragma once


//        二叉树 

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>


typedef int TreeDataType;

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

//快速创建一个二叉树
BTNode* BinaryTreeCreat();

//前序遍历
void PrevOrder(BTNode* root);

//中序遍历
void InOrder(BTNode* root);

//后序遍历
void PostOrder(BTNode* root);

//二叉树的节点个数
int BTreeSize(BTNode* root);

//求叶子节点的个数
int BTreeLeafSize(BTNode* root);

//二叉树的高度
int BTreeHight(BTNode* root);

//第k层节点的个数
int  BTreeLeafKSize(BTNode* root, int k);

//查找值为x的结点
BTNode* BTreeFind(BTNode* root, TreeDataType x);

//层序遍历
void LevelOrder(BTNode* root);

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* CreatBinaryTree(char* a, int* pi);

//销毁二叉树
void BTDestroy(BTNode* root);

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root);

Source file: Tree.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Tree.h"
#include "Queue.h"

//快速创建一个二叉树

BTNode* BuyBTNode(TreeDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

	node->data = x;
	node->left = NULL;
	node->right = NULL;
	return node;
}

BTNode* BinaryTreeCreat()
{
	//创建节点
	BTNode* node1 = BuyBTNode(1);
	BTNode* node2 = BuyBTNode(2);
	BTNode* node3 = BuyBTNode(3);
	BTNode* node4 = BuyBTNode(4);
	BTNode* node5 = BuyBTNode(5);
	BTNode* node6 = BuyBTNode(6);
	BTNode* node7 = BuyBTNode(7);

	//链接成树
	node1->left = node2;
	node1->right = node3;
	node2->left = node4;
	node2->right = node5;
	node3->left = node6;
	node3->right = node7;

	return node1;
}


//前序遍历
void PrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	//先访问根节点
	printf("%d ", root->data);
	//再访问左子树
	PrevOrder(root->left);
	//最后访问右子树
	PrevOrder(root->right);
}

//中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}
	//先访问左子树
	InOrder(root->left);
	//再访问根
	printf("%d ", root->data);
	//在访问右子树
	InOrder(root->right);
}

//后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("N ");
		return;
	}

	//先访问左子树
	PostOrder(root->left);
	//再访问右子树
	PostOrder(root->right); 
	//最后访问根
	printf("%d ", root->data);
}

//二叉树的节点个数
int BTreeSize(BTNode* root)
{
	//为空就返回0
	if (root == NULL)
	{
		return 0;
	}

	//不为空就返回根节点和它的左右子树节点的和
	return BTreeSize(root->left)
		+ BTreeSize(root->right)
		+ 1;
}

//求叶子节点的个数
int BTreeLeafSize(BTNode* root)
{
	//左右子树只存在一个或者为空树
	if (root == NULL)
	{
		return 0;
	}
	//左右子树都不存在
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//递归调用下面的子树
	return BTreeLeafSize(root->left) 
		+ BTreeLeafSize(root->right);
}

//二叉树的高度
int BTreeHight(BTNode* root)
{
	//是否为空树
	if (root == NULL)
	{
		return 0;
	}
	//保存左右子树的高度
	int LeftTreeHight = BTreeHight(root->left);
	int RightTreeHight = BTreeHight(root->right);

	return LeftTreeHight > RightTreeHight ? LeftTreeHight + 1 : RightTreeHight + 1; //加上自己本身的高度1
}

//不推荐的写法
//二叉树的高度
//int BTreeHight(BTNode* root)
//{
//	//是否为空树
//	if (root == NULL)
//	{
//		return 0;
//	}
//
//	//直接递归
//	return BTreeHight(root->left) > BTreeHight(root->right) ? 
//		BTreeHight(root->left) + 1 : BTreeHight(root->right) + 1; //加上自己本身的高度1
//}


//第k层节点的个数
int  BTreeLeafKSize(BTNode* root, int k)
{
	//判断k的合理性
	assert(k > 0);
	//树为空
	if (root == NULL)
	{
		return 0;
	}
	//树不为空,且k==1
	if (k == 1 )
	{
		//算一个有效结点
		return 1;
	}
	//树既不为空,k也不为1,继续向下走
	return BTreeLeafKSize(root->left, k - 1) + 
		BTreeLeafKSize(root->right, k - 1);
}

//查找值为x的结点
BTNode* BTreeFind(BTNode* root, TreeDataType x)
{
	//判断是否为空树
	if (root == NULL)
	{
		return NULL;
	}
	//找到了就返回地址
	if (root->data == x)
	{
		return root;
	}
	//先去左树找,找到了就返回
	BTNode* left = BTreeFind(root->left, x);
	if (left)
	{
		return left;
	}
	//再去右树找,找到了就返回
	BTNode* right = BTreeFind(root->right, x);
	if (right)
	{
		return right;
	}
	//如果左右树都没有找到就返回NULL
	return NULL;
}

//层序遍历
void LevelOrder(BTNode* root)
{
	//创建队列
	Queue q;
	QueueInit(&q);

	//先将第一层的插入队列
	if (root)
		QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		//保存队头的数据
		BTNode* front = QueueFront(&q);
		//删除队头
		QueuePop(&q);
		printf("%d ", front->data);

		//将第二层的数据带入队列
		if (front->left)
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);

	}
	printf("\n");
	QueueDestroy(&q);
}

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
//#代表空树
//创建二叉树
BTNode* CreatBinaryTree(char* a, int* pi)
{
	//判断是否为'#'
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	//不为'#'就创建
	BTNode* root = BuyBTNode(a[*pi]);
	//继续访问下一个元素
	(*pi)++;

	//递归创建它的左子树
	root->left = CreatBinaryTree(a, pi);
	//递归创建它的右子树
	root->right = CreatBinaryTree(a, pi);
	return root;
}

//销毁二叉树
void BTDestroy(BTNode* root)
{
	//后序遍历销毁
	//为空就返回
	if (root == NULL)
		return;

	//先走左树再走右树
	BTDestroy(root->left);
	BTDestroy(root->right);

	//释放
	free(root);
}

//判断二叉树是否为完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	//创建队列进行层序遍历
	Queue q;
	QueueInit(&q);

	if (root)
		QueuePush(&q, root);

	//层序遍历
	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;
}

Source file: Test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "Tree.h"
#include "Queue.h"

void TestTree1()
{
	BTNode* root = BinaryTreeCreat();
	//前序遍历
	PrevOrder(root);
	printf("\n");

	//中序遍历
	InOrder(root);
	printf("\n");

	//后序遍历
	PostOrder(root);
	printf("\n");

	//
	printf("BTreeSize:%d\n", BTreeSize(root));

	printf("BTreeLeafSize:%d\n", BTreeLeafSize(root));

	printf("BTreeHight:%d\n", BTreeHight(root));

	LevelOrder(root);

	printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root));

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

void TestTree2()
{
	char a[] = { "abc##de#g##f###" };
	int i = 0;
	BTNode* root = CreatBinaryTree(a, &i);

	//中序遍历
	InOrder1(root);
	printf("\n");
	printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root));

	BTDestroy(root);
	root = NULL;
}

int main()
{
	TestTree1();
	//
	//TestTree2();
	return 0;
}

That’s all for sharing today’s blog, if you like it, leave your three links, thank you! See you next time! ! 

Guess you like

Origin blog.csdn.net/Yikefore/article/details/130859450