Implementation of Chained Binary Tree
- 1. Preliminary Instructions
- 2. Traversal of binary tree
- 3. Number of nodes and height, etc.
1. Preliminary Instructions
Why build a chained binary tree?
In order to store a non-complete binary tree structure, that is, an irregular binary tree structure, intermediate nodes may not store elements. E.g:
Note: It is meaningless to add, delete, check, and modify a common binary tree. If it is just to store data, it is better to use the structure of the binary tree of the sequence table. ?
Q: So why learn chained binary trees?
Answer: In order to be able to better control its structure, lay the foundation for the subsequent learning of more complex search binary trees. In addition, many binary tree OJ problems are on ordinary binary trees.
Simply create a chained binary tree:
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType data;//节点存储的数据
struct BinaryTreeNode* left;//存储左子节点的地址
struct BinaryTreeNode* right;//存储右子节点的地址
}BTNode;
BTNode* BuyNode(BTDataType x)//开辟一个新节点
{
BTNode* newnode = (BTNode*)malloc(sizeof(BTNode));
assert(newnode);
newnode->data = x;
newnode->left = NULL;
newnode->right = NULL;
return newnode;
}
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->left = node2;
node1->right = node4;
node2->left = node3;
node4->left = node5;
node4->right = node6;
return node1;
}
Note: The above code is not the way to create a binary tree, the real way to create a binary tree is explained in detail after the sequence.
Before looking at the basic operations of a binary tree, let's review the concept of a binary tree. A binary tree is:
- empty tree
- Non-empty: the root node, the left subtree of the root node, and the right subtree of the root node.
As can be seen from the concept, the definition of a binary tree is recursive, so the basic operations of the post-order are basically implemented according to this concept.
2. Traversal of binary tree
2.1 Preorder, inorder and postorder traversal (depth-first traversal (DFS))
Binary tree traversal (Traversal) is to perform corresponding operations on the nodes in the binary tree in turn according to a certain rule, and each node is only operated once . The operations performed by the access node 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.
According to the rules, the traversal of the binary tree includes: preorder/inorder/postorder recursive structure traversal :
-
Preorder traversal (also known as pre-root traversal) - The operation of visiting the root node occurs before traversing its left and right subtrees. That is, the access order is: root node - left subtree - right subtree
Taking the above binary tree diagram as an example, the preorder traversal is shown in the figure:
-
In-order traversal (also known as mid-root traversal)—Access to the root node occurs during traversal (middle) of its left and right subtrees. That is, the access order is: left subtree - root node - right subtree
The in-order traversal is shown in the figure:
-
Post-order traversal (also known as post-root traversal)—Access to the root occurs after traversing its left and right subtrees. That is, the access order is: left subtree - right subtree - root node
The post-order traversal is shown in the figure:
2.2 Implementation of three traversals
2.2.1 Implementation of preorder traversal
Code:
void PreOrder(BTNode*root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
printf("%d ", root->data);//遍历根节点
PreOrder(root->left);//遍历左子树节点
PreOrder(root->right);//遍历右子树节点
}
Illustration: The numbers in ( ) are the code execution order
2.2.2 Implementation of in-order traversal
Code:
void InOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
printf("%d ", root->data);
InOrder(root->right);
}
Icon:
2.2.3 Implementation of post-order traversal
Code:
void PostOrder(BTNode* root)
{
if (root == NULL)
{
printf("NULL ");
return;
}
InOrder(root->left);
InOrder(root->right);
printf("%d ", root->data);
}
2.3 Level-order traversal (breadth-first traversal (BFS))
Layer order traversal - traverse directly layer by layer, starting from the head node and traversing backward one by one. The traversal order in the above figure is: 1-2-4-3-5-6
Ideas:
- First put the root into the queue, with the help of the first-in-first-out nature of the queue
- When the node of the previous layer goes out, bring the left and right child nodes of the next layer in
Icon:
accomplish:
Note: Use the two files Queue.c and Queue.h, and define the data type stored in the queue as BinaryTreeNode*, pay attention to the inclusion of the header file and the declaration of the structure in the Queue file!
Modifications in the Queue.h file:
Modifications in Test.c file
Code:
void LevelOrder(BTNode* root)
{
Queue q;//队列的创建
QueueInit(&q);
if (root)//判断是否为空二叉树
{
QueuePush(&q, root);//将root节点push到队列中
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);//拿到队列首元素
QueuePop(&q);//将出队列的节点从队列中删掉
if (front->left)
{
QueuePush(&q, front->left);//将左子节点push到队列中
}
if (front->right)
{
QueuePush(&q, front->right);//将右子节点push到队列中
}
printf("%d ", front->data);//打印出队列的数据
}
//对列的销毁
QueueDestory(&q);
}
2.4 The application of level-order traversal----judgment of complete binary tree
Ideas:
- Level order traversal, empty nodes are also queued.Note: Before entering the queue, check whether the front is empty, otherwise there will be an error of dereferencing a null pointer.
- After exiting to an empty node, all the data in the exit queue, if all are empty, is a complete binary tree, if there is non-empty, it is not.
Icon:
Code:
//判断一个二叉树是否是完全二叉树
bool BTreeComplete(BTNode* root)
{
Queue q;//队列的创建
QueueInit(&q);
if (root)//判断是否为空二叉树
QueuePush(&q, root);//将root节点push到队列中
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
QueuePop(&q);
if (front == NULL)//出到空的时候退出,后面再进行判断
{
break;
}
//为什么要放到后面push呢?为了防止对空指针进行解引用
QueuePush(&q, front->left);//此时front一定不为空
QueuePush(&q, front->right);
}
while (!QueueEmpty(&q))
{
BTNode* front = QueueFront(&q);
//空后面出现非空,就说明不是完全二叉树
if (front)
{
return false;
}
QueuePop(&q);
}
//队列的销毁
QueueDestory(&q);
return true;
}
Note : Consider the following situation:
3. Number of nodes and height, etc.
3.1 Number of binary tree nodes
Two methods:
method one
Idea: Traverse + Count
Code:
int count = 0;
void BTreeSize(BTNode* root)
{
if (root == NULL)
{
return;
}
count++;
BTreeSize(root->left);
BTreeSize(root->right);
}
Of course, local static variables can also be used to store the number of nodes, but it is not recommended. The code is as follows:
int BTreeSize(BTNode* root)
{
static int count = 0;//只会在第一次进入时初始化一次
if (root == NULL)
{
return count;
}
count++;
BTreeSize(root->left);
BTreeSize(root->right);
return count;
}
Of course, neither of the above two methods is good, and neither of them takes full advantage of the structural properties of the recursive definition of chained binary trees, so the above two methods are not recommended. The second of the above two methods is particularly bad, especially when the number of elements is calculated multiple times. If the count is not re-initialized in the second calculation, the value of the previous calculation will still be retained. If it is used Global variables can also be re-assigned after each count, but the second method cannot change the residual value after the last count at all.
There will be problems with global variables when multi-threaded. For example, there will be problems in multi-threaded situations: multiple threads use the global variable count at the same time, and thread safety problems will occur at this time.
Of course, you can also define a count variable in the calling function, and then pass in the address of count when calling, then the BTreeSize function must be defined like this:
int BTreeSize(BTNode* root,size_t *count)
{
static int count = 0;//只会在第一次进入时初始化一次
if (root == NULL)
{
return count;
}
(*count)++;
BTreeSize(root->left);
BTreeSize(root->right);
return count;
}
Method 2 (recommended)
(recursive method)
Idea: Subproblems
1. Empty tree, minimum scale sub-problem, the number of nodes returns 0
2. Non-empty, the number of left subtree nodes + the number of right subtree nodes + 1 (self)
Code:
int BTreeSize(BTNode* root)
{
return root==NULL ? 0 :
BTreeSize(root->left) +
BTreeSize(root->right) + 1;//1在最前面就是前序,在中间就是中序,在最后就是后序
}
This method makes full use of the structural properties of the recursive definition of the chained binary tree, because the essence of the chained binary tree is composed of the root node and two subtrees. To find the entire subtree is to find the number of nodes of the root node 1 plus the nodes of the left and right subtrees. number.
as the picture shows:
Note: The purple numbers in the figure represent the return value, and the black numbers represent the return value of the corresponding function.
The algorithm idea used :divide and conquer
Divide and conquer: Divide complex problems into smaller-scale sub-problems, and sub-problems are then divided into smaller-scale sub-problems...until the sub-problems can no longer be divided, and the results can be obtained directly.
3.2 Number of binary tree child nodes
method one
Ideas: (traversal + count)
It is basically the same as the above idea, except that a judgment condition of a leaf node is added in front of count++, so as to achieve the purpose of counting leaf nodes.
Code:
int count = 0;
void BTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL&&root->right==NULL)
{
count++;
}
BTreeLeafSize(root->left);
BTreeLeafSize(root->right);
}
Method Two
Ideas: Adopt the idea of divide and conquer.
Code:
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
return 0;
if (root->left == NULL&&root->right==NULL)
{
return 1;
}
else
return BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
Of course, the above code can be shortened to the following code:
int BTreeLeafSize(BTNode* root)
{
if (root == NULL)
{
return 0;
}
return root->left == NULL && root->right == NULL ?
1 + BTreeLeafSize(root->left)+ BTreeLeafSize(root->right) :
0 + BTreeLeafSize(root->left) + BTreeLeafSize(root->right);
}
However, relatively speaking, the unsimplified one is more recommended, because that one is easier to understand.
3.3 Number of nodes in the kth layer of a binary tree
Note: k>=1
Thought:
- Empty tree, returns 0
- non-null, returns 1
- non-empty, and k > 1, convert to seekThe number of nodes in the k-1 level of the left subtree + the number of nodes in the k-1 level of the right subtree。
int BTreeKLevelSize(BTNode* root,int k)
{
assert(k >= 1);
if (root == NULL||k<)
{
return 0;
}
if (k == 1)
{
return 1;
}
return BTreeKLevelSize(root->left, k - 1) +
BTreeKLevelSize(root->left, k - 1);
}
Icon:
3.4 Depth of binary tree
Ideas: The idea of divide and conquer
The height of the binary tree = the height of the left subtree and the height of the right subtree, the larger one + 1.
int BTreeDepth(BTNode* root)
{
if (root == NULL)
{
return 0;
}
int leftDepth = BTreeDepth(root->left);//左子树的深度
int rightDepth = BTreeDepth(root->right);//y
return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;//1是因为根节点本身也算作是一层
}
3.5 Binary tree to find node with value x
Ideas:divide and conquer Binary tree = root node + left subtree + right subtree
- Check if the current root node is empty
- Determine whether the current node is the value we are looking for
- Determine if the node we are looking for exists in the left subtree
- Determine whether the node we are looking for exists in the right subtree
- The node we are looking for does not exist in the current binary tree
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
//节点为空的情况:直接返回NULL
if (root == NULL)
{
return NULL;
}
//当前节点就是我们要查找的节点:返回当前节点的地址
if (root->data == x)
{
return root;
}
//左子树
BTNode* leftRet = BinaryTreeFind(root->left, x);
if (leftRet)
{
return leftRet;
}
//右子树
BTNode* rightRet = BinaryTreeFind(root->right, x);
if (rightRet)
{
return rightRet;
}
//都找不到的情况下返回NULL,就是说当前二叉树中不存在存储该值的节点
return NULL;
}