"Data Structure Cultivation Manual"----The Implementation of Chained Binary Tree

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:

image-20220409151527822

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:

  1. empty tree
  2. Non-empty: the root node, the left subtree of the root node, and the right subtree of the root node.

image-20220409155057833

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.

image-20220409155849494

image-20220409160040183

According to the rules, the traversal of the binary tree includes: preorder/inorder/postorder recursive structure traversal :

  1. 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:

    image-20220409161302347

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

    image-20220409165050924

  3. 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:

    image-20220409171418202

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

image-20220409200219445

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:

image-20220409210540857

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:

  1. First put the root into the queue, with the help of the first-in-first-out nature of the queue
  2. When the node of the previous layer goes out, bring the left and right child nodes of the next layer in

Icon:

image-20220415154604840

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:

image-20220415152436909

Modifications in Test.c file

image-20220415152615070

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:

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

image-20220415170651476

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:

image-20220415172625693

image-20220415172901775

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:

image-20220410105756117

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:

  1. Empty tree, returns 0
  2. non-null, returns 1
  3. 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:

image-20220410135435235

image-20220410141749616

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

  1. Check if the current root node is empty
  2. Determine whether the current node is the value we are looking for
  3. Determine if the node we are looking for exists in the left subtree
  4. Determine whether the node we are looking for exists in the right subtree
  5. 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;
}

Guess you like

Origin blog.csdn.net/m0_57304511/article/details/124342419