[binary tree of the initial level of data structure]: properties related to binary tree and classic exercises (implemented in C language and explained in detail with the attached drawings)

1, Concept and structure of tree

1. Concept of tree

What is a tree in a data structure? We have the following definitions and properties:
Definition: tree is a nonlinear data structure. It is a set with hierarchical relationship composed of n (n > = 0) finite nodes. It is called a tree because it looks like an upside down tree, that is, it has roots up and leaves down.
nature:
1. There is a special node, called the root node, which has no precursor node.
2. Except for the root node, the other nodes are divided into m (M > 0) disjoint sets T1, T2,..., Tm, and each set Ti (1 < = I < = m) is a subtree with a structure similar to the tree. The root node of each subtree has only one precursor, and can have 0 or more successor nodes.
3. Therefore, the tree is recursively defined.
Note: in the tree structure, there must be no intersection between subtrees, otherwise it is not a tree structure

2. Related concepts in the tree

  • Node degree: the number of subtrees contained in a node is called the degree of the node; As shown in the figure above, the degree of node B is 3.
  • Leaf node or terminal node: a node with a degree of 0 is called a leaf node; As shown in the figure above, K, J, F, L, O and P nodes are leaf nodes.
  • Parent node or parent node: if A node contains child nodes, this node is called the parent node of its child nodes; As shown in the figure above, A is the parent node of B.
  • 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 in the figure above: B is the child node of A.
  • Sibling node: nodes with the same parent node are called sibling nodes; As shown in the figure above, B and C are sibling nodes.
  • Tree degree: the degree of the largest node in a tree is called the degree of the tree; As shown in the figure above: the degree of the tree is 3.
  • Node hierarchy: defined from the root, the root is the first layer, and the child nodes of the root are the second layer, and so on.
  • Height or depth of tree: the maximum level of nodes in the tree; As shown in the figure above: the height of the tree is 5
  • Cousin node: nodes with parents on the same layer are cousins to each other; As shown in the figure above, M and N are brother nodes to each other.
  • Ancestor of a node: all nodes from the root to the branch through which the node passes; As shown in the figure above: A is the ancestor of all nodes.
  • Descendant: any node in the subtree with a node as the root is called the descendant of the node. As shown in the figure above: all nodes are descendants of A.

3. Representation of tree

The tree structure is more complex than the linear table, and it is more troublesome to store and express. Since the value range is saved, the relationship between nodes should also be saved. In fact, there are many kinds of tree representations, such as parent representation, child representation, child parent representation and child brother representation.
Let's briefly understand the most commonly used child brother expression:

typedef int DataType;
struct Node
{
struct Node* leftChild1; // first child node 
struct Node* rightBrother; // Point to its next sibling node
DataType data; // Data fields in nodes
};

2, Concept and structure of binary tree

1. Concept of binary tree

Binary tree is a set of n finite elements. The set is either empty, or composed of an element called root and two disjoint binary trees called left subtree and right subtree respectively. It is an ordered tree.
When the set is empty, the binary tree is called an empty binary tree. In a binary tree, an element is also called a node.

According to the above figure, it can be analyzed that:

  1. Binary tree does not have nodes with degree greater than 2
  2. Bit technology 2. The subtree of a binary tree can be divided into left and right, and the order cannot be reversed. Therefore, a binary tree is an ordered tree
  3. For any binary tree, it is composed of the following situations: empty tree, only root node, only left subtree, only right subtree, and both left and right subtrees exist.

2. Special binary tree

  • Full binary tree: a binary tree. If the number of nodes in each layer reaches the maximum, the binary tree is a full binary tree. In other words, if the number of layers of a binary tree is K and the total number of nodes is, it is a full binary tree.
  • Complete binary tree: a complete binary tree is a highly efficient data structure. A complete binary tree is derived from a full binary tree. A binary tree with depth K and n nodes is called a complete binary tree if and only if each node corresponds to the nodes numbered from 1 to n in the full binary tree with depth K. It should be noted that full binary tree is a special complete binary tree.

3. Properties of binary tree

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

4. Storage structure of binary tree

Binary tree can be stored in two structures: a sequential structure and a chain structure.

  • Sequential storage:
    Sequential structure storage is to use arrays for storage. Generally, arrays are only suitable for representing complete binary trees, because there will be a waste of space if they are not complete binary trees. In reality, only the heap will use array to store, so the sequential storage of binary tree is an array physically and a binary tree logically.
    The heap is analyzed in detail in this blog. Click jump – > [detailed analysis of reactor]

  • Linked Storage
    The chain storage structure of binary tree refers to that a binary tree is represented by a linked list, that is, a chain 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, data field and left and right pointer field. The left and right pointers are used to give the storage address of the chain node where the left child and right child of the node are located respectively. Next, we mainly use this method to learn the binary tree

3, Implementation of binary tree chain structure

1. Creation of binary tree

Before learning the basic operations of a binary tree, you need to create a binary tree before you can learn its related basic operations; But using C language is very troublesome and difficult to understand, so we are going to create a simple tree manually to learn the essence of the two fork tree.
Like other data structures, to create a binary tree, you must first create a binary tree type data. The code is as follows:

typedef char BTDataType; //Rename the char type BTDataType

//Create a binary tree type
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;


The creation code is as follows:

typedef char BTDataType; //Rename the char type BTDataType

//Create a binary tree type
typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

//Open up a node function
BTNode* BuyNode(BTDataType x)
{
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	if (tmp == NULL)
	{
		perror("erron ");
		exit(-1);
	}
	tmp->data = x;
	tmp->left = NULL;
	tmp->right = NULL;
	return tmp;

}

//Function to create a tree
BTNode* CreatBinaryTree()
{
    //First, open up multiple nodes in turn
	BTNode* a = BuyNode('A');
	BTNode* b = BuyNode('B');
	BTNode* c = BuyNode('C');
	BTNode* d = BuyNode('D');
	BTNode* e = BuyNode('E');
	BTNode* f = BuyNode('F');
	BTNode* g = BuyNode('G');
    
    //Then connect the nodes into a tree
	a->left = b;
	a->right = c;
	b->left = d;
	c->left = e;
	c->right = f;
	
	return a;
}

int main()
{
    //Create a tree and use the variable root to receive the root of the tree
	BTNode* root = CreatBinaryTree();
}

The question is, what is the value of creating a binary tree? What do you want to study about binary trees? Let's slowly analyze the classical problems about binary trees.

Let's analyze the first, middle and last traversal of binary tree. The definition of first, middle and last traversal is as follows:

  1. Preorder Traversal (also known as Preorder Traversal) - the operation of accessing the root node occurs before traversing its left and right subtrees.
  2. Inorder traversal - the operation of accessing the root node occurs in traversing its left and right subtrees.
  3. Postorder traversal -- the operation of accessing the root node occurs after traversing its left and right subtrees.

The results of the first, middle and last traversal of the binary tree are analyzed as follows:

2. Preorder traversal of binary tree

We know the preorder traversal of this tree. How can we implement it in code?
The code is as follows:

//Preorder traversal
void PrevOrder(BTNode* root)
{
	if (root == NULL){
		printf("NULL ");
		return;
	}
	//Access the root node first
	printf("%c ", root->data);
	//Then visit the left and right subtrees
	PrevOrder(root->left);
	PrevOrder(root->right);
}

int main()
{
    //Manually connect nodes to create a tree
	BTNode* root = CreatBinaryTree();
	//Preorder traversal
	PrevOrder(root);
	printf("\n");
}

Maybe you can see that this is a recursion! How does it do preorder traversal? I can't see it. In fact, this is connected with the knowledge point of function stack frame in C language. If you don't know about function stack frame, you can take a look at my two blogs, which introduce recursion and function stack frame. Click to jump = = > [quick grasp of recursion] [creation and destruction of function stack frame]
Well, since you're a little confused, let's draw a recursive expansion diagram. The picture is as follows:

3. Middle order traversal of binary tree

The middle order traversal of this binary tree first accesses the left subtree, then the root node, and finally the right subtree
The code implementation is as follows:

//Medium order traversal
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	//First access the left subtree
	InOrder(root->left);
	//Ask root again
	printf("%c ", root->data);
	//Then access the right subtree
	InOrder(root->right);
}

int main()
{
    //Manually connect nodes to create a tree
	BTNode* root = CreatBinaryTree();
	//Medium order traversal
	InOrder(root);
	printf("\n");
}

This and preorder traversal are implemented by recursion. We may not be able to conceive all the paths of recursion in our mind. We still draw a recursion expansion diagram for analysis. The picture is as follows:

4. Post order traversal of binary tree

The post order traversal of binary tree is to visit the left subtree first, then the right subtree, and finally the root.
The code implementation is as follows:

//Postorder traversal
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	//First access the left subtree
	PostOrder(root->left);
	//Then access the right subtree
	PostOrder(root->right);
	//Ask root again
	printf("%c ", root->data);
}

int main()
{
    //Manually connect nodes to create a tree
	BTNode* root = CreatBinaryTree();
	//Postorder traversal
	PostOrder(root);
	printf("\n");
}

5. Destruction of binary tree

When destroying this binary tree, we should pay attention not to destroy the root first, and then the left and right subtrees, because if we release the root first, we can't find the address of the left and right subtrees. So we destroy the left and right subtrees first, and then the root, which is similar to post order traversal.
The code is as follows:

//Destruction of binary tree
void DestroyTree(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	//First release the left and right subtrees, and then release the roots
	DestroyTree(root->left);
	DestroyTree(root->right);
	free(root);
}

int main()
{
    //Manually connect nodes to create a tree
	BTNode* root = CreatBinaryTree();
	
    //Preorder traversal
	PrevOrder(root);
	printf("\n");
	//Medium order traversal
	InOrder(root);
	printf("\n");
	//Postorder traversal
	PostOrder(root);
	printf("\n");
	
    //Destroy binary tree
    DestroyTree(root);
}

The destruction of binary trees is similar to the post order traversal. Both the left and right subtrees are processed first, and then the roots are processed. If you feel that you don't understand enough, you can draw the recursive expansion diagram one by one according to the pre order and middle order traversal in front of me, which can greatly increase our understanding of recursion.

4, Node and height of binary tree

1. Find the number of binary tree nodes

To find the number of binary tree nodes, we still transform it into a subproblem to solve it. Find the total number of nodes of the binary tree, then I can first find the number of nodes of the left and right subtrees, and then add 1 to be the total number; The left and right subtrees can be subdivided into the left and right subtrees, recursion layer by layer, and then come back.
The code is as follows:

int  BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0 ;
	}
	//First find the trees of the left and right subtrees, and then add the root node, which is the total number of nodes
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;
}

int main()
{
    //Manually connect nodes to create a tree
	BTNode* root = CreatBinaryTree();
	
    //Count the number of nodes of binary tree
	int ret=BinaryTreeSize(root);
	printf("The number of binary tree nodes is%d\n", ret);
}

If we still feel uneasy and always feel something wrong, we can draw a recursive expansion diagram with the old method for analysis. The recursive expansion diagram is as follows:

2. Find the number of leaf nodes of binary tree

This is the number of leaf nodes, and the definition of leaf node is that the left and right subtrees are empty. Therefore, we still use the idea of recursive divide and conquer to count the number of leaf nodes of the left subtree plus the number of leaf nodes of the right subtree. In this way, the left and right subtrees recurse respectively, and finally return the total number of leaf nodes.
The code is as follows:

//Count the number of leaf nodes
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	//When the left and right subtrees are empty, they are leaves, and return 1
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	//Count the number of leaf nodes of the left and right subtrees respectively, and add up to the total number
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);
}

The recursive expansion diagram is as follows:

3. Find the number of nodes in the k-th layer of the binary tree

This is also to use the recursive divide and conquer idea to count the number of nodes in layer K. We first count the number of nodes in layer K - 1 and divide recursively until k equals 1. If the node is not empty, it is a node.
For example, to find the number of nodes in layer 4, we can convert it into the number of left subtree nodes in layer 3 + the number of right subtree nodes; The third layer can be transformed into the number of nodes of the left and right subtrees of the second layer, which has been recursive.
The code implementation is as follows:

// Number of nodes in the k-th layer of binary tree
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
	{
		return 0;
	}
	//When the node is not empty and k==1, the number of nodes is 1
	if (k == 1)
	{
		return 1;
	}
    //It is transformed into finding the number of left and right subtree nodes of layer k-1, and divide and conquer until NULL or k==1 is encountered
	return BinaryTreeLevelKSize(root->left, k - 1) 
		+ BinaryTreeLevelKSize(root->right, k - 1);
}

The recursive expansion diagram is as follows:

4. Find the height of binary tree

This still adopts the recursive divide and conquer idea. We first find the height of the left subtree, and then the height of the right subtree. Then compare the two. The larger plus 1 is the height of the binary tree; The left and right subtrees recurse to find the height, and finally we can get the height of the binary tree. The code implementation is as follows:

//Find the height of binary tree
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
	{
		return  0;
	}
	//First find the height of the left and right subtrees
	int leftDepth = BinaryTreeDepth(root->left);
	int rightDepth = BinaryTreeDepth(root->right);

	//Compare the height of the left and right subtrees. The greater plus 1 is the height of the tree
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

5. Find the node with the value of x in the binary tree

The title means to find a node with the same X value in the binary tree. If it is found, it will return the address of the node. If it is not found, it will return the NULL pointer NULL;
We still use divide and conquer to solve the problem. First find it in the left subtree. If it is found, return to the node address. If the left subtree is not found, go to the right subtree to find it. If it is found, return to the address. If the left and right subtrees are not found, the NULL pointer NULL is returned.
The code implementation is as follows:

//Find the node with the value of X in the binary tree
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return NULL;
	}
	//Once found, the node address is returned directly
	if (root->data == x)
	{
		return root;
	}
	//Look in the left subtree first
	BTNode* left = BinaryTreeFind(root->left,x);
	if (left != NULL)
	{
		return left;
	}
     //If you don't find it in the left subtree, look in the right subtree
	BTNode* right = BinaryTreeFind(root->right, x);
	if (right != NULL)
	{
		return right;
	}
	//If the left and right subtrees are not found, a null pointer is returned
	return NULL;
}

6. Sequence traversal of binary tree

We have learned the pre -, middle - and post order traversal of binary trees, and the last one is sequence traversal; As the name suggests, it is necessary to traverse layer by layer. We can solve it by using the queue we learned earlier.
The first step is to judge whether the binary tree is empty. If it is empty, it will return directly
The second step is to put the root of the binary tree into the queue, then take the element of the queue head, and then out of the queue
The third step is to judge whether the left and right subtrees of the queue head element are empty. If they are not empty, they will enter the queue, and if they are empty, they will not enter the queue
The fourth step is to judge whether the queue is empty. If it is not empty, continue to take the elements of the queue head, and then exit the queue. Cycle the third and fourth steps, so as to traverse layer by layer
The picture analysis is as follows:

Because there is no queue in C language, we need to manually implement a queue. We introduced and implemented the queue in detail in the previous blog. Click to jump = = > [simulation implementation of queue] , so you can directly reference it in the following code,
The code implementation is as follows:

void BinaryTreeOrder(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	Queue q;
	QueueInit(&q);
	//Root into queue
	QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		//Take the element of the team head
		BTNode* front = QueueFront(&q);
		printf("%c", front->data);
		QueuePop(&q);  //Out of queue
        
		if (front->left != NULL)
		{
			QueuePush(&q, front->left);
		}
		if (front->right != NULL)
		{
			QueuePush(&q, front->right);
		}
	}
	//Destroy queue
	QueueDestroy(&q);

}

7. Judge whether the binary tree is a complete binary tree

We know that the leaf nodes of the last layer of a complete binary tree must be continuous. If the leaf nodes are not continuous and there is a null pointer in the middle, they are not a complete binary tree; So we use this feature and queue solution,
First, we put the non empty root into the queue, then take the element of the queue head, and then out of the queue
Then we enter the queue regardless of whether the left and right subtrees of the elements of the team head are empty or not;
Then, keep taking the elements of the queue head. If you get the null pointer, you will not enter the queue again. Judge whether the remaining elements of the queue are null pointers. If they are all null pointers, it means that the leaf nodes are continuous and are a complete binary tree; If they are not all null pointers, it means that the leaf nodes are discontinuous and not a complete binary tree.

The code implementation is as follows:

//Judge whether the binary tree is a complete binary tree
bool BinaryTreeComplete(BTNode* root)
{
	if (root == NULL)
	{
		return false;
	}
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);

	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		//If the queue head element is not empty, it will be included in the left and right subtrees of the queue head element
		//Exit if the queue header element is empty
		if (front != NULL)
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
		else
		{
			break;
		}
	}

	//Determine whether the remaining elements of the queue are null pointers
	//All null pointers are complete binary trees
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		if (front != NULL)
		{
			QueueDestroy(&q);
			return false;
		}
		QueuePop(&q);
	}

	return true;
}

Tags: C Algorithm data structure leetcode linked list

Posted on Mon, 22 Nov 2021 21:04:50 -0500 by Zay