Data Structure — Binary Tree

Table of contents

1. Tree concept and structure

1. The concept of a tree

2. Related concepts of trees

3. Tree Representation

4. The practical use of trees (representing the directory tree structure of the file system)

2. Concept and structure of binary tree

 1. Binary tree concept

 2. Special binary tree

 3. Properties of Binary Trees

 4. Storage structure of binary tree

1. Sequential storage

2. Chain storage

3. The sequential structure and implementation of the binary tree

 1. The sequential structure of the binary tree

 2. The concept and structure of the heap

 3. Implementation of the heap

  3.1 The structure of the heap

  3.2 Heap creation

  3.3 Upward adjustment of pile building

  3.4 Delete heap top data

  3.5 Down adjustment

  3.6 Other functions of the heap

 4. Heap building time complexity 

 5. Application of the heap

  5.1 Heap sort

  5.2 TOP-K problem

  6. Implementation of binary tree chain structure

   6.1 Structure of Binary Tree

   6.2 Binary tree traversal

   6.2 Building a binary tree

   6.3 The number of nodes in a binary tree

   6.4 Finding Nodes

   6.5 Destroy the binary tree

   6.6 Level order traversal

   6.7 Determine whether a binary tree is a complete binary tree


 

1. Tree concept and structure

 1.   The concept of a tree

  1. A tree is a non-linear data structure, which is composed of n ( n>=0 ) finite nodes to form a set with a hierarchical relationship. It is called a tree because it looks like an upside-down tree, which means it has the roots pointing up and the leaves pointing down .
  2. There is a special node called the root node, the root node has no predecessor node, except the root node, the other nodes are divided into M (M>0) disjoint sets T1 , T2 , ... , Tm , Each set Ti (1<= i <= m) is a subtree similar in structure to a tree. The root node of each subtree has one and only one predecessor, and can have zero or more successors. Therefore, the tree is defined recursively .

 Note: In the tree structure, there can be no intersection between subtrees, otherwise it is not a tree structure

 2. Related concepts of trees

  1. Degree of node : The number of subtrees contained in a node is called the degree of the node; as shown in the figure above: A is 6
  2. Leaf node or terminal node : A node with a degree of 0 is called a leaf node; as shown in the above figure: B , C , H , I... etc. are leaf nodes
  3. Non-terminal node or branch node : a node whose degree is not 0 ; as shown in the figure above: nodes such as D , E , F , G... are branch nodes
  4. Parent node or parent node : If a node contains child nodes, this node is called the parent node of its child nodes; as shown above: A is the parent node of B
  5. Child node or child node : the root node of the subtree contained in a node is called the child node of the node; as shown above: B is the child node of A
  6. Brother nodes : Nodes with the same parent node are called brother nodes; as shown in the above figure: B and C are brother nodes
  7. Degree of the tree : In a tree, the degree of the largest node is called the degree of the tree; as shown above: the degree of the tree is 6
  8. The level of nodes : starting from the definition of the root, the root is the first level , the child nodes of the root are the second level, and so on;
  9. Tree height or depth : the maximum level of nodes in the tree; as shown above: the height of the tree is 4
  10. Cousin nodes : Nodes whose parents are on the same layer are cousins; as shown in the above figure: H and I are sibling nodes
  11. Ancestors of a node : from the root to all nodes on the branches of the node; as shown in the figure above: A is the ancestor of all nodes
  12. Descendants : Any node in the subtree rooted at a node is called a descendant of the node. As shown above: all nodes are descendants of A
  13. Forest : A collection of m ( m>0 ) disjoint trees is called a forest;

3.  Tree Representation

The tree structure is more complicated than the linear table, and it is more troublesome to store and express. Since the value range is saved, the relationship between nodes and nodes is also saved . In practice, there are many ways to represent trees, such as: parent representation , child representation, child parent representation, and child sibling representation. Here we simply understand the most commonly used child brother notation .
typedef int DataType;
struct Node
{
     struct Node* _firstChild1; // 第一个孩子结点
     struct Node* _pNextBrother; // 指向其下一个兄弟结点
     DataType _data; // 结点中的数据域
};

4.  The application of trees in practice (representing the directory tree structure of the file system)

 


2. Concept and structure of binary tree

 1. Binary tree concept

A binary tree is a finite set of nodes that :
1. or empty
2. It consists of a root node plus two binary trees called left subtree and right subtree

 As can be seen from the figure above:

1. There is no node with degree greater than 2 in the binary tree
2. The subtrees of the binary tree are divided into left and right, and the order cannot be reversed, so the binary tree is an ordered tree
Note: For any binary tree, it is composed of the following situations:

2.  Special binary tree

1. Full binary tree : a binary tree, if the number of nodes in each layer reaches the maximum value, then this binary tree is a full binary tree. That is to say, if a binary tree has K layers and the total number of nodes is (2^k)-1, then it is a full binary tree.
2. Complete binary tree : A complete binary tree is a very efficient data structure, and a complete binary tree is derived from a full binary tree. For a binary tree with a depth of K and n nodes, it is called a complete binary tree if and only if each node has a one-to- one correspondence with the nodes numbered from 1 to n in the full binary tree with a depth of K. It should be noted that a full binary tree is a special kind of complete binary tree.

 3.  Properties of Binary Trees

1. If the number of layers of the root node is specified as 1 , then there are at most 2^(i-1) nodes on the i-th layer  of a non-empty binary tree .
2. If the number of layers of the root node is specified as 1 , then the maximum number of nodes in a binary tree with a depth of h is (2^h)-1.
3. For any binary tree , if the number of leaf nodes is 0 and the number of branch nodes is 2, then n₀=n₂+1
4. If the number of layers of the root node is specified as 1 , the depth of a full binary tree with n nodes , h=㏒₂(n+1) . (ps: ㏒₂(n+1) is log base 2, n+1 is logarithm )
5. For a complete binary tree with n nodes, if all nodes are numbered from 0 in the order of the array from top to bottom and from left to right, then for the node with the serial number i :
1. If i>0 , the parent number of the node at i position: (i-1)/2 ; i=0 , i is the root node number, no parent node
2. If 2i+1<n , the left child number: 2i+1 , 2i+1>=n otherwise there is no left child
3. If 2i+2<n , the serial number of the right child: 2i+2 , 2i+2>=n , otherwise there is no right child

 4. Storage structure of binary tree

Binary trees can generally be stored using two structures, a sequential structure and a chain structure.

1. Sequential storage

        Sequential structure storage is to use arrays for storage . Generally, arrays are only suitable for representing complete binary trees , because not complete binary trees will waste space. In reality, only heaps are stored in arrays. Binary tree sequential storage is physically an array and logically a binary tree.

2. 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.

typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
     struct BinTreeNode* _pLeft; // 指向当前节点左孩子
     struct BinTreeNode* _pRight; // 指向当前节点右孩子
     BTDataType _data; // 当前节点值域
}
// 三叉链
struct BinaryTreeNode
{
     struct BinTreeNode* _pParent; // 指向当前节点的双亲
     struct BinTreeNode* _pLeft; // 指向当前节点左孩子
     struct BinTreeNode* _pRight; // 指向当前节点右孩子
     BTDataType _data; // 当前节点值域
};

3. The sequential structure and implementation of the binary tree

 1. The sequential structure of the binary tree

        Ordinary binary trees are not suitable for storage in arrays, because there may be a lot of wasted space. The complete binary tree is more suitable for sequential structure storage. In reality, we usually store the heap ( a binary tree ) in an array of sequential structures. It should be noted that the heap here and the heap in the virtual process address space of the operating system are two different things. One is the data structure, and the other is the management in the operating system. A region of memory is segmented.

 2.  The concept and structure of the heap

If there is a set of key codes K={K₀, ​​K₁, K₂, ..., K(n-1)}, store all its elements in a one-dimensional array in the order of a complete binary tree, and satisfy: Ki <=K(2*i+1) and Ki<=K(2*i+2) (Ki>=K(2*i+1) and Ki>=K(2*i+2))i=0 , 1, 2..., it is called a small heap (or a large heap). The heap with the largest root node is called the largest heap or large root heap, and the heap with the smallest root node is called the smallest heap or small root heap.
 
Properties of the heap:

  1. The value of a node in the heap is always not greater than or not less than the value of its parent node;
  2. The heap is always a complete binary tree.

 3.  Implementation of the heap

  3.1 The structure of the heap

typedef int HpDataType;
typedef struct Heap
{
	HpDataType* _a;
	int _size;
	int _capacity;
}Heap;

  3.2 Heap creation

        For the construction of the heap, I directly reuse the initialization and insertion of the heap. For the insertion, I use upward adjustment to build the heap. Whether it is a large or small heap, you can control it yourself.

//堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}
// 堆的构建
void HeapCreate(Heap* hp, HpDataType* a, int n)
{
	assert(hp);
	HeapInit(hp);

	for(int i = 0;i < n;++i)
	{
		HeapPush(hp, a[i]);
	}
}

//插入
void HeapPush(Heap* hp, HpDataType x)
{
	assert(hp);
	if (hp->_capacity == hp->_size)
	{
		HpDataType newcapacity = hp->_capacity == 0 ? 4 : hp->_capacity * 2;
		HpDataType* tmp = (HpDataType*)realloc(hp->_a, sizeof(HpDataType)* newcapacity);
		if (tmp == NULL)
		{
			perror("malloc fail");
			exit(-1);
		}
		
		hp->_a = tmp;
		hp->_capacity = newcapacity;
	}
	hp->_a[hp->_size] = x;
	hp->_size++;
	Adjustup(hp->_a,hp->_size - 1);//向上调整建堆
}

3.3 Upward adjustment of pile building

        Because after inserting a piece of data from the end, there may be problems with the post section of the heap, so an adjustment is made every time a piece of data is inserted.

        If it is to build a small pile, start from the last position, and the last subscript must be the child, then if the value of the child's position is smaller than the value of the father's position, the values ​​of the two should be exchanged, and then continue to go up.

//向上调整
void Adjustup(HpDataType* a,int child)
{
	int parent = (child - 1) / 2;

	while (child > 0)
	{
		if (a[child] < a[parent])//小堆
		{
			swap(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}

}

  3.4 Delete heap top data

        Deleting the data at the top of the heap cannot directly delete the first data. If you directly delete the heap section, there will be problems, and you need to rebuild the heap later, which is time-consuming and laborious. So first exchange the data at the end with the data at the top of the heap, and then size--, the data is deleted. At this time, the data at the top of the heap is exchanged, which does not conform to the structure of the heap, and then the data at the top of the heap is down Tweak, tweak, and delete data is not done.

//删除堆顶数据
void HeapPop(Heap* hp)
{
	assert(hp);
	if (!HeapEmpty(hp))
	{
		swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
		hp->_size--;
		Adjustdown(hp->_a,hp->_size,0);
	}
}

  3.5 Down adjustment

        Still take Jianxiaodui as an example, first look at the left and right children who are younger, and exchange whoever is younger. The child is smaller than the father, exchange and continue to go down until the structure of the heap is satisfied, and the adjustment is over.

//向下调整
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;
		}
	}
}

  3.6 Other functions of the heap

//显示堆的数据
void HeapPrint(Heap* hp)
{
	assert(hp);
	for(int i = 0;i < hp->_size;++i)
	{
		printf("%d ", hp->_a[i]);
	}
	printf("\n");
}
//交换
void swap(HpDataType* q1, HpDataType* q2)
{
	HpDataType tmp = *q1;
	*q1 = *q2;
	*q2 = tmp;
}
//判断是否为空
bool HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}
//获取堆顶的数据
HpDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->_a[0];
}
//堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}
//堆的销毁
void HeapDestry(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_capacity = hp->_size = 0;
}

 4. Time complexity of building a heap 

        Because the heap is a complete binary tree, and the full binary tree is also a complete binary tree, here we use a full binary tree to prove it for simplicity (the time complexity is originally an approximation, and a few more nodes will not affect the final result) :

From this, it can be concluded that the time complexity of building a heap is O(N) .

 5.  Application of the heap

  5.1 Heap sort

        Heap sorting is to use the idea of ​​heap to sort, which is divided into two steps:
1. Build a heap
Ascending order: build a large pile
Descending order: build a small heap
2. Use the idea of ​​​​heap deletion to sort
        Taking ascending order as an example, first build a large heap. At this time, the data at the top of the heap (that is, the element with the subscript 0) must be the largest, and the top data and the last position (that is, the element with the subscript n-1) must be the largest. element) data exchange, after the exchange is completed, the largest number is in the last position, so there is no need to move this data (size--). Then adjust the data at the top of the pile downwards. After the adjustment is completed, the top of the pile will be the next largest, and then continue to exchange and adjust again until the order is arranged.

// 对数组进行堆排序
void HeapSort(int* a, int n)
{
	//升序建大堆,降序建小堆
	for (int i = (n-2)/2; i >= 0; --i)
	{
		Adjustdown(a, n, i);
	}
	//先选出最大的或最小的,交换到数组尾部,向下调整后再选出次大或次小的再次交换到尾部位置的前一个位置
	for (int i = 1; i < n; ++i)
	{
		swap(&a[0], &a[n - i]);
		Adjustdown(a, n - i, 0);
	}
}

  5.2 TOP-K problem

        TOP-K problem: Find the top K largest elements or smallest elements in the data combination. Generally, the amount of data is relatively large . For example: the top 10 professional players, the world's top 500 , the rich list, the top 100 active players in the game , etc.
        For the Top-K problem, the most simple and direct way that can be thought of is sorting, but: if the amount of data is very large, sorting is not advisable ( it may not be possible to load all the data into memory at once) . The best way is to use the heap to solve it. The basic idea is as follows:
1. Use the first K elements in the data set to build a heap
For the first k largest elements, build a small heap,
For the first k smallest elements, build a large heap.
2. Use the remaining NK elements to compare with the top elements in turn, and replace the top elements if they are not satisfied
After comparing the remaining NK elements with the top elements of the heap in turn, the remaining K elements in the heap are the first K smallest or largest elements sought .
        Take K as an example, assuming that n is large and k is small, read data from the file, first insert the first k data in the file into the array, and then build a small heap. After the heap is built, read the data sequentially, and compare each one with the data at the top of the heap. If it is larger than the data at the top of the heap, replace it, and then adjust downward until the n data are read. At this time The data in the team is the first K sparring elements, but the data in the heap cannot be guaranteed to be in order. It can be sorted after selection, and the efficiency will be much higher.
//找n个数里最大或最小的前K个
void PrintTopK(int* a, int n, int k)
{
	const char* filename = "test1.txt";
	FILE* fout = fopen(filename, "r");
	if (fout == NULL)
	{
		perror("fopen fail");
		return;
	}
	
	
	for(int i = 0;i<k;++i)
	{
		fscanf(fout, "%d", &a[i]);
		
	}
	for (int i = (k - 2) / 2; i >= 0; --i)
	{
		Adjustdown(a, k, i);
	}
	int ret = 0;
	while (fscanf(fout, "%d", &ret) != EOF)
	{
		if (ret > a[0])
		{
			a[0] = ret;
			Adjustdown(a, k, 0);
		}
	}

	//free(a);
	fclose(fout);
}
//往文件里写数据
void WriteData(const char* filename, int n)
{
	FILE* fin = fopen(filename, "w");
	if (fin == NULL)
	{
		perror("fopen fail");
		return;
	}
	srand(time(NULL));

	while (n--)
	{
		fprintf(fin, "%d ", rand());
	}


	fclose(fin);
}

  6.  Implementation of binary tree chain structure

   6.1 Structure of Binary Tree

Here is a review of the concept of a binary tree:

Binary tree is: 1. Empty tree , 2. Non-empty: root node, left subtree of root node, right subtree of 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.

//符号和结构的定义
typedef char BTDataType;
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

   6.2 Binary tree traversal

There are four types of binary tree traversals: preorder, inorder, postorder, and layer order.

        Binary tree structure, the easiest way is to traverse. Binary tree traversal is to perform corresponding operations on the nodes in the binary tree , and each node is only operated once . The operations performed by the access nodes depend on the specific application problem. Traversal is one of the most important operations on a binary tree, and it is also the basis for other operations on a binary tree.
The order of each traversal of the binary tree:
Preorder: root, left subtree, right subtree;
Inorder: left subtree, root, right subtree;
Postorder: left subtree, right subtree, root;
Layer sequence: first layer, second layer, Nth layer, (from left to right)
Here, the layer-order traversal will be discussed separately later. The front, middle, and back-order traversals are very similar, so I will only explain the pre-order traversal.
Recursive diagram of preorder traversal:
First visit the root node 1, continue to go down if it is not empty, and then visit its left subtree. When visiting its left subtree, the function will be called again, and a layer of function stack frame will be added. At this time, the node stack frame of 2 Among them, 2 is the left subtree of 1 and also the root, if it is not empty, output 2, continue to go down, to the stack frame of node 3, if it is not empty, output 3, enter the left subtree of 3, return to the top if it is empty One layer (that is, the function stack frame of node 3), the left of 3 is empty and returns, enter the right of 3, the right of 3 is also empty, and then return. At this time, the function stack frame of node 3 returns after running (it returns from the left of 2), the left of 2 returns, enters the right of 2, the right of 2 is empty and returns empty, and the function stack frame of 2 returns after running, at this time After running the left subtree of 1, enter the right subtree of 1, and continue to go down with the call just now, until the function stack frame of the root node of 1 finishes running, and after returning, the preorder traversal is completed.

// 二叉树前序遍历
void BTPrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->_Data);
	BTPrevOrder(root->_Left);
	BTPrevOrder(root->_Right);
}
// 二叉树中序遍历
void BTInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BTPrevOrder(root->_Left);
	printf("%d ", root->_Data);
	BTPrevOrder(root->_Right);
}
// 二叉树后序遍历
void BTPostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BTPrevOrder(root->_Left);
	BTPrevOrder(root->_Right);
	printf("%d ", root->_Data);
}

 6.2 Building a binary tree

        The data of the nodes here uses characters, and the numbers are the same, just change the custom type. Use preorder traversal to build a binary tree, use # to represent NULL, and assign the contents of the array a to each node in turn.

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)++;
		return NULL;
	}
	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	if (root == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	
	root->_Data = a[(*pi)++];
	root->_Left = BinaryTreeCreate(a, pi);
	root->_Right = BinaryTreeCreate(a, pi);

	return root;
}

  6.3 The number of nodes in a binary tree

// 二叉树节点个数
int BTSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	return BTSize(root->_Left) + BTSize(root->_Right) + 1;
}
// 二叉树叶子节点个数
int BTLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	int leSize = BTLeafSize(root->_Left);
	int riSize = BTLeafSize(root->_Right);

	return leSize > riSize ? leSize + 1 : riSize + 1;
}
// 二叉树第k层节点个数
int BTLevelKSize(BTNode* 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);
}

  6.4 Finding Nodes

        If it is found here, just return to the current node layer by layer. If the left subtree cannot be found, the right subtree cannot be found and needs to return empty.

// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->_Data == x)
	{
		return root;
	}
	BTNode* lret = BinaryTreeFind(root->_Left, x);
	if (lret != NULL)
		return lret;
	BTNode* rret = BinaryTreeFind(root->_Right, x);
	if (rret != NULL)
		return rret;
	return NULL;
}

  6.5 Destroy the binary tree

// 二叉树销毁
void BinaryTreeDestory(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreeDestory(root->_Left);
	BinaryTreeDestory(root->_Right);
	free(root);

}

  6.6 Level order traversal

        Queues are needed here, so you need to refer to the previous code.

// 层序遍历
void BTLevelOrder(BTNode* root)
{
	Queue q;
	QueueInit(&q);//队列初始化
	if (root)
		QueuePush(&q, root);//入队列
	while (!QueueEmpty(&q))
	{
		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");

	QueueDestory(&q);//销毁队列
}

The code using the queue here needs to pay attention to putting the structure of the binary tree in the header file of the queue, otherwise the compilation report will not miss it.

   6.7 Determine whether a binary tree is a complete binary tree

        It is to traverse the binary tree with the method of layer-order traversal, enter the root first, and leave the queue if the root is not empty. At the same time, let its left and right subtrees also enter the queue, and traverse until the first empty one is encountered, then it must be followed by empty, otherwise it is not a complete binary tree.

// 判断二叉树是否是完全二叉树
int BTComplete(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 != NULL)
		{
			QueueDestory(&q);
			return false;
		}
	}
	QueueDestory(&q);
	return true;
}

Complete code: Binary/Binary The evening wind is not as good as your smile/homework library- Code Cloud- Open Source China (gitee.com)

Guess you like

Origin blog.csdn.net/weixin_68993573/article/details/128518386