Data Structure Based on C Language_Binary Tree Study Notes

Table of contents

07 Trees and Binary Trees

07.1 Create a binary tree (query method)

07.2 Traversing a binary tree (first order, second order)

07.3 Hierarchical Traversal of Binary Trees

07.4 Statistical Binary Tree Depth

07.5 Count the number of leaves in the binary tree

07.6 Count the number of binary tree nodes

07.7 Create a binary tree with the filling method

07.8 Threaded binary tree

07.9 Traversing the thread binary tree

07.10 Operation thread binary tree


Earlier we learned the one-to-one linear relationship - stack queue linked list

This time, let's learn the one-to-many nonlinear relationship-tree

As for the characteristics, advantages and disadvantages of these data structures, some conceptual nouns, etc. Beginners don’t need to rush to read the summary in the book. Let’s talk about the logic from the reality first. After reading the code, you can learn to integrate naturally, and some concepts will be understood naturally.

07 Trees and Binary Trees

07.1 Create a binary tree (query method)

Look at the picture We call such a data structure a tree structure. There are two subtrees under the root tree A. They are the left subtree B and the right subtree C respectively, where under the left subtree B there are left and right subtrees DE, under the right subtree C there is a right subtree F...and so on, just like nesting dolls. Here is the idea of ​​function nesting .

So how to store data with this structure? It can be analyzed that the root tree and all subtree data structures should be the same! In addition to storing data, two pointers are needed to point to the addresses of the left and right subtrees, similar to the idea of ​​a linked list. So we use the structure data type to store these three members.

Code

#include <stdio.h>
#include <stdlib.h>

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

struct BinTree_node *create_bintree(void);

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();


	return 0;
}

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}

Combine the above code to create your binary tree structure. In the next section, we will talk about how to traverse your binary tree.

07.2 Traversing a binary tree (first order, second order)

The traversal of the binary tree uses the idea of ​​function recursion, which can simplify some cumbersome operation steps, such as the code implementation of the Tower of Hanoi.

Recursion means that the function calls itself. After the processing is completed, it returns to the position of the function call to continue execution. It is recommended to use the stack diagram to learn the essence of function calls.

1). Preorder traversal first
        visits the root node, then traverses the left subtree in preorder, and then traverses the right subtree in preorder

void pre_order(struct BinTree_node *tree)
{
	if(tree)
	{
		printf("%c", tree->elem);
		pre_order(tree->ltree);
		pre_order(tree->rtree);
	}
}

è¿éæå¥å¾çæè¿°

2). In-order traversal
        first traverses the left subtree in in-order, then visits the root node, and then traverses the right sub-tree in in-order

void in_order(struct BinTree_node *tree)
{
	if(tree)
	{
		in_order(tree->ltree);
		printf("%c", tree->elem);
		in_order(tree->rtree);
	}
}

è¿éæå¥å¾çæè¿°

 3). Subsequent traversal
        traverses the left subtree in sequence, then traverses the right subtree in sequence, and finally visits the root node

void pos_order(struct BinTree_node *tree)
{
	if(tree)
	{
		pos_order(tree->ltree);
		pos_order(tree->rtree);
		printf("%c", tree->elem);
	}
}

è¿éæå¥å¾çæè¿°

source code reference

#include <stdio.h>
#include <stdlib.h>

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

void pre_order(struct BinTree_node *tree);
void in_order(struct BinTree_node *tree);
void pos_order(struct BinTree_node *tree);

struct BinTree_node *create_bintree(void);

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();

//	pre_order(mytree);
//	in_order(mytree);
	pos_order(mytree);

	printf("\n");
	return 0;
}

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}

void pre_order(struct BinTree_node *tree)
{
	if(tree)
	{
		printf("%c", tree->elem);
		pre_order(tree->ltree);
		pre_order(tree->rtree);
	}
}

void in_order(struct BinTree_node *tree)
{
	if(tree)
	{
		in_order(tree->ltree);
		printf("%c", tree->elem);
		in_order(tree->rtree);
	}
}

void pos_order(struct BinTree_node *tree)
{
	if(tree)
	{
		pos_order(tree->ltree);
		pos_order(tree->rtree);
		printf("%c", tree->elem);
	}
}

07.3 Hierarchical Traversal of Binary Trees

        Whether it is pre-order traversal or in-order post-order traversal, they all have a common feature that is the use of recursive algorithms. This kind of function call is actually very resource-intensive! The creation of a function not only saves the scene information of the current stack area, but also opens up a buffer for the new stack, and restores the scene when exiting to keep the stack balanced. Assembly language can be referenced here. So in real programming, we can use loops without using recursion, so is there an algorithm for traversing binary trees using loops? Here is an algorithm called hierarchical traversal.


The idea of ​​the hierarchical traversal algorithm
        uses the first-in-first-out feature of the queue; first put the root tree into the queue, and then in the loop "first out of a queue and print its tree element values. If the current tree has left and right subtrees, then enter the queue in turn.... ..." reciprocating loop until all queues are exhausted and the queue is empty.

void level_traverse(struct BinTree_node *tree)
{
	struct BinTree_node node;

	enqueue(*tree);

	while(!is_empty())
	{
		node = dequeue();
		printf("%c", node.elem);
		if(node.ltree)
			enqueue(*node.ltree);
		if(node.rtree)
			enqueue(*node.rtree);
	}
}

 Source code reference

main.c

#include "binary_tree.h"

int main(void)
{
	struct BinTree_node *mytree;

	mytree = create_bintree();

	level_traverse(mytree);

	printf("\n");
	return 0;
}

binary_tree.h

#ifndef __BINARY_TREE_H__
#define __BINARY_TREE_H__

#include <stdio.h>
#include <stdlib.h>

struct BinTree_node
{
	unsigned char elem;
	struct BinTree_node *ltree, *rtree;
};

#include "queue.h"

struct BinTree_node *create_bintree(void);

void level_traverse(struct BinTree_node *tree);

#endif

binary_tree.c

#include "binary_tree.h"

struct BinTree_node *create_bintree(void)
{
	unsigned char flag;
	struct BinTree_node *tree;

	tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
	printf("Please input the node elem:\n");
	while((tree->elem = getchar()) == '\n');
	printf("Do you want to insert l_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->ltree = create_bintree();
	else
		tree->ltree = NULL;

	printf("Do you want to insert r_tree for %c, (Y/N)?\n", tree->elem);
	while((flag = getchar()) == '\n');

	if(flag == 'Y')
		tree->rtree = create_bintree();
	else
		tree->rtree = NULL;

	return tree;
}



void level_traverse(struct BinTree_node *tree)
{
	struct BinTree_node node;

	enqueue(*tree);

	while(!is_empty())
	{
		node = dequeue();
		printf("%c", node.elem);
		if(node.ltree)
			enqueue(*node.ltree);
		if(node.rtree)
			enqueue(*node.rtree);
	}
}

queue.h

#ifndef __QUEUE_H__
#define __QUEUE_H__

#include <stdio.h>
#include "binary_tree.h"

#define SIZE	512

void enqueue(struct BinTree_node c);
struct BinTree_node dequeue(void);
int is_empty(void);
int is_full(void);

#endif

tail.c

#include "queue.h"

struct BinTree_node queue[SIZE];

int head = 0, tail = 0;

void enqueue(struct BinTree_node c)
{
	queue[tail] = c;
	tail = (tail + 1) % SIZE;
}

struct BinTree_node dequeue(void)
{
	struct BinTree_node ch;
	ch = queue[head];
	head = (head + 1) % SIZE;

	return ch;
}

int is_empty(void)
{
	return head == tail;
}

int is_full(void)
{
	return (tail + 1) % SIZE == head;
}

07.4 Statistical Binary Tree Depth

The depth of the binary tree refers to the distance from the root tree to the farthest subtree + 1. In the case of only the number of roots, the depth of the farthest subtree is 0. When the binary number is empty, its depth is zero; recursion is often used The idea is to get the depth of the binary tree.

Return 0 when the algorithm idea
        tree is empty; otherwise, continue to recursively call and assign to two temporary variables until the tree is empty, and compare the two temporary variables to return the largest +1; it is equivalent to traversing to the farthest subtree
        node; The variable +1 at the end of each recursion can be understood as the number of statistical recursion = depth; Note: recursion includes the first call to the function

unsigned int depth(struct BinTree_node *tree)
{
	unsigned int l_depth;
	unsigned int r_depth;

	if(tree == NULL)
		return 0;
	else
	{
		l_depth = depth(tree->ltree);
		r_depth = depth(tree->rtree);

		return (l_depth > r_depth) ? (l_depth + 1) : (r_depth + 1);
	}
}

07.5 Count the number of leaves in the binary tree

What is a leaf
        A tree that no longer has subtrees can be called a leaf

There are three cases of recursive thought statistics leaves
        1) The binary tree is empty, the leaf tree is 0
        2) The binary tree has only the root node and no left and right subtrees, the leaf tree is 1
        3) In general, the leaf tree = left subtree leaf tree + right child tree leaf tree

code reference

unsigned int leaf_num(struct BinTree_node *tree)
{
	if(tree == NULL)
		return 0;
	else if((tree->ltree == NULL) && (tree->rtree == NULL))
		return 1;
	else
		return leaf_num(tree->ltree) + leaf_num(tree->rtree);
}

07.6 Count the number of binary tree nodes

What is a node?

There are two cases of recursive thought statistics nodes
        1) The number of nodes in the binary tree is empty and 0
        2) The number of nodes in the left subtree + the number of nodes in the right subtree + 1

code reference

unsigned int node_num(struct BinTree_node *tree)
{
	if(tree == NULL)
		return 0;
	else
		return node_num(tree->ltree) + node_num(tree->rtree) + 1;
}

07.7 Create a binary tree with the filling method

Inquiry method: troublesome; requires repeated user input; error-prone.

So can a string of strings with node information be converted into a specified binary tree at one time?
How should this string of information be written to represent the information of this binary tree? Recall that the traversal output order of the following binary tree can be used?

Filling method
       1. Fill the binary tree (features: the leaves of the original binary tree are no longer leaves, and # is added to new leaves), and write the preorder sequence of the binary tree.
        2. Convert the preorder sequence
                1) ch == '#' node is an empty subtree
                2) ch != '#' creates a new node; the left and right subtrees are called again (still recursive thinking)

struct BinTree_node *Create_BinTree(void) //fill_blank_method
{
	char ch;
	struct BinTree_node *tree;

	scanf("%c", &ch);

	if(ch == '#')
		tree = NULL;
	else
	{
		tree = (struct BinTree_node *)malloc(sizeof(struct BinTree_node));
		tree->elem = ch;
		tree->ltree = Create_BinTree();
		tree->rtree = Create_BinTree();
	}

	return tree;
}

07.8 Threaded binary tree

         The binary tree mentioned above is a non-linear data structure. We can find the left and right subtrees of the tree through pointers, but we cannot know where the tree comes from! Who is the pioneer! And not all trees have left and right subtrees, which leads to some trees whose pointers are empty and not used. We said that recursive calls are very resource-consuming! Is there a loop algorithm that replaces an algorithm like inorder traversal?
        Maybe you will think of adding a pointer to point to the parent node, which is a good way to also give you a three-point linked list of nouns . However, this method of increasing pointers undoubtedly increases resource overhead and wastes space. In fact, we do not necessarily require the predecessor of the node to be the parent node of the node. Here I also made a mistake in the concept of words. Since the predecessor and the successor are mentioned, this data structure must be a linear data structure. Linearizable data structures are very easy to traverse with loops.
        

        The best way to solve the above problems is to linearize the nonlinear binary tree. The purpose of linearization is to quickly find the predecessor and successor nodes and realize the loop traversal. So who will be the forerunner and successor? Here we choose the sequence of in-order traversal, which is relatively simple to implement.
        We need to modify the structure of the original tree and create flags to indicate whether the left and right pointers point to a subtree or a predecessor and successor node.

We stipulate that if the left pointer is empty (not used), then the left flag is set to 1, and the pointer points to the predecessor node; if the right pointer is empty (not used), then the right flag is set to 1, and the pointer points to the successor node .

 Code

void In_order_Thread(struct BinTree_node *tree)
{
	if(tree)
	{
		//1.Do_Inorder_Thread to ltree
		In_order_Thread(tree->ltree);

		//2.Deal with current node, 
		if(!tree->ltree)
		{
			tree->lflag = 1;
			//current node's ltree points to the pre node
			tree->ltree = pre;
		}
		else
			tree->lflag = 0;

		if(pre)
		{
			if(!pre->rtree)
			{
				pre->rflag = 1;
				pre->rtree = tree;
			}
			else
				pre->rflag = 0;
		}
		pre = tree;

		//3.Do_Inorder_Thread to rtree
		In_order_Thread(tree->rtree);
	}
}

void Create_Inorder_Thread(struct BinTree_node *T)
{
	if(T)
	{
		In_order_Thread(T);
		pre->rtree = NULL;
		pre->rflag = 1;
	}
}

07.9 Traversing the thread binary tree

Traversing, but this time using the loop traversal of the inorder sequence

The idea of ​​the algorithm is to first traverse to the leftmost tree and print out the value. If the right flag exists, then print the node value and follow it again, because the designed right flag pointer is the successor node of this sequence. If there is no right flag, then Just update the right subtree to the current one and then traverse to the leftmost tree... (loop thinking).

void Traverse_Inorder_Thread(struct BinTree_node *tree)
{
	while(tree)
	{
		while(tree->lflag == 0)
			tree = tree->ltree;
		printf("%c ", tree->elem);

		while((tree->rflag == 1) && (tree->rtree))
		{
			tree = tree->rtree;
			printf("%c ", tree->elem);
		}
		tree = tree->rtree;
	} 
}

07.10 Operation thread binary tree

1). Searching for a node is similar to traversal code. We only need to add judgment and return the value.

struct BinTree_node *Search_Inorder_Thread(struct BinTree_node *tree, char ch)
{
	while(tree)
	{
		while(tree->lflag == 0)
			tree = tree->ltree;
		if(tree->elem == ch)
			return tree;

		while((tree->rflag == 1) && (tree->rtree))
		{
			tree = tree->rtree;
			if(tree->elem == ch)
				return tree;
		}
		tree = tree->rtree;
	} 	
}

2). Find the predecessor point of the node. According to the design, if the lflag of the node exists, the pointed node is the precursor point. If it does not exist, you need to find the rightmost point of the left subtree

struct BinTree_node *Prenode_Inorder_Thread(const struct BinTree_node *node)
{
	struct BinTree_node *nd;

	if(node->lflag == 1)
		return node->ltree;
	else
	{
		nd = node->ltree;
		while(nd->rflag == 0)
			nd = nd->rtree;
		return nd;
	}
}

3). Find the successor point of the node, the idea is the same as finding the predecessor point, but if rflag does not exist, the successor point is the leftmost point of the right subtree.

struct BinTree_node *Succnode_Inorder_Thread(const struct BinTree_node *node)
{
	struct BinTree_node *nd;

	if(node->rflag == 1)
		return node->rtree;
	else
	{
		nd = node->rtree;
		while(nd->lflag == 0)
			nd = nd->ltree;
		return nd;
	}
}

It is also necessary to pay attention to the phenomenon that the predecessor point and successor point of the node are null

Guess you like

Origin blog.csdn.net/shelter1234567/article/details/129967368