On the operation you can't do with a binary tree -- a super detailed explanation of chain binary tree

1, Foreword

This chapter will explain:

The concept of binary tree and the implementation of various interfaces

Note: Here we will not learn the addition, deletion, query and modification of binary tree as before. Binary tree is not a linear structure and is not suitable for storing data, that is, addition, deletion, query and modification are not suitable for it. Here we mainly expand on its own nature

2, Binary tree

1. Binary tree concept

A binary tree is a tree whose degree is not greater than two. It may have left subtree, right subtree, or empty tree

  • Any binary tree is composed of the following situations:

2. Chain storage

  • Concept:

A binary tree is represented by a linked list, that is, a chain is used to indicate the logical relationship of elements

  • Common methods:

Each node in the linked list consists 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

  • Chain structure classification:

Chain structure is divided into binary chain and trigeminal chain. At present, we are talking about binary chain. Trigeminal chain will be used when learning high-order data structures such as red black tree

  • Illustration:

3, Implementation of chain binary tree

1. Interface display

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;//data
	struct BinaryTreeNode* left;//Left child node address
	struct BinaryTreeNode* right;//Right child node address
}BTNode;

// Binary tree destruction
void BinaryTreeDestory(BTNode** root);
// Number of binary tree nodes
int BinaryTreeSize(BTNode* root);
// Number of leaf nodes of binary tree
int BinaryTreeLeafSize(BTNode* root);
// Number of nodes in the k-th layer of binary tree
int BinaryTreeLevelKSize(BTNode* root, int k);
// The binary tree looks for a node with a value of x
BTNode* BinaryTreeFind(BTNode* root, BTDataType x);
// Preorder traversal of binary tree 
void BinaryTreePrevOrder(BTNode* root);
// Order traversal in binary tree
void BinaryTreeInOrder(BTNode* root);
// Postorder traversal of binary tree
void BinaryTreePostOrder(BTNode* root);
// level traversal 
void BinaryTreeLevelOrder(BTNode* root);
// Judge whether the binary tree is a complete binary tree
int BinaryTreeComplete(BTNode* root);
//Depth of binary tree
int BinaryTreeDepth(BTNode* root);

2. Node type creation

Note: Here we mainly study binary linked list

The binary linked list has a data field and a 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 the right child of the node are located respectively

  • Reference code:
typedef char BTDataType;//data type

typedef struct BinaryTreeNode
{
	BTDataType data;//data
	struct BinaryTreeNode* left;//Left child node address
	struct BinaryTreeNode* right;//Right child node address
}BTNode;

Note: because the characteristics of the binary tree structure (the root node of the subtree has and only has one precursor, and can have at most two successors) – are defined recursively, the following operations on the binary tree need to rely on recursion, which can be solved quickly

3. Build a tree quickly

Before learning the basic operations of a binary tree, you need to create a binary tree before you can learn its related basic operations

Since the binary tree structure is not well understood, in order to reduce the learning cost, manually and quickly create a simple binary tree, quickly enter the binary tree operation learning, and verify the correctness of the operation (learn the real creation method of binary tree after in-depth understanding)

  • The diagram corresponds to the logical structure of binary tree:

Note: # represents NULL, i.e. empty tree

//Development node
BTNode* BuyNode(BTDataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	if (node == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	node->data = x;
	node->left = node->right = NULL;
	return node;
}

BTNode* CreatBinaryTree()
{
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}

4. Binary tree traversal

  • Concept:

Binary tree traversal is to operate the nodes in the binary tree in turn according to a specific rule, and each node operates only once

Note: the operation of the access node depends on the specific application problem. Traversal is one of the most important operations on the binary tree and the basis of other operations on the binary tree

  • Traversal classification:

The traversal of binary tree has a recursive structure of preorder / inorder / postorder:

  • Preorder Traversal (also known as Preorder Traversal) - first visit the root node, then traverse its left subtree, and then traverse its right subtree
  • Inorder traversal -- first access the left subtree, then traverse its root node, and then traverse the right subtree
  • Postorder traversal -- first access the left subtree, then traverse the right subtree, and then access the root node

be careful:

  1. The access mode is determined according to the access order of the root node
  2. The implementation logic of various traversals is basically the same, but the order is different

1) Preorder traversal

  • Reference code:
// Preorder traversal of binary tree (left and right roots)
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	BinaryTreePrevOrder(root->left);//Preamble recursive call
	BinaryTreePrevOrder(root->right);
}
  • Pre sequence traversal diagram:

2) Medium order traversal

  • Reference code:
// Order traversal in binary tree (left root right)
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreeInOrder(root->left);//Medium order recursive call
	printf("%c ", root->data);
	BinaryTreeInOrder(root->right);
}

Note: the recursive process diagram can be drawn and solved by yourself

3) Postorder traversal

  • Reference code:
// Postorder traversal of binary tree (left and right roots)
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	BinaryTreePostOrder(root->left);//Sequential recursive call
	BinaryTreePostOrder(root->right);
	printf("%c ", root->data);
}

Note: the recursive process diagram can be drawn and solved by yourself

4) Sequence traversal

  • Concept:

In addition to sequence traversal, there is sequence traversal
Let the number of layers of the root node of the binary tree be 1. Sequence traversal starts from the root node of the binary tree, first visit the root node of the first layer, then visit the nodes on the second layer from left to right, and then the nodes on the third layer
By analogy, the process of accessing the nodes of the tree layer by layer from top to bottom and from left to right is sequence traversal

  • Logic diagram:

  • Traversal mode:
  1. We need to use our queue to solve this problem
  2. First put the address of the root node into the queue, then put the left and right child nodes of the root node into the queue (if it is not NULL), and then take the root node out of the queue
  3. If the queue is not empty, access the current queue header, queue the left and right subtrees of the queue header (if not NULL), and then give the current queue header data to the queue
  4. Until the queue is empty, the sequence traversal is completed
  • Traversal diagram:
  • be careful:
  1. For C language, we need to implement queue first
  2. The data type of the queue is binary tree node address

Note: we need to make a pre declaration for non built-in types, otherwise the compiler will act as if it does not know the type and report an error

  • Diagram:
  • Reference code:
// level traversal 
void BinaryTreeLevelOrder(BTNode* root)
{
	if (root == NULL)
		return;
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		struct BinaryTreeNode* front = QueueFront(&q);//Save node address
		printf("%c ", front->data);
		QueuePop(&q);//Out of queue
		if (front->left)//Bring in child node
			QueuePush(&q, front->left);
		if (front->right)
			QueuePush(&q, front->right);
	}
	QueueDestroy(&q);
}

5) Traversal test

  • Test code:
int main()
{
	BTNode* root = CreatBinaryTree();

	BinaryTreePrevOrder(root);
	printf("\n");
	BinaryTreeInOrder(root);
	printf("\n");
	BinaryTreePrevOrder(root);
	printf("\n");
	BinaryTreeLevelOrder(root);
    printf("\n");
	BinaryTreeDestory(&root);
	return 0;
}
  • Result diagram:

5. Determine whether it is a complete binary tree

  • be careful:
  1. This interface also needs to be implemented using queues
  2. The definition of a complete binary tree is that only a left subtree can exist a right subtree (Note: my understanding). Here, we also traverse the binary tree in sequence. The only difference is that the empty tree is also queued
  3. When the outgoing list is NULL, check the subsequent queue data. If it is mostly NULL, it is a complete binary tree, otherwise it is not
  • Graphic process:
  • Reference code:
// Judge whether the binary tree is a complete binary tree
int BinaryTreeComplete(BTNode* root)
{
	assert(root);
	Queue q;
	QueueInit(&q);
	QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		struct BinaryTreeNode* front = QueueFront(&q);//Save node address
		QueuePop(&q);//Out of queue
		if (front == NULL)//If a null pointer is encountered out of the queue, exit the loop directly
			break;
		else//The child node can only be brought in if it is not a null pointer, otherwise an error will be reported when accessing a null pointer
		{
			QueuePush(&q, front->left);
			QueuePush(&q, front->right);
		}
	}
	//If an empty node is encountered, it is a complete binary tree if the rest are NULL
	//Otherwise, it is an incomplete binary tree
	while (!QueueEmpty(&q))
	{
		if (QueueFront(&q))
		{
			QueueDestroy(&q);//Destroy before returning
			return 0;//0 means false
		}
		QueuePop(&q);
	}
	QueueDestroy(&q);//Destroy before returning
	return 1;//Non-zero means true
}

6. Binary tree destruction

  • be careful:
  1. Nodes are opened dynamically again and again, and they also need to be released again and again
  2. When destroying the current node, first destroy and empty the child node to avoid wild pointers
  3. For destruction, we also want to destroy and empty the root node of the binary tree, and the root node itself is an address. Here, we need to pass in the secondary pointer to modify its pointing object
  • Reference code:
// Binary tree destruction
void BinaryTreeDestory(BTNode** root)//Root itself is the address. You can modify root only by passing in the secondary pointer
{
	if (root == NULL)//Empty tree
		return;
	BinaryTreeDestory(&(*root)->left);//Recursive release node
	BinaryTreeDestory(&(*root)->right);
	free(*root);
	*root = NULL;//Release and empty
}
  • Recursive expansion:

Note: drawing recursive graphs is a good way to understand recursive operations

7. Number of binary tree nodes

  • be careful:
  1. Empty tree does not count
  2. If the tree is not empty, count 1, and recursively obtain the number of left and right subtree nodes
  • Abstract thought:

Number of binary tree nodes = = current root node + number of left and right subtree nodes

Note: if you have problems with the idea of recursion, you can draw a recursion expansion diagram for easy understanding. The following interface implementations are the same

  • Reference code:
// Number of binary tree nodes
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)//Empty trees do not count
		return 0;

	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right) + 1;//Recursive count
}

8. Number of leaf nodes of binary tree

  • be careful:
  1. The characteristic of leaf node is that the left and right subtrees are empty trees
  2. If the current node is an empty tree, the count is 0
  3. If the left and right subtrees of the current node are empty, count 1
  4. If it is not a leaf node, continue recursive counting
  • Abstract thought:

Number of leaf nodes = = number of leaf nodes of left and right subtrees (if the current node is not a leaf node)

  • Reference code:
// Number of leaf nodes of binary tree
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)//Empty tree
		return 0;
	if (root->left == NULL && root->right == NULL)//It is not an empty tree and has no left and right subtrees. It is a leaf node
		return 1;
	return BinaryTreeLeafSize(root->left) + BinaryTreeLeafSize(root->right);//If it is not a leaf node, continue recursion
}

9. Number of nodes in layer K of binary tree

  • be careful:
  1. For how to control the recursion depth, we use k to control the number of recursion layers
  2. First, judge the rationality of k
  • Abstract thought:

Layer k node = = number of layer k nodes of left and right subtrees (if the current node is not layer k)

  • Reference code:
// Number of nodes in the k-th layer of binary tree
int BinaryTreeLevelKSize(BTNode* root, int k)//k is used to control the number of recursions
{
	assert(k >= 1);//Assertion k must be greater than 0
	if (root == NULL)//Empty tree
		return 0;
	if (k == 1)//Is the number of layers
		return 1;
	return BinaryTreeLevelKSize(root->left,k-1) + BinaryTreeLevelKSize(root->right,k-1);//Recursion if not
}

10. The binary tree looks for a node with a value of x

  • be careful:
  1. An empty tree is not found
  2. If it is not an empty tree, further check whether the value is x
  3. If not, recursively search the left and right subtrees
  4. If it is still not found, NULL is returned
  • Reference code:
// The binary tree looks for a node with a value of x
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->data == x)
		return root;
	BTNode* left = BinaryTreeFind(root->left, x);//Find in the left and right subtrees in turn
	if (left != NULL)//eureka
		return left;

	BTNode* right = BinaryTreeFind(root->right, x);
	if (right != NULL)
		return right;

	return NULL;
	//return BinaryTreeFind(root->right, x);
}

11. Depth of binary tree

  • be careful:
  1. If the tree is empty, the current layer does not exist
  2. If it is not an empty tree, the current layer exists
  • Abstract thought:

The current number of layers plus the maximum number of layers of the left and right subtrees is the depth of the binary tree

  • Reference code:
//Depth of binary tree
int BinaryTreeDepth(BTNode* root)
{
	if (root == NULL)
		return 0;

	int left = BinaryTreeDepth(root->left);//Find and compare the left and right subtree depths
	int right = BinaryTreeDepth(root->right);

	return left > right ? left + 1 : right + 1;
}

4, Testing

  • Test the corresponding binary tree:
  • Test code:
BTNode* CreatBinaryTree()
{
	BTNode* nodeA = BuyNode('A');
	BTNode* nodeB = BuyNode('B');
	BTNode* nodeC = BuyNode('C');
	BTNode* nodeD = BuyNode('D');
	BTNode* nodeE = BuyNode('E');
	BTNode* nodeF = BuyNode('F');
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;

	return nodeA;
}
int main()
{
	BTNode* root = CreatBinaryTree();
	printf("TreeSize:%d\n", BinaryTreeSize(root));
	printf("TreeLeafSize:%d\n", BinaryTreeLeafSize(root));
	printf("TreeLevelKSize:%d\n", BinaryTreeLevelKSize(root, 3));
	printf("BinaryTreeFind:%p\n", BinaryTreeFind(root, 'F'));
	printf("BinaryTreeDepth:%d\n", BinaryTreeDepth(root));
	printf("BinaryTreeComplete:%d\n", BinaryTreeComplete(root));
	BinaryTreeDestory(root);
	root = NULL;
	return 0;
}
  • Result diagram:

If you have questions, you can leave a message. If you can, you are welcome to support the third company!

Tags: data structure

Posted on Wed, 24 Nov 2021 20:01:56 -0500 by Mattyspatty