Storage of DS binary tree

Preface

In the last issue, we have introduced the basic knowledge related to trees, learned about the concepts and structures related to trees, the concepts, structures and properties of binary trees, and also introduced its storage method! In this issue, we will implement the sequential storage and chain storage of binary trees respectively based on the introduction in the previous issue!

Introduction to the contents of this issue

The sequential structure of a binary tree

The concept and structure of heap

Heap implementation

Heap applications

Binary tree chain structure

Binary tree traversal

Basic problems with binary trees

1. Sequential structure of binary tree

1. Sequential structure of binary tree

We introduced in the last issue that general binary trees are not suitable for storage in arrays because it may cause a lot of waste of space. ! But complete binary trees are suitable for storage in arrays! This is how the real heap (complete binary tree) works! Note that the heap here is a data structure, not the heap in memory (the concept of the operating system)!

2. Concept and structure of heap

If there is a key set K={k0,k1,k2,...,kn-1}, all their elements are stored in a one-dimensional array in the order of a complete binary tree, and satisfy: Ki <= K2*i+1 && Ki <= K2*i+2 (Ki >= K2*i+1 && Ki >= K2*i+2) i = 0,1,2. ...is called a small heap (large heap), the heap with the largest root node is called the maximum heap or large root heap, and the heap with the smallest root node is called the minimum heap or small root heap.

Properties of heap:

The total value of a node in the heap is not greater or less than the value of the parent node!

The heapis always a complete binary tree!

Big pile: The value of the parent node is greater than or equal to the value of its child node!

Small heap: The value of the parent node is less than or equal to the value of its child node!

OK, let’s draw a picture to explain:

3. Implementation of heap

Heap structure declaration

typedef int HPDataType;
typedef struct Heap 
{
	HPDataType* a;//存储数据的数组
	int size;//有效个数
	int capacity;//数组容量
}HP;

Heap initialization, destruction and printing

This is the same as the sequence table and sequence stack, so I won’t explain it anymore, just go to the code!

//初始化
void HPInit(HP* p)
{
	assert(p);
	p->a = NULL;
	p->size = 0;
	p->capacity = 0;
}

//销毁
void HPDestory(HP* p)
{
	assert(p);
	free(p->a);
	p->size = p->capacity = 0;
}

//打印
void HPPrint(HP* p)
{
	assert(p);
	for (int i = 0; i < p->size; i++)
	{
		printf("%d ", p->a[i]);
	}
	printf("\n");
}

Heap insertion

Since the heap here is stored in the array in the order of a complete binary tree, tail insertion is the most efficient and effective for array insertion! It also fits the characteristics of the pile here. But to continue to ensure that the heap is maintained after insertion, some adjustments must be made! There are two ways here, one is to adjust upward and the other is to adjust downward! Let’s first introduce the adjustment algorithm!

Adjust the algorithm upwards

When a piece of data is inserted into the heap, that is, an element is inserted at the end of the array, the array may no longer be a heap. To become a heap, it needs to be adjusted upward (downward adjustment will be introduced later)!

Ideas for upward adjustment:

When the value of the child node is less than (small heap) or greater than (large heap) the parent node, the child needs to be exchanged with the parent! Since we don’t know exactly how much the inserted value will affect, we need to compare the ancestor nodes of each layer of the node one by one, that is, /span> the child adjusts to the 0 position, it can no longer adjust (it will cross the boundary if it is adjusted again) and it is over! What we usually think of is: it ends when the father is less than 0, but! The father cannot be less than 0, even if the father is at position 0, (0-1)/2 == 0, C language will round! Therefore, it is not possible to judge by the father. Then you can only use the child to judge. WhenWhen does the cycle of exchange (adjustment) end? ), and the judgment continues... ExactlyThe value of the parent node is less than or greater than the exchange, otherwise it will end without exchange! After the exchange, the child goes to the father's position, and the father goes to his father's position (-1 divided by 2

Specific adjustment process:

Code:

void AdjsutUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;//第一次找孩子的父亲
	while (child > 0)//当孩子为0结束
	{
		if (a[child] < a[parent])//孩子比父亲小(小堆)
		{
			Swap(&a[child], &a[parent]);//交换
			child = parent;//孩子到父亲的位置
			parent = (parent - 1) / 2;//父亲到他父亲的位置
		}
		else
		{
			break;//孩子和父亲相等或大于父亲时结束
		}
	}
}

Complexity analysis: There are 0 less adjustments here and a maximum height adjustment (h = log2(N+1)) times, so the time complexity is: O(lg(N )),Space complexity: The number of additional temporary variables used is a constant, so Space complexity: O(1)

To achieve insertion here, a downward adjustment algorithm must be implemented! Let’s implement it:

Adjust algorithm downwards

The prerequisite for downward adjustment is:The left and right subtrees must be heaps! ! !

The idea of ​​adjusting the algorithm downward:

When the left and right subtrees of the node are both heaps (large heap or small heap) and the value of the node is less than (large heap) or greater than (small heap) the value of the root node of the left and right subtreesBut one thing to note here is that the node does not necessarily have a right child if it has a left child, so when looking for the smaller or larger child, you must determine whether its right child exists* 2+1 Whether< nWhen the father reaches the last level, the child's position will exceed the length of the array by *2+1 and it will end! ? When does the loop end Otherwise, continue to loop and judge! ! End directly when encountering a node in the subtree that the node is equal to or greater than , and continues to judge. , When the value of the father node is exchanged with the child node to be exchanged, the father reaches the position of the child, and the child reaches the position of the child's child, but the specific exchange The number of layers is determined, so you have to compare each layer one by one! find the smaller or larger node in the left and right subtrees of the node, and exchange it with the node, to ensure that it is still a heap, it needs to be adjusted downward. At this time, you have to

Specific adjustment process:

Here is a situation where his right child does not exist (leading to out of bounds):

Code:


//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;//假设第一个孩子就是要交换的那个孩子
	while (child < n)
	{
		//假设错误,调整
		if (child + 1 < n && a[child] > a[child + 1])
		{
			++child;
		}

		if (a[child] < a[parent])//父亲比孩子大(小堆)
		{
			Swap(&a[child], &a[parent]);//交换
			parent = child;//父亲到孩子的位置
			child = parent * 2 + 1;//孩子到他孩子的位置
		}
		else
		{
			break;//当父亲和孩子相等或父亲大于孩子时结束
		}
	}
}

The downward adjustment algorithm also adjusts the height a maximum of times and a minimum of 0 times. Therefore, Time complexity: O(lg(N)), and the number of additional temporary variables used is a constant, so Space complexity: O(1)

Code for inserting data into the heap:

void HPPush(HP* p, HPDataType x)
{
	assert(p);
	//判断扩容
	if (p->capacity == p->size)
	{
		int newcapacity = p->capacity == 0 ? 4 : p->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(p->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}

		p->a = tmp;
		p->capacity = newcapacity;
	}

	//插入
	p->a[p->size++] = x;
	//向上调整
	AdjustUp(p->a, p->size - 1);
}

Time complexity is the complexity adjusted upward: O(lg(N)), The number of additional spaces used is a constant, that is, and the space complexity is O(1),

Heap creation

The idea of ​​creating a heap here is to insert the elements in the array into the heap one by one. Each insertion will be adjusted upward, and finally it becomes a heap!

void Test()
{
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HPPush(&hp, a[i]);
	}
	HPPrint(&hp);

	HPDestory(&hp);
}

Time complexity: n elements, each insertion is lgN, so the whole is:O(N*lgN).

Space complexity: To create a heap, you need to open space for its underlying array. Assume that the length of the array is N, that is, O(N) a>

Note: If you want a large pile here, you can change the greater than sign to less than sign in the upward adjustment algorithm to achieve it!

Heap deletion

The physical storage of the heap is an array, so what we essentially operate on is an array! Heap deletion is to delete the data at the top of the heap. But you cannot directly delete the data on the top of the heap. If you directly delete the data on the top of the heap, the remaining data may not be part of the heap! The next time you insert or delete, you have torebuild the heap (O(N*(N*lgN)), Adjust the heap construction upward to O(N*lgN). Here, a heap is built every time one is deleted. If there are N elements, it is O(N*(N*lgN), the overall complexity will become very high! So this method is not feasible. The gameplay here is, The first one and the last one are exchanged first, and then the last one is deleted (-- size) and then adjust the element at position 0 downward!Time complexity:O(lg(N))

Code

//删除
void HPPop(HP* p)
{
	assert(p);
	assert(p->size > 0);//数组为空就不要删了
	//先把堆的第一个数据与最后一个数据交换,然后--size
	Swap(&p->a[0], &p->a[p->size - 1]);
	--p->size;
	//向下调整
	AdjustDown(p->a, p->size, 0);
}

Test empty and return the top data of the heap

//取堆顶的数据
HPDataType HPTop(HP* p)
{
	assert(p);
	assert(p->size > 0);//没有元素
	return p->a[0];
}

//判断是否为空
bool IsEmpty(HP* p)
{
	assert(p);
	return p->size == 0;
}

Test it as a whole:

void Test()
{
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HPPush(&hp, a[i]);
	}
	HPPrint(&hp);

	while (!IsEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}

	HPDestory(&hp);
}

Here, the data in the heap is taken and deleted, and it immediately becomes orderly after one trip! But this is not a heap sort! Heap row will be introduced later and is explained!

All the code in the heap

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


typedef int HPDataType;
typedef struct Heap 
{
	HPDataType* a;//存储数据的数组
	int size;//有效个数
	int capacity;//数组容量
}HP;

//初始化
void HPInit(HP* p);
//销毁
void HPDestory(HP* p);
//打印
void HPPrint(HP* p);
//交换
void Swap(HPDataType* a, HPDataType* b);
//向上调整算法
void AdjustUp(HPDataType* a, int child);
//向下调整算法
void AdjustDown(HPDataType*a, int n, int parent);
//插入
void HPPush(HP* p, HPDataType x);
//删除
void HPPop(HP* p);
//取堆顶的数据
HPDataType HPTop(HP* p);
//判断是否为空
bool IsEmpty(HP* p);
#include "Heap.h"

//初始化
void HPInit(HP* p)
{
	assert(p);
	p->a = NULL;
	p->size = 0;
	p->capacity = 0;
}

//销毁
void HPDestory(HP* p)
{
	assert(p);
	free(p->a);
	p->size = p->capacity = 0;
}

//打印
void HPPrint(HP* p)
{
	assert(p);
	for (int i = 0; i < p->size; i++)
	{
		printf("%d ", p->a[i]);
	}
	printf("\n");
}

//交换
void Swap(HPDataType* a, HPDataType* b)
{
	HPDataType tmp = *a;
	*a = *b;
	*b = tmp;
}

//向上调整算法
void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;//第一次找孩子的父亲
	while (child > 0)//当孩子为0结束
	{
		if (a[child] < a[parent])//孩子比父亲小(小堆)
		{
			Swap(&a[child], &a[parent]);//交换
			child = parent;//孩子到父亲的位置
			parent = (parent - 1) / 2;//父亲到他父亲的位置
		}
		else
		{
			break;//孩子和父亲相等或大于父亲时结束
		}
	}
}

//向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;//假设第一个孩子就是要交换的那个孩子
	while (child < n)
	{
		//假设错误,调整
		if (child + 1 < n && a[child] > a[child + 1])
		{
			++child;
		}

		if (a[child] < a[parent])//父亲比孩子大(小堆)
		{
			Swap(&a[child], &a[parent]);//交换
			parent = child;//父亲到孩子的位置
			child = parent * 2 + 1;//孩子到他孩子的位置
		}
		else
		{
			break;//当父亲和孩子相等或父亲大于孩子时结束
		}
	}
}

//插入
void HPPush(HP* p, HPDataType x)
{
	assert(p);
	//判断扩容
	if (p->capacity == p->size)
	{
		int newcapacity = p->capacity == 0 ? 4 : p->capacity * 2;
		HPDataType* tmp = (HPDataType*)realloc(p->a, sizeof(HPDataType) * newcapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}

		p->a = tmp;
		p->capacity = newcapacity;
	}

	//插入
	p->a[p->size++] = x;
	//向上调整
	AdjustUp(p->a, p->size - 1);
}

//删除
void HPPop(HP* p)
{
	assert(p);
	assert(p->size > 0);//数组为空就不要删了
	//先把堆的第一个数据与最后一个数据交换,然后--size
	Swap(&p->a[0], &p->a[p->size - 1]);
	--p->size;
	//向下调整
	AdjustDown(p->a, p->size, 0);
}

//取堆顶的数据
HPDataType HPTop(HP* p)
{
	assert(p);
	assert(p->size > 0);//没有元素
	return p->a[0];
}

//判断是否为空
bool IsEmpty(HP* p)
{
	assert(p);
	return p->size == 0;
}
#include "Heap.h"

void Test()
{
	int a[] = { 65,100,70,32,50,60 };
	HP hp;
	HPInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		HPPush(&hp, a[i]);
	}
	HPPrint(&hp);
	int k = 5;

	while (!IsEmpty(&hp) && k--)
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}

	HPDestory(&hp);
}

int main()
{
	Test();
	return 0;
}

4. Application of heap

There are two applications of heap, one isheap sort, and the other isTopK problem< /span>Needless to say, heap sorting is used to sort an array. TopK questions are also very common questions in daily life! For example: you usually order takeout, and the ranking that shows the local roast duck rice is TopK, and the top ten people in your major are all TopK questions! !

Heap sort

Although our heap above can already be sorted! But we say it is not heap sorting. The reason is that when you usually sort arrays, you do not heap this data structure! Although this structure is simple, it still has 200 lines. It is not cost-effective to do it by hand! On the other hand, even if you squeeze it out, the heap still needs to open up space, and there will be space consumption. Our general sorting is to sort it out into an array! How to do it? There are two ways here: adjust heap construction upward + adjust sorting downward, adjust heap construction downward + adjust sorting downward!

About ascending and descending order:Ascending order--->Build a big heap Descending order----->Build a small heap

We generally want to build a small heap in ascending order, but if the small heap removes the smallest top data from the heap, the other data may not necessarily be a heap. If we want to continue sorting, we must build a heap. As analyzed above, the complexity has become higher! So here adopts the idea of ​​deletion, replaces the largest or smallest one to the end, and then does the first N-i (i= 1, 2, 3...n) for downward adjustment!

Adjust heap upward

void HeapSort(HPDataType* a, int n)
{
	//向上调整建堆
	//O(N*lgN)
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}

	//向下调整排序
	//O(N*lgN)
	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

HereUpward adjustment to build a heap is the same idea as the insertion of the heap above! The time complexity is O(lgN*N),The time complexity of adjusting downward sorting is: O( N*lgN)---->There are n elements, and each one is arranged with a subscript, which is the deletion idea above!

Adjust heap downward

void HeapSort(HPDataType* a, int n)
{
	//向下调整建堆
	//O(N)
	for (int i = (n - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}

	//向下调整排序
	//O(N*lgN)
	int end = n - 1;
	while (end)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		--end;
	}
}

Adjusting downward to build a heap means starting from the parent node of the last element and adjusting downward to a heap! In this way, the left and right subtrees of the upper-level nodes must be heaps!

Downward adjustment of the space complexity of heap construction is the same as the upward adjustment of heap construction above! It is also O(N*lgN), about the time complexity of downward adjustment to build the heap is O(N) ,Come here and push it!

In factThe complexity of adjusting the heap building time upwards is also calculated in this way! We can also do the math:

TopK problem

TopKAs the name suggests, it means to find the first K largest or smallest elements in the data set. Generally, the amount of data is very large!

For example: Find the world's top 500 companies, the top 5 majors, the 100 most active players in the game, etc.! For the above problems, the normal thing to think of is sorting the data. But if the amount is very large, it may not be able to be stored in the memory or the data may not be loaded into the memory at once. Sorting cannot be solved at this time! The best solution here is to use a heap! The idea is as follows:

1. Use the first K data in the data to build a heap in the memory (the first K largest ones, build a small heap; the first K smallest ones, build a large heap)

2. Use the remaining n-k elements to compare with the top of the heap data in sequence. If not satisfied, replace the top elements of the heap.

3. After completing the above two steps, the remaining K elements in the heap are the top K largest or smallest elements.

Next, I write 100,000 data in the file and find the top 5 largest ones as an example:

Create data

//创造数据
void CreateData()
{
	int n = 100000;
	srand((size_t)time(NULL));//产生随机数
	const char* file = "data.txt";//文件名
	FILE* fin = fopen(file, "w");//用fopen打开文件
	if (fin == NULL)
	{
		perror("fopen failed");//打开失败,直接终止程序
		exit(-1);
	}

	for (int i = 0; i < n; i++)
	{
		int x = (rand() + i) % 100000;//产生随机数
		fprintf(fin, "%d\n", x);//把x以%d的形式写入fin指向的文件中
	}

	fclose(fin);//关闭文件
}

You can take a look:

TopK

//TopK
void PrintTopK(const char* filename, int k)
{
	FILE* fout = fopen(filename, "r");//打开文件以读的形式
	if (fout == NULL)
	{
		perror("fopen failed");
		exit(-1);
	}

	int* minheap = (int*)malloc(sizeof(int) * k);//创建k个空间的数组
	if (minheap == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}

	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &minheap[i]);//从fout指向的文件中以%d形式读取k个,放到数组中
	}

	//向下调整建堆
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(minheap, k, i);
	}

	int x = 0;
	while (~fscanf(fout, "%d", &x))//当fscanf没有读取失败即文件中还有数据时一直读取
	{
		//判断是否入堆
		if (x > minheap[0])
		{
			//替换入2堆
			minheap[0] = x;
			AdjustDown(minheap, k, 0);//向下调整
		}
	}

	//输出
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
	printf("\n");
		
	free(minheap);//释放malloc的空间,防止内存泄露
	fclose(fout);//关闭文件
}

The above are all the basic knowledge about C language files. I have added very detailed comments so I won’t go into details again! Let me explain here why the largest K elements need to create a small heap, while the smallest K elements need to create a large heap! And why the last remaining elements are TopK elements!

This is the largest element in TopK, and the same is true for the smallest one!

OK, let’s test the above TopK:

But a question is how do we know that he is correct? Our solution is to add 5 larger values ​​at random positions in the data.txt file, and then compare it with the running results!

OK, the results are consistent! This is the TopK problem.

2. Chain structure of binary tree

The chained structure of a binary tree uses a linked list structure to store binary trees. Unlike a complete binary tree, there are no restrictions. All binary trees can be stored in a chained structure! We introduced the basics of binary trees in the previous issue. The chain binary tree consists of three parts: the root, the left subtree, and the right subtree. Here, according to the structure of the binary tree ( the left and right subtrees can be divided into root and left and right subtrees), we can see that it is very suitable for processing with a recursive structure!

Binary tree traversal

Binary tree traversal is the operation of visiting each node of the binary tree in sequence according to certain special rules (each node can only be visited once)! There are four traversal methods (recursive): pre-order traversal, in-order traversal, post-order traversal and level-order traversal!

There must be a binary tree before traversing, so you must first create a binary tree!

Node declaration of binary tree:

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BT;

Creation of binary tree

Create hereWe use the preorder to create the binary tree (If you don’t know the preorder, you can go to the following to read the preorder), and then traverse it in sequence String creation! input a string containing NULL (#) in the order of the binary tree! Here we must have a tree before traversing. We can# means that the node is NULL, otherwise it means the value of the node !

Idea:

Traverse the string pointed to by str in sequence, skipping when # indicates that the node is NULL, otherwise open a node for the value, and then skip the character. And use the same idea to create its left and right subtrees in turn! When the left and right subtrees are created, return to the root node!

//创建
BT* CreateBT(char* str, int* i)
{
	if (str[*i] == '#')//如果是#即为NULL
	{
		++(*i);//跳过
		return NULL;//返回当前节点的值为空
	}

	BT* root = (BT*)malloc(sizeof(BT));//否则为该值开空间
	if (root == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}

	root->data = str[*i];//吧改值赋给当前开的节点的数据域
	(*i)++;//跳过当前字符
	root->left = CreateBT(str,i);//继续构建该节点的左子树
	root->right = CreateBT(str,i);//继续构建该节点的右子树

	return root;//构建好了当前子树返回根节点
}

Let me draw a picture to explain:

Preorder traversal

Binary treePreorder traversal (Preorder Traversal) It is a traversal method in which first visits the root node, then the left subtree, and finally the right subtree!

//前序遍历
void PrveOrder(BT* root)
{
	if (root == NULL)//如果该节点已经为空,直接返回NULL
		return;

	printf("%c ", root->data);//先访问该节点的值
	PrveOrder(root->left);//然后访问其左子树
	PrveOrder(root->right);//访问其右子树
}

The recursive traversal of the pre-order, mid-order and post-order is very similar. Here I draw a specific recursive expansion diagram of the pre-order traversal. The same goes for the mid-order and post-order!

inorder traversal

Binary treeInorder traversal (Inorder Traversal) It is a traversal method that first visits the left subtree, then the root node, and finally the right subtree!

//中序遍历
void InOrder(BT* root)
{
	if (root == NULL)
		return;

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

Postorder traversal

Binary treePostorder traversal (Postorder Traversal) It is a traversal method in which first visits the left subtree, then the right subtree, and finally visits the root node!

//后序遍历
void PostOrder(BT* root)
{
	if (root == NULL)
		return;

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

OK, test it out:

void Test()
{
	char str[100] = { 0 };
	printf("请输入二叉树序列的字符串:> ");
	gets(str);
	int i = 0;
	BT* root = CreateBT(str, &i);
	PrveOrder(root);
	printf("\n");
	InOrder(root);
	printf("\n");
	PostOrder(root);
}

OK, no problem! Let’s introduce another type of traversal—level-order traversal!

level-order traversal

Binary tree level-order traversal means starting from the root node of, one level from top to the next Layers, each layer accesses each node in sequence from left to right! To implement this algorithm, we need to use a data structure we introduced before --->queue to assist in implementation.

Idea:If the root node is not empty, then join the queue. Thenjudge whether the queue is empty. If it is not empty, get the system node of the queue head node , and then determine whether the left and right children of the retrieved team head node are empty, if not empty thenJoin the team, otherwise you won’t join! Then pop the team head node, continue to execute the judgment of the new team head node!

Let’s take an example and draw a picture to understand:

Code

//层序遍历
void LevelOrder(BT* root)
{
	Queue q;
	QInit(&q);
	if (root)
		QPush(&q, root);

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);
		printf("%c ", front->data);

		if (front->left)
			QPush(&q, front->left);

		if (front->right)
			QPush(&q, front->right);

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

	QDestory(&q);
}

Basic problems with binary trees

The basic questions here are mainly:Find the number of nodes in a binary tree, find the number of leaf nodes in a binary tree, the number of nodes in the kth layer of a binary tree, and search by value in a binary tree , the height of a binary tree and other basic issues.

Find the number of nodes in a binary tree

Idea: summary point of left subtree + summary point of right subtree + root node

//节点个数
int BTNodeSize(BT* root)
{
	if (root == NULL)
		return 0;
	
	return BTNodeSize(root->left) + BTNodeSize(root->right) + 1;
}

Find the number of leaf nodes

Idea: The characteristic of leaf nodes is that both the left and right subtrees are empty. When both the left and right subtrees of a node are empty, it is a leaf node.

//叶子节点的个数
int BTLeafNodeSize(BT* root)
{
	if (root == NULL)
		return 0;

	if (!root->left && !root->right)
		return 1;

	return BTLeafNodeSize(root->left) + BTLeafNodeSize(root->right);
}

The number of nodes in the Kth layer

Idea: The node at the Kth level is the k level of the root node and the k-1 level of k's children. So give the problem to the child in turn, and look for it layer by layer -1 until k==1, which is the node of K layer. Note: K must be judged to be greater than 0

//第k层的节点个数
int BTLevelKSize(BT* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k -1);
}

height of binary tree

Idea: The overall height of a binary tree is the higher of the left and right subtrees + the root node

//二叉树的高度
int BTDepth(BT* root)
{
	if (root == NULL)
		return 0;

	int Left = BTDepth(root->left);
	int Right = BTDepth(root->right);

	return Left > Right ? Left + 1 : Right + 1;
}

Binary tree search by value

Idea: Start searching from the root node. If the return node address is found, if not found, continue to search in the left and right subtrees.

//按值查找
BT* BTFind(BT* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

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

	return BTFind(root->left, x) ? BTFind(root->left, x) : BTFind(root->right, x);
}

This way of writing may not be readable, but you can also use the following way!

BT* BTFind(BT* root, int x)
{
	if (root == NULL)
		return NULL;

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

	BT* ret = NULL;
	ret = BTFind(root->left, x);
	if (ret)
		return ret;

	ret = BTFind(root->right, x);
	if (ret)
		return ret;

	return NULL;
}

Or this also works:

//按值查找
BT* BTFind(BT* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

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

	BT* ret = BTFind(root->left, x);
	if (ret)
		return ret;

	return BTFind(root->right, x);
}

Determine whether it is a complete binary tree

Idea:The characteristic of a complete binary tree is that the first k-1 levels are full binary trees, and the nodes of the kth level are continuous (must be the left child first, then the right child, no The left child must not have a right child). So according to this characteristic, when we traverse nodes using level order, we will directly terminate the level order as long as we encounter NULL Traverse. Then check to see if there are non-empty nodes in the queue. If there are, it is an incomplete binary tree, otherwise it is a complete binary tree! Note: When it is found that there are non-empty nodes in the pair class, the queue space needs to be released before returning false, otherwise it will cause a memory leak! !

//判断是否是完全二叉树
bool IsCompleteBT(BT* root)
{
	Queue q;
	QInit(&q);
	if (root)
		QPush(&q, root);

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);

		if (front == NULL)
		{
			break;
		}

		QPush(&q, root->left);
		QPush(&q, root->right);

		QPop(&q);
	}

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);
		if (front)
		{
			QDestory(&q);
			return false;
		}
		QPop(&q);
	}

	QDestory(&q);
	return true;
}

Destruction of binary tree

Idea: first destroy the left subtree, then destroy the right subtree, and finally destroy the root

//二叉树的销毁
void BTDestory(BT* root)
{
	if (root == NULL)
		return;

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

have a test:

All codes stored in chain:

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BT;

//创建
BT* CreateBT(char* str, int* i);
//前序遍历
void PrveOrder(BT* root);
//中序遍历
void InOrder(BT* root);
//后序遍历
void PostOrder(BT* root);
//层序遍历
void LevelOrder(BT* root);
//节点个数
int BTNodeSize(BT* root);
//叶子节点的个数
int BTLeafNodeSize(BT* root);
//第k层的节点个数
int BTLevelKSize(BT* root, int k);
//二叉树的高度
int BTDepth(BT* root);
//按值查找
BT* BTFind(BT* root, BTDataType x);
//判断是否是完全二叉树
bool IsCompleteBT(BT* root);
//二叉树的销毁
void BTDestory(BT* root);
#include "BinaryTree.h"
#include "Queue.h"
//创建
BT* CreateBT(char* str, int* i)
{
	if (str[*i] == '#')
	{
		++(*i);
		return NULL;
	}

	BT* root = (BT*)malloc(sizeof(BT));
	if (root == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}

	root->data = str[*i];
	(*i)++;
	root->left = CreateBT(str,i);
	root->right = CreateBT(str,i);

	return root;
}

//前序遍历
void PrveOrder(BT* root)
{
	if (root == NULL)
		return;

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

//中序遍历
void InOrder(BT* root)
{
	if (root == NULL)
		return;

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

//后序遍历
void PostOrder(BT* root)
{
	if (root == NULL)
		return;

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

//层序遍历
void LevelOrder(BT* root)
{
	Queue q;
	QInit(&q);
	if (root)
		QPush(&q, root);

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);
		printf("%c ", front->data);

		if (front->left)
			QPush(&q, front->left);

		if (front->right)
			QPush(&q, front->right);

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

	QDestory(&q);
}

//节点个数
int BTNodeSize(BT* root)
{
	if (root == NULL)
		return 0;
	
	return BTNodeSize(root->left) + BTNodeSize(root->right) + 1;
}

//叶子节点的个数
int BTLeafNodeSize(BT* root)
{
	if (root == NULL)
		return 0;

	if (!root->left && !root->right)
		return 1;

	return BTLeafNodeSize(root->left) + BTLeafNodeSize(root->right);
}

//第k层的节点个数
int BTLevelKSize(BT* root, int k)
{
	assert(k > 0);
	if (root == NULL)
		return 0;

	if (k == 1)
		return 1;

	return BTLevelKSize(root->left, k - 1) + BTLevelKSize(root->right, k -1);
}

//二叉树的高度
int BTDepth(BT* root)
{
	if (root == NULL)
		return 0;

	int Left = BTDepth(root->left);
	int Right = BTDepth(root->right);

	return Left > Right ? Left + 1 : Right + 1;
}

//按值查找
BT* BTFind(BT* root, BTDataType x)
{
	if (root == NULL)
		return NULL;

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

	BT* ret = BTFind(root->left, x);
	if (ret)
		return ret;

	return BTFind(root->right, x);
}


//BT* BTFind(BT* root, int x)
//{
//	if (root == NULL)
//		return NULL;
//
//	if (root->data == x)
//		return root;
//
//	BT* ret = NULL;
//	ret = BTFind(root->left, x);
//	if (ret)
//		return ret;
//
//	ret = BTFind(root->right, x);
//	if (ret)
//		return ret;
//
//	return NULL;
//}


//判断是否是完全二叉树
bool IsCompleteBT(BT* root)
{
	Queue q;
	QInit(&q);
	if (root)
		QPush(&q, root);

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);

		if (front == NULL)
		{
			break;
		}

		QPush(&q, front->left);
		QPush(&q, front->right);

		QPop(&q);
	}

	while (!QEmpty(&q))
	{
		BT* front = QTop(&q);
		if (front)
		{
			QDestory(&q);
			return false;
		}
		QPop(&q);
	}

	QDestory(&q);
	return true;
}

//二叉树的销毁
void BTDestory(BT* root)
{
	if (root == NULL)
		return;

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

queue code

#include "Queue.h"

//初始化
void QInit(Queue* p)
{
	assert(p);
	p->head = p->tail = NULL;
	p->size = 0;
}

//销毁
void QDestory(Queue* p)
{
	assert(p);
	QNode* cur = p->head, *next = NULL;
	while (cur)
	{
		next = cur->next;
		free(cur);
		cur = next;
	}
	p->head = p->tail = NULL;
	p->size = 0;
}

//开一个新节点
QNode* BuyNode(QDataType x)
{
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//入队列
void QPush(Queue* p, QDataType x)
{
	assert(p);
	QNode* node = BuyNode(x);
	if (p->head == NULL)
	{
		p->head = p->tail = node;
	}
	else
	{
		p->tail->next = node;
		p->tail = node;
	}
	p->size++;
}

//出队列
void QPop(Queue* p)
{
	assert(p);
	assert(p->head);

	QNode* next = p->head->next;
	free(p->head);
	p->head = next;
	p->size--;
}

//获取队列头的数据
QDataType QTop(Queue* p)
{
	assert(p);
	assert(p->size > 0);

	return p->head->data;
}

//获取队列尾的数据
QDataType QTail(Queue* p)
{
	assert(p);
	assert(p->size > 0);

	return p->tail->data;
}

//是否为空
bool QEmpty(Queue* p)
{
	assert(p);
	return p->size == 0;
}

//获取队列的元素个数
int QSize(Queue* p)
{
	assert(p);
	return p->size;
}
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

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

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

//初始化
void QInit(Queue* p);

//销毁
void QDestory(Queue* p);

//开一个新节点
QNode* BuyNode(QDataType x);

//入队列
void QPush(Queue* p, QDataType x);

//出队列
void QPop(Queue* p);

//获取队列头的数据
QDataType QTop(Queue* p);

//获取队列尾的数据
QDataType QTail(Queue* p);

//是否为空
bool QEmpty(Queue* p);

//获取队列的元素个数
int QSize(Queue* p);

OK, that’s it for this sharing! Good brothers, see you next time!

Guess you like

Origin blog.csdn.net/m0_75256358/article/details/134410826