Data structure - implementation of four traversals of binary trees

Table of contents

1. The concept of tree

1. Definition of tree

1) tree

2) Empty tree

3) Subtree

2. Definition of node

1) Root node

2) Leaf nodes

3) Internal nodes

3. Relationship between nodes

1) Child node

2) Parent node

3) Brother node

4. Depth of tree

5. Definition of forest

2. The concept of binary tree

1. Properties of binary trees

2. Special binary tree

1) leaning tree

2) Full binary tree

2) Complete binary tree

3. Properties of binary trees

1) Property 1

2) Property 2

3) Property 3

4) Property 4

3. Storage and creation of binary trees

1. Sequence table storage

1) Complete binary tree

2) Non-complete binary tree

3) Sparse binary tree

2. Linked list storage

3. Creation of binary tree

4. Binary tree traversal

1. Preorder traversal

1) Algorithm description

2) Detailed explanation of source code

2. In-order traversal

1) Algorithm description

2) Detailed explanation of source code

3. Post-order traversal

1) Algorithm description

2) Detailed explanation of source code

4. Layer sequential traversal

1) Algorithm description

2) Detailed explanation of source code

5. Integration of four traversal codes


1. The concept of tree

1. Definition of tree

1) tree

  A tree is a finite set of n (n≥0) nodes. When n>0, it is a non-empty tree that meets the following conditions:
    1) There is and is only one specific node, called the root node Root;
    2) Except for the root node, the remaining nodes are divided into m A mutually disjoint finite set T1​, T2​,…………,Tm​, each of which Ti​(1≤i≤m) is a tree and is a subtree of the root node Root. As shown in the figure, it represents a tree with a as the root node.

2) Empty tree

  When n=0, that is, the case of 0 nodes is also a tree, and it is called an empty tree.

3) Subtree

  The definition of tree uses the idea of ​​recursion. That is, the concept of tree is still used in the definition of tree. As shown in the figure, T1​ and T2​ are subtrees of node a. The tree composed of nodes d, g, h, i is a subtree of node b, and so on.

  There is no limit to the number of subtrees, but they must be disjoint. The one shown in the figure below is not a tree. Because in both graphs, the subtrees of a have intersecting edges.

2. Definition of node

  A tree node contains a  data field  and m  pointer fields  pointing to its subtrees. The types of nodes are divided into: root nodes, leaf nodes, and internal nodes. The number of subtrees a node has is called  the degree of the node . The maximum value of the degree of each node in the tree is called  the degree of the tree .

1) Root node

  A tree has only one root node.

2) Leaf nodes

  Nodes with degree 0 are called  leaf nodes  or  terminal nodes . Leaf nodes do not point to any subtree.

3) Internal nodes

  Nodes other than the root node and leaf nodes are called internal nodes.

  As shown in the figure above, the red node  is the root node, the blue node  is the internal node, and the yellow node  is the leaf node.

3. Relationship between nodes

1) Child node

  For a node, the root node of its subtree is called the  child node of the node .

  As shown in the figure above, the yellow node d  is   the child node of the red node b .

2) Parent node

  This node is called  the parent node of the child node .

  As shown in the figure above, blue node a  is   the parent node of red node b .

3) Brother node

  Child nodes under the same parent node are called  brother nodes .

  As shown in the figure above, green node c  and  red node b  are brother nodes of each other.

4. Depth of tree

  The level of nodes starting from the root node is recorded as level 1. If a node is on level i, then the root node of its subtree is on level i+1. The maximum level of a node in the tree is called The depth of the tree.
  As shown in the figure below, it represents a tree with a depth of 4.

5. Definition of forest

  A forest is a collection of m disjoint trees. For each node of the tree, the set of its subtrees is the forest.
  As shown in the figure, the set of two subtrees b and c is a forest.

2. The concept of binary tree

1. Properties of binary trees

  A binary tree is a tree that has the following characteristics:
    1) Each node has at most 2 subtrees, that is, the number of child nodes of each node is 0, 1, and 2;
    2) These two subtrees have The sequential ones are called: left subtree and right subtree respectively;
    3) If there is only one subtree, the order also needs to be distinguished, as shown in the figure:

  b is the left subtree of a;

  c is the right subtree of a;

2. Special binary tree

1) leaning tree

  A binary tree in which all nodes have only left subtrees is called a left-skew tree.

  A binary tree in which all nodes have only right subtrees is called a right-skew tree.

  A skew tree is somewhat similar to a linear table, so a linear table can be understood as a special form of tree.

2) Full binary tree

  For a binary tree, if all its root nodes and internal nodes have left and right subtrees, and all leaf nodes are at the same level, such a tree is a full binary tree.

  A full binary tree has the following characteristics:
    1) The leaf node must be at the last level;
    2) The degree of the non-leaf node is 2;
    3) For binary trees with the same depth, the full binary tree has the largest number of nodes, which is 
(where h represents depth).

2) Complete binary tree

  A binary tree with n nodes is numbered according to the hierarchical order. If the node number i and the node number i in a full binary tree of the same depth are exactly the same in the binary tree, it is called a complete  binary tree .

  A full binary tree must be a complete binary tree, but a complete binary tree is not necessarily a full binary tree.
  A complete binary tree has the following characteristics:
    1) Leaf nodes can only appear in the bottom two levels.
    2) The leaf nodes of the lowest layer must be concentrated in the continuous position on the left; if there are leaf nodes in the penultimate layer, they must be concentrated in the continuous position on the right.
    3) If the degree of a node is 1, there is only the left subtree, that is,  there is no situation where there is only the right subtree  .
    4) For a binary tree with the same number of nodes, the depth of a complete binary tree is the smallest.

  As shown in the figure below, it is not a complete binary tree, because node No. 5 does not have a right subtree, but node No. 6 has a left subtree, which does not satisfy the second point above.

3. Properties of binary trees

  Next, let’s take a look at the important properties of binary trees.

1) Property 1

  [Property 1] There are at  most .

  Since it is at most, we only need to consider the case of a full binary tree. For a full binary tree, the number of nodes in the current layer is twice that of the previous layer. The number of nodes in the first layer is 1, so the number of nodes in the i-th layer can be equal to The ratio sequence formula is calculated as  .

2) Property 2

  [Property 2] A binary tree with depth h has at most ​​​​​​​nodes .

  For any binary tree with a depth of h, the number of nodes in the full binary tree must be the largest, so we can use the full binary tree to calculate, and the number of nodes in each layer of it is .
  Using the geometric series summation formula, the total number of nodes is obtained:

3) Property 3

  [Property 3] For any binary tree T, if the number of leaf nodes is x0​ and the number of nodes with degree 2 is x2​, then

x0​=x2​+1

  Let x1​ represent the number of nodes with degree 1, and the total number of nodes is n, then there is:
n=x0​+x1​+
  x2 ​The connection from any node to its child node is called the tree An edge. For any non-empty tree, the number of edges is equal to the number of nodes minus one. Let the number of edges be e, then there is:
e=n−1

For a node with degree 1, 1 edge can be provided, such as the yellow node in the figure ; for a node with degree 2, 2 edges can be provided, such as the red node in the figure . So the number of edges can be calculated from the number of nodes with degrees 1 and 2:

                                e=x1​+2x2​ 

Combining the above three equations, we get:

                                e=n−1=x0​+x1​+x2​−1=x1​+2x2​  

After simplification, it is proved:
                                x0​=x2​+1

4) Property 4

  [Property 4] The depth of a complete binary tree with n nodes is [log2​n]+1.

  From [Property 2], we can get that a binary tree with depth ℎh has at most one node. Therefore, assuming that the depth of a tree is h and its number of nodes is n, it must satisfy:

Since it is a complete binary tree, it must have more nodes than the depth of ℎ−1h−1, that is:

After slightly rearranging the above two inequalities, we get:

Then, taking the base 2 logarithm of both sides of the inequality, we get:

Here, since h must be an integer, we have:

3. Storage and creation of binary trees

1. Sequence table storage

  Sequential storage of binary trees refers to using arrays to store binary trees. The storage location of the node is the array subscript, which can reflect the logical relationship between the nodes, such as the relationship between the parent node and the child node, the relationship between the left and right sibling nodes, etc.

1) Complete binary tree

  Let's look at a complete binary tree. We store it as follows. ​​​​​​​

  The number represents the absolute position of the array subscript, and the mapping is as follows:

subscript 0 1 2 3 4 5 6 7 8 9 10 11 12
data a b c d e f g h i j k l
  For convenience here, we leave the position with array index 0 blank. In this way, when you know the subscript x of a node, you can know that the subscripts of its left and right sons are 2x and 2x+1 respectively; conversely, when you know the subscript x of a node, you can also know its parent The subscript of the node is [x / 2]

2) Non-complete binary tree

  For non-complete binary trees, you only need to set the corresponding non-existent nodes to empty.

  The number represents the absolute position of the array subscript, and the mapping is as follows:

subscript 0 1 2 3 4 5 6 7 8 9 10 11 12
data a b c d e f g k l

3) Sparse binary tree

  For relatively sparse binary trees, the following situation will occur. At this time, if stored in this way, it will be a waste of memory.

  The number represents the absolute position of the array subscript, and the mapping is as follows:

subscript 0 1 2 3 4 5 6 7 8 9 10 11 12
data a b c d g h
Therefore, we can use linked lists for storage.

2. Linked list storage

  Each node in a binary tree has at most two child nodes, so for each node, just set a  data field  and two  pointer fields  . The pointer fields  point to the left child node and the right child node respectively.

typedef char datatype_bt;
typedef struct btreenode{
	datatype_bt data;
	struct btreenode *lchild;  // 指向左孩子节点
    struct btreenode *rchild;   // 指向右孩子节点
}btree_node,*btree_pnode;

3. Creation of binary tree

First create the root node, then create the left subtree, and finally create the right subtree. If the node does not exist, the input is represented by #

Creating without formal parameters is implemented in C language as follows:

btree_pnode create_btree(void)
{
	btree_pnode t;
	datatype_bt ch;

	//如果结点不存在,则输入时,用#表示
	scanf("%c",&ch);
	if('#' == ch)
		t = NULL;
	else{
		//创建根结点
		t = (btree_pnode)malloc(sizeof(btree_node));
		if(NULL == t){
			perror("malloc");
			exit(1);
		}
		t->data = ch;
		//创建左子树
		t->lchild = create_btree();
		//创建右子树
		t->rchild = create_btree();
	}
	return t;
}

Creating with formal parameters is implemented in C language as follows:

void create_btree(btree_pnode *T)
{
	datatype_bt ch;

	//如果结点不存在,则输入时,用#表示
	scanf("%c",&ch);
	if('#' == ch)
		*T = NULL;
	else{
		//创建根结点
		*T = (btree_pnode)malloc(sizeof(btree_node));
		if(NULL == *T){
			perror("malloc");
			exit(1);
		}
		(*T)->data = ch;
		//创建左子树
		create_btree(&(*T)->lchild);
		//创建右子树
		create_btree(&(*T)->rchild);
	}
}

4. Binary tree traversal

  Binary tree traversal 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.
  For linear table traversal, either from beginning to end or from end to beginning, the traversal method is relatively simple, but the tree is different. Each node of it may have two child nodes, so the order of traversal faces different problems. choose.
  There are four commonly used traversal methods for binary trees: pre-order traversal, in-order traversal, post-order traversal, and level-order traversal.

 

1. Preorder traversal

1) Algorithm description

  [ Pre- order traversal] If the binary tree is empty, return directly. Otherwise, visit the root node first, then recursively traverse the left subtree in order, and then traverse the right subtree recursively in order.

  The result of preorder traversal is as follows: abdghcefi.

2) Detailed explanation of source code

Preorder traversal is implemented in C language as follows:

void pre_order(btree_pnode t)
{
	if(t != NULL){
		//访问根结点
		printf("%c",t->data);
		//先序遍历左子树
		pre_order(t->lchild);
		//先序遍历右子树
		pre_order(t->rchild);

	}
}

Extension: Pre-order non-recursive algorithm implementation, using chain stack, please refer to the following article: Data Structure - Implementation of Sequential Stack and Chain Stack - CSDN Blog

Implemented in C language as follows:

void unpre_order(btree_pnode t)
{
	link_pstack top;  //top为指向栈顶结点的指针

	init_linkstack(&top);  //初始化链栈
	while(t != NULL || !isempty_linkstack(top)){
	    if(t != NULL){
		  printf("%c",t->data);
		  if(t->rchild != NULL)
			push_linkstack(t->rchild,&top);
		  t = t->lchild;
	    }else
		  pop_linkstack(&t,&top);
    }
}

2. In-order traversal

1) Algorithm description

  [In-order traversal] If the binary tree is empty, return directly. Otherwise, first traverse the left subtree recursively in order, then visit the root node, and then traverse the right subtree recursively in order.

  The result of in-order traversal is as follows: gdhbaecif.

2) Detailed explanation of source code

In-order traversal is implemented in C language as follows:

void mid_order(btree_pnode t)
{
	if(t != NULL){
		//中序遍历左子树
		mid_order(t->lchild);
		//访问根结点
		printf("%c",t->data);
		//中序遍历右子树
		mid_order(t->rchild);
	}
}

3. Post-order traversal

1) Algorithm description

  [Post-order traversal] If the binary tree is empty, return directly. Otherwise, the left subtree is traversed recursively, then the right subtree is traversed recursively and in order, and then the root node is visited.

  The result of post-order traversal is as follows: ghdbeifca.

2) Detailed explanation of source code

Postorder traversal is implemented in C language as follows:

void post_order(btree_pnode t)
{
	if(t != NULL){
		//先序遍历左子树
		post_order(t->lchild);
		//先序遍历右子树
		post_order(t->rchild);
		//访问根结点
		printf("%c",t->data);
	}
}

4. Layer sequential traversal

1) Algorithm description

  [Level-order traversal] If the binary tree is empty, return directly. Otherwise, start from the first level of the tree and traverse layer by layer from top to bottom. In the same layer, nodes are visited one by one from left to right.

  Layer-order traversal requires queue knowledge. You can refer to the following articles:

Data Structure - Implementation of Sequential Queue and Chained Queue-CSDN Blog

2) Detailed explanation of source code

Level sequential traversal is implemented in C language as follows:

void level_order(btree_pnode t)
{
	link_pqueue q;

	init_linkqueueu(&q);  //初始化链式队列
	while(t != NULL){
		printf("%c",t->data);
		if(t->lchild != NULL)
			in_linkqueue(t->lchild,q);
		if(t->rchild != NULL)
			in_linkqueue(t->rchild,q);
		if(is_empty_linkqueue(q))
			break;
		else
			out_linkqueue(&t,q);
	}
}

5. Integration of four traversal codes

btree.h

#ifndef __BTREE_h__
#define __BTREE_h__

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

typedef char datatype_bt;
typedef struct btreenode{
	datatype_bt data;
	struct btreenode *lchild,*rchild;
}btree_node,*btree_pnode;

extern void create_btree(btree_pnode *T);
extern void pre_order(btree_pnode t);
extern void unpre_order(btree_pnode t);
extern void mid_order(btree_pnode t);
extern void post_order(btree_pnode t);
extern void level_order(btree_pnode t);
extern void travel(char const *str,void (*pfun)(btree_pnode ),btree_pnode t);

#endif

btree.c

#include "btree.h"
#include "linkqueue.h"
#include "linkstack.h"

#if 0
btree_pnode create_btree(void)
{
	btree_pnode t;
	datatype_bt ch;

	//如果结点不存在,则输入时,用#表示
	scanf("%c",&ch);
	if('#' == ch)
		t = NULL;
	else{
		//创建根结点
		t = (btree_pnode)malloc(sizeof(btree_node));
		if(NULL == t){
			perror("malloc");
			exit(1);
		}
		t->data = ch;
		//创建左子树
		t->lchild = create_btree();
		//创建右子树
		t->rchild = create_btree();
	}
	return t;
}
#else
void create_btree(btree_pnode *T)
{
	datatype_bt ch;

	//如果结点不存在,则输入时,用#表示
	scanf("%c",&ch);
	if('#' == ch)
		*T = NULL;
	else{
		//创建根结点
		*T = (btree_pnode)malloc(sizeof(btree_node));
		if(NULL == *T){
			perror("malloc");
			exit(1);
		}
		(*T)->data = ch;
		//创建左子树
		create_btree(&(*T)->lchild);
		//创建右子树
		create_btree(&(*T)->rchild);
	}
}
#endif

//先序遍历
void pre_order(btree_pnode t)
{
	if(t != NULL){
		//访问根结点
		printf("%c",t->data);
		//先序遍历左子树
		pre_order(t->lchild);
		//先序遍历右子树
		pre_order(t->rchild);

	}
}

//先序非递归算法实现
void unpre_order(btree_pnode t)
{
	link_pstack top;  //top为指向栈顶结点的指针

	init_linkstack(&top);  //初始化链栈
	while(t != NULL || !isempty_linkstack(top)){
	    if(t != NULL){
		  printf("%c",t->data);
		  if(t->rchild != NULL)
			push_linkstack(t->rchild,&top);
		  t = t->lchild;
	    }else
		  pop_linkstack(&t,&top);
    }
}

//中序遍历
void mid_order(btree_pnode t)
{
	if(t != NULL){
		//中序遍历左子树
		mid_order(t->lchild);
		//访问根结点
		printf("%c",t->data);
		//中序遍历右子树
		mid_order(t->rchild);
	}
}

//后序遍历
void post_order(btree_pnode t)
{
	if(t != NULL){
		//先序遍历左子树
		post_order(t->lchild);
		//先序遍历右子树
		post_order(t->rchild);
		//访问根结点
		printf("%c",t->data);
	}
}

//层序遍历
void level_order(btree_pnode t)
{
	link_pqueue q;

	init_linkqueueu(&q);  //初始化链式队列
	while(t != NULL){
		printf("%c",t->data);
		if(t->lchild != NULL)
			in_linkqueue(t->lchild,q);
		if(t->rchild != NULL)
			in_linkqueue(t->rchild,q);
		if(is_empty_linkqueue(q))
			break;
		else
			out_linkqueue(&t,q);
	}
}

void travel(char const *str,void (*pfun)(btree_pnode ),btree_pnode t)
{
      printf("%s",str);
      pfun(t);
      printf("\n");
}

linkstack.h

#ifndef __LINKSTACK_H__
#define __LINKSTACK_H__

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "btree.h"

typedef btree_pnode datatype_ls;

typedef struct linkstack{
	datatype_ls data;
	struct linkstack *next;
}link_stack,*link_pstack;

extern void init_linkstack(link_pstack *Top);
extern bool push_linkstack(datatype_ls data,link_pstack *Top);
extern bool pop_linkstack(datatype_ls *t,link_pstack *Top);
extern bool isempty_linkstack(link_pstack top);
//extern void show_linkstack(link_pstack top);
#endif

linkstack.c

#include "linkstack.h"

//链栈的初始化
void init_linkstack(link_pstack *Top)
{
	*Top = NULL;
}

//入栈
bool push_linkstack(datatype_ls data,link_pstack *Top)
{
	link_pstack new;

	new = (link_pstack)malloc(sizeof(link_stack));
	if(NULL == new){
		perror("malloc");
		return false;
	}
	new->data = data;

	new->next = *Top;
	*Top = new;

	return true;
}

//出栈
bool pop_linkstack(datatype_ls *t,link_pstack *Top)
{
	link_pstack q;
	if(isempty_linkstack(*Top)){
		printf("链栈是空的!\n");
		return false;
	}
	//出栈
	q = *Top;
	*Top = (*Top)->next;
	*t = q->data;
	free(q);
	return true;
}
//判断顺序栈是否空
bool isempty_linkstack(link_pstack top)
{
	if(top == NULL)
		return true;
	else
		return false;
}
#if 0
void show_linkstack(link_pstack top)
{
	link_pstack p;

	for(p = top; p != NULL; p = p->next)
		printf("%d\t",p->data);
	printf("\n");
}
#endif

 linkqueue.h

#ifndef __LINKQUEUE_H__
#define __LINKQUEUE_H__

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "btree.h"


typedef btree_pnode datatype_lq;
typedef struct listnode{
	datatype_lq data;
	struct listnode *next;
}list_node,*list_pnode;
typedef struct linkqueue{
	list_pnode front,rear;
}link_queue,*link_pqueue;

extern void init_linkqueueu(link_pqueue *Q);
extern bool in_linkqueue(datatype_lq data,link_pqueue q);
extern bool out_linkqueue(datatype_lq *t,link_pqueue q);
extern bool is_empty_linkqueue(link_pqueue q);
//extern void show_linkqueue(link_pqueue q);
#endif

 linkqueue.c  

#include "linkqueue.h"

void init_linkqueueu(link_pqueue *Q)
{
	*Q = (link_pqueue)malloc(sizeof(link_queue));
	if(NULL == *Q){
		perror("malloc");
		exit(1);
	}
	(*Q)->front = (list_pnode)malloc(sizeof(list_node));
	if(NULL == (*Q)->front){
		perror("malloc");
		exit(1);
	}
	(*Q)->front->next = NULL;
	(*Q)->rear = (*Q)->front;
}

bool in_linkqueue(datatype_lq data,link_pqueue q)
{
	list_pnode new;

	new = (list_pnode)malloc(sizeof(list_node));
	if(NULL == new){
		perror("malloc");
		return false;
	}
	new->data = data;

	new->next = q->rear->next;
	q->rear->next = new;
	q->rear = new;

	return true;
}
bool out_linkqueue(datatype_lq *t,link_pqueue q)
{
	list_pnode p;

	if(is_empty_linkqueue(q)){
		printf("队列是空的!\n");
		return false;
	}

	p = q->front;
	q->front = q->front->next;
	*t = q->front->data;
	free(p);
	return true;
}

bool is_empty_linkqueue(link_pqueue q)
{
	if(q->rear == q->front)
		return true;
	else
		return false;
}
#if 0
void show_linkqueue(link_pqueue q)
{
	list_pnode p;
	for(p = q->front->next;p;p = p->next)
		printf("%d\t",p->data);
	printf("\n");
}
#endif

 test.c

#include "btree.h"

int main(void)
{
	btree_pnode t;  //定义根结点指针

	create_btree(&t);   //创建二叉树

	travel("先序遍历二叉树:",pre_order,t);
	travel("先序非递归遍历二叉树:",unpre_order,t);
	travel("中序遍历二叉树:",mid_order,t);
	travel("后序遍历二叉树:",post_order,t);
	travel("按层遍历二叉树:",level_order,t);
	return 0;
}

Makefile  

CC = gcc
CFLAGS = -Wall -g -O0
SRC = btree.c test.c linkqueue.c  linkstack.c
OBJS = test

$(OBJS):$(SRC)
	$(CC) $(CFLAGS) -o $@ $^

clean:
	$(RM) $(OBJS) .*.sw?
  • -o0" means the optimization level is 0, which means turning off optimization. This will result in a larger executable file, but it can be easily debugged.
  • "-g" means generating debugging information so that you can view the values ​​of variables, function call stacks and other information when debugging the program.
  • "-Wall" means to enable all warnings, and the compiler will display all possible warning messages to help developers find potential problems.
  • $(RM): This is a variable in the Makefile used to represent the delete command. It may be set to the actual delete command on the system, such as rmor del.
  • $(OBJS): This is a variable in the Makefile that represents the list of target files to be deleted.
  • .*.sw?: This is a wildcard expression that matches .any filename starting with , followed by .swand one character (for example .swp). This is typically used to delete temporary files generated by the editor.

Validation results:

Guess you like

Origin blog.csdn.net/m0_74712453/article/details/135360309