[Dahua Data Structure] Chapter 6 Summary - Tree (Part 2)

content

foreword

storage structure of binary tree

traverse binary tree

Building a binary tree

clue binary tree

Conversion of Trees, Forests and Binary Trees

Summarize


foreword

This is the last and most important part of the tree in Chapter 6 of "Dahua Data Structure", and of course there will be more content.

Without further ado, let's get to the point.

 

storage structure of binary tree

Sequential storage structure:

Due to the particularity of binary trees, one-dimensional arrays can be used to store the nodes of binary trees.

First look at how a complete binary tree is stored using a sequential storage structure.

(For the content of the complete binary tree, you can click on the right to go directly to [Dahua Data Structure] Chapter 6 Summary - Tree (middle) )

 

Store the above image in a one-dimensional array, as shown in the following image:

 

The subscript of the array represents the position of the node. For example, the array subscript of the E node is 5, which means its position is 5.

If the following incomplete binary tree is stored in an array, how to represent it?

 

The above picture is a non-complete binary tree, in which only ABCEGJ (ie blue part) nodes exist in the binary tree,

In order to facilitate storage in the array, it needs to be complemented into a complete binary tree, and ^ is used to represent non-existing nodes, as shown in the following figure:

 

Consider another extreme case. If it is a right-slope tree with a depth of k, there are only k nodes, but 2 ^ k - 1 storage unit space needs to be allocated, which obviously causes a waste of space, as shown in the following figure:

 

Therefore, we can also know that the sequential storage structure is generally only suitable for complete binary trees .

 

Chained storage structure:

As can be seen from the above, the applicability of the sequential storage structure is not strong, so we consider using the chain storage structure.

Since each node of a binary tree has at most two children, it is a natural idea to design a data field and two pointer fields for it, and such a linked list also becomes a binary linked list.

The structure diagram of the node is as follows:

 

where data is the data field, lchild is the pointer field that stores the left child, and rchild is the pointer field that stores the right child

The code indicates that the node structure is as follows:

//二叉树的二叉链表结点结构定义
typedef struct BiTNode {
    TElemType data;//结点数据
    struct BiTNode *lchild, *rchild;//左右孩子指针
}BiTNode, *BiTree;

 

The structure diagram is as follows:

 

traverse binary tree

Traversing a binary tree means starting from the root node and visiting all the nodes in the binary tree in a certain order, so that each node is visited once and only once.

There are four main ways to traverse a binary tree: preorder traversal , inorder traversal , postorder traversal , and level order traversal .

 

Preorder traversal (around the root):

If the binary tree is empty, do not operate and return directly;

Otherwise, visit the root node first, then traverse the left subtree and then the right subtree, that is, according to the order of the root left and right , the preorder traversal result of the following figure is ABDGHCEIF

 

Inorder traversal (left root right):

If the binary tree is empty, do not operate and return directly;

Otherwise, start from the root node (note that the root node is not visited first), traverse the left subtree of the root node in inorder, then visit the root node, and finally traverse the right subtree in inorder

That is, according to the order of left root and right , the result of the in-order traversal of the following figure is GDHBAEICF

 

Postorder traversal (left and right roots):

If the binary tree is empty, do not operate and return directly;

Otherwise, the left and right subtrees are traversed from left to right, followed by the leaves and then the nodes, and finally the root node is accessed.

That is, in the order of the left and right roots , the post-order traversal result of the following figure is GHDBIEFCA

 

Layer order traversal:

If the binary tree is empty, do not operate and return directly;

Otherwise, start from the first layer of the tree, that is, the root node, and traverse layer by layer from top to bottom. In the same layer, visit the nodes one by one in the order from left to right.

The result of the level order traversal in the following figure is ABCDEFGHI

 

preorder traversal algorithm

Since a binary tree can be defined recursively, it is also possible to traverse a binary tree recursively.

The code for the preorder traversal algorithm is as follows:

//二叉树的前序遍历递归算法
void PreOrderTraverse(BiTree T)
{ 
	if(T==NULL)
            return;
	printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	PreOrderTraverse(T->lchild); /* 再先序遍历左子树 */
	PreOrderTraverse(T->rchild); /* 最后先序遍历右子树 */
}

 

Inorder traversal algorithm

In fact, the pre-order traversal algorithm is very similar to the in-order traversal algorithm, but the order of some codes is different!

The code for the inorder traversal algorithm is as follows:

//二叉树的中序遍历递归算法
void InOrderTraverse(BiTree T)
{ 
	if(T==NULL)
            return;
	InOrderTraverse(T->lchild); /* 中序遍历左子树 */
	printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
	InOrderTraverse(T->rchild); /* 最后中序遍历右子树 */
}

The in-order traversal algorithm just advances the recursive function that calls the left child. I believe everyone can think of the implementation of the post-order traversal algorithm.

 

post-order traversal algorithm

That's right, put the print statement in the last sentence.

//二叉树的中序遍历递归算法
void PostOrderTraverse(BiTree T)
{
	if(T==NULL)
            return;
	PostOrderTraverse(T->lchild); /* 先后序遍历左子树  */
	PostOrderTraverse(T->rchild); /* 再后序遍历右子树  */
	printf("%c",T->data);/* 显示结点数据,可以更改为其它对结点操作 */
}

 

Building a binary tree

In fact, after talking for a long time, a binary tree has not been generated, and there is no tree. How to traverse it? So let's talk about the problem of building a binary tree

If we want to build the following binary tree, we extend it for convenience.

It becomes like the picture on the right, that is, the null pointer of each node in the binary tree leads to a virtual node whose value is a specific value, such as "#".

We call this processed binary tree an extended binary tree of the original binary tree. At this time, the preorder traversal result of the right image is AB#D##C##

 

At this point, let's take a look at how to generate a binary tree. Assuming that the nodes of the binary tree are all one character, we input the preorder traversal sequence AB#D##C## one by one with the keyboard.

The algorithm is as follows:

/* 按前序输入二叉树中结点的值(一个字符) */
/* #表示空树,构造二叉链表表示二叉树T。 */
void CreateBiTree(BiTree *T)
{ 
	TElemType ch;
	
	/* scanf("%c",&ch); */
	ch=str[index++];

	if(ch=='#') 
		*T=NULL;
	else
	{
		*T=(BiTree)malloc(sizeof(BiTNode));
		if(!*T)
			exit(OVERFLOW);
		(*T)->data=ch; /* 生成根结点 */
		CreateBiTree(&(*T)->lchild); /* 构造左子树 */
		CreateBiTree(&(*T)->rchild); /* 构造右子树 */
	}
}

 

When building a binary tree, the principle of recursion is also used, but the operation of generating nodes and assigning values ​​to nodes is changed in the place where nodes should be printed.

(Of course, in-order or post-order traversal can also be used to build a binary tree, but the order of generating nodes in the code and constructing the left and right subtrees is exchanged)

 

clue binary tree

Why build a threaded binary tree? What is a clue binary tree?

Let's look at the figure below, and we will find that not all pointer fields are fully utilized, there are many "^", that is, the existence of null pointer fields.

For a binary linked list with n nodes, each node has two pointer fields pointing to the left and right children, so there are 2n pointer fields in total.

And a binary tree with n nodes has a total of n - 1 branch lines, that is, there are 2n - (n - 1) = n + 1 null pointer fields.

For example, there are 10 nodes in the figure, and the number of null pointer fields reaches 11, which is a waste of memory resources. This is why we use these null pointer fields.

 

On the other hand, if we want to know the predecessors and successors of a node, we may need to do a traversal to find out. Every time we need to know in the future, we have to traverse again, and if the tree is very large, it will undoubtedly waste a lot of repetitive time. Why not consider remembering these predecessors at creation time ?

Therefore, we can use the large number of null pointer fields in the above figure to store the addresses of the predecessor and successor nodes of the node .

We call this pointer to the predecessor and successor a thread , and the binary linked list with the thread is called a thread linked list , and the corresponding binary tree is called a Threaded Binary Tree.

 

As shown in the figure above, change the rchild of all null pointer fields to point to its successor node. So we can know that the successor node of H is D, the successor node of I is B, the successor node of J is E, the successor node of E is A, the successor node of F is C, and the successor node of G is because it does not exist. Pointing to NULL, there are 6 empty pointer fields to be used at this time.

As for why the successor node of node H is D, and the successor node of I is B. . . ? In fact, it is because we have performed an in-order traversal of it, namely HDIBJEAFCG.

 

Looking at the above figure again, at this time, change the lchild in all null pointer fields to point to the predecessor of the current node. So the predecessor of H is NULL, the predecessor of I is D, the predecessor of J is B, the predecessor of F is A, and the predecessor of G is C. A total of 5 null pointer fields are used, which adds up to exactly 11 null pointer fields.

 

From the above figure, we can find that (the solid line of the hollow arrow is the predecessor, and the black arrow of the dashed line is the successor), in fact, the clue binary tree is equivalent to converting a binary tree into a doubly linked list. At this time, inserting and deleting nodes and searching for nodes are given We bring convenience. So we call the process of traversing a binary tree into a threaded binary tree in a certain order as threaded.

 

However, the problem has not been completely solved. How do we know whether a node's lchild points to its left child or to its predecessor? Does rchild point to its right child or to its successor?

Obviously, we need a distinguishing flag when deciding whether lchild points to the left child or the predecessor, and whether rchild points to the right child or the successor.

Therefore, we add two more flag fields ltag and rtag to each node, which only store the boolean type of 0 or 1 number. The node structure is as follows:

 

in:

When ltag is 0, it points to the left child of the node, and when it is 1, it points to the predecessor of the node

When rtag is 0, it points to the right child of the node, and when it is 1, it points to the successor of the node.

Therefore, the binary tree linked list diagram just now can be modified as follows:

 

Thread binary tree structure implementation:

The code to define the structure is as follows:

typedef enum {Link,Thread} PointerTag;	/* Link==0表示指向左右孩子指针, */
			        /* Thread==1表示指向前驱或后继的线索 */
typedef  struct BiThrNode	/* 二叉线索存储结点结构 */
{
	TElemType data;	/* 结点数据 */
	struct BiThrNode *lchild, *rchild;	/* 左右孩子指针 */
	PointerTag LTag;
	PointerTag RTag;		/* 左右标志 */
} BiThrNode, *BiThrTree;

The essence of threading is to change the null pointer in the binary linked list into a thread that points to the predecessor or successor .

Since the information of the predecessor and successor can only be obtained when traversing the binary tree, the process of threading is the process of modifying the null pointer during the traversal process.

 

The recursive function code for inorder traversal threading is as follows:

BiThrTree pre; /* 全局变量,始终指向刚刚访问过的结点 */
/* 中序遍历进行中序线索化 */
void InThreading(BiThrTree p)
{ 
	if(p)
	{
		InThreading(p->lchild); /* 递归左子树线索化 */
		if(!p->lchild) /* 没有左孩子 */
		{
			p->LTag=Thread; /* 前驱线索 */
			p->lchild=pre; /* 左孩子指针指向前驱 */
		}
		if(!pre->rchild) /* 前驱没有右孩子 */
		{
			pre->RTag=Thread; /* 后继线索 */
			pre->rchild=p; /* 前驱右孩子指针指向后继(当前结点p) */
		}
		pre=p; /* 保持pre指向p的前驱 */
		InThreading(p->rchild); /* 递归右子树线索化 */
	}
}

 

The traversal code is as follows:

/* 中序遍历二叉线索树T(头结点)的非递归算法 */
Status InOrderTraverse_Thr(BiThrTree T)
{ 
	BiThrTree p;
	p=T->lchild; /* p指向根结点 */
	while(p!=T)
	{ /* 空树或遍历结束时,p==T */
		while(p->LTag==Link)
			p=p->lchild;
		if(!visit(p->data)) /* 访问其左子树为空的结点 */
			return ERROR;
		while(p->RTag==Thread&&p->rchild!=T)
		{
			p=p->rchild;
			visit(p->data); /* 访问后继结点 */
		}
		p=p->rchild;
	}
	return OK;
}

From the above, we can conclude that if the binary tree used needs to be traversed frequently or needs some kind of predecessor and successor in the traversal sequence when finding nodes, then the storage structure of the clue binary linked list is a very good choice.

 

Conversion of Trees, Forests and Binary Trees

Convert tree to binary tree

1. Add line. Add a line between all sibling nodes

2, go to the line. For each node in the tree, only the connection between it and the first child node is kept, and the connection between it and other child nodes is deleted.

3. Level adjustment. Taking the root node of the tree as the axis, rotate the whole tree clockwise by a certain angle to make the structure clear.

(Note that the first child is the left child of the binary tree node, and the child converted by the brother is the right child of the node)

As shown in the figure below, it is the steps to convert a tree into a binary tree.

 

Convert forest to binary tree

The forest is composed of several trees, so we can understand that each tree in the forest is a brother, and can be operated according to the processing method of the brother.

1. Convert each tree into a binary tree

2. The first binary tree does not move. Starting from the second binary tree, the root node of the latter binary tree is used as the right child of the root node of the former binary tree, and the lines are connected. When all the binary trees are connected, the binary tree converted from the forest is obtained.

As shown in the figure below, convert the three trees of the forest into a binary tree

 

Convert binary tree to tree

Converting a binary tree to a tree is actually the inverse process of converting a tree to a binary tree.

1. Add line. If the left child node of a node exists, then the right child node of the left child, the right child node of the right child, and the right child node of the right child of the right child are set. . . . .

That is, the n right child nodes of the left child are all children of this node. Connect the node to the right child nodes with lines.

2, go to the line. Delete all nodes in the original binary tree and their right child nodes.

3. Level adjustment. Make it structured.

 

Convert binary tree to forest

To judge whether a binary tree can be converted into a tree or a forest, the key point is to judge whether the root node of the binary tree has a right child, if there is a forest, if not, it is a tree.

1. Starting from the root node, if the right child exists, delete the connection with the right child node, and then check the separated binary tree. If the right child exists, delete the connection. . . . , until all the right child connections are removed, and a detached binary tree is obtained

2. Convert each separated binary tree into a tree.

 

Summarize

Traversing a binary tree can actually be said to be a science. Front, middle, back and layer order traversal are all required to be proficient. In fact, I suggest that you can simulate the computer yourself and draw the implementation of the recursive method by yourself, which can deepen our understanding of recursion.

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326076552&siteId=291194637