二叉树常见操作
一、遍历二叉树
二叉树的存储采用的是链式存储方式(链表)但是其链数是不确定的,所以我们不可直接采用链表的遍历方法。在这里介绍一下二叉树的常见遍历方法。
遍历二叉树的顺序存在以下三种顺序、六种情况。
1.先序遍历,TLR(根左右),TRL(根右左)。
2.中序遍历,LTR(左根右),RTL(右根左)。
3.后序遍历,LRT(左右根),RLT(右左根)。
这里面每种顺序都有两个情况,这是根据人们的左右习惯进行区分的。我们通常采用TLR,LTR,LRT这三种情况,另外三种情况往往不予采用。这里我们也发现了三种遍历方式先中后的遍历方式命名是按照访问根节点的顺序来命名的。
三种遍历顺序的流程:
先序遍历的流程:
1.若二叉树为空,结束遍历。
2.先遍历根节点。
3.先序遍历左子树。
4.先序遍历右子树。
中序遍历的流程:
1.若二叉树为空,结束遍历。
2.中序遍历左子树。
3.访问根节点。
4.中序遍历右子树。
后序遍历的流程:
1.若二叉树为空,结束遍历。
2.后序遍历左子树。
3.后续遍历右子树。
4.访问根节点。
采用三种变量方式遍历下图得到的结果分别为:
1.先序(根左右):e、a、c、b、d、f、h、g
2.中序(左根右):a、b、c、d、e、f、g、h
3.后序(左右根):b、d、c、a、g、h、f、e
这里对先序遍历的流程进行讲解一下:
1.先序的顺序是根左右,按照先序的流程,我们先访问根节点,得到第一个数e。
2.开始遍历左子树,得到第二个数左子树的根节点a,由于a节点下的左子树为空,所以得到第三个数c,再按序得到第四第五两个数,b和d。此时我们遍历完了根节点e的左子树,得到的结果是:e、a、c、b、d。
3.开始遍历右子树,得到第六个数右子树的根节点f,由于f下的左子树为空,直接遍历其的右子树,得到第七个数h,再遍历h下的左子树得到最后一个数,g。
最终结果为e、a、c、b、d、f、h、g
代码实现:
上面的介绍中我们能看出,遍历操作实际上是将非线性结构线性化的过程,其结果为线性序列。遍历操作是一个递归的过程,所以我们可以采用递归函数来实现。
先序遍历代码如下:
_LINK_T *Traverse_P(_LINK_T *head)
{
if (!head)
{
return ;
}
/*访问根节点*/
printf("%c ",head->data);
/*遍历左子树*/
Traverse_P(head->lchild);
/*遍历右子树*/
Traverse_P(head->rchild);
return ;
}
调用后得到结果如下:
e a c b d f h g
中序遍历代码如下:
_LINK_T *Traverse_M(_LINK_T *head)
{
if (!head)
{
return ;
}
/*遍历左子树*/
Traverse_M(head->lchild);
/*访问根节点*/
printf("%c ",head->data);
/*遍历右子树*/
Traverse_M(head->rchild);
return ;
}
调用后得到结果如下:
a b c d e f g h
后序遍历代码如下:
_LINK_T *Traverse_L(_LINK_T *head)
{
if (!head)
{
return ;
}
/*遍历左子树*/
Traverse_L(head->lchild);
/*遍历右子树*/
Traverse_L(head->rchild);
/*访问根节点*/
printf("%c ",head->data);
return ;
}
调用后得到结果如下:
b d c a g h f e
完整测试代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct LINK_T //二叉树节点结构体
{
int data;
struct LINK_T *lchild;
struct LINK_T *rchild;
}_LINK_T;
_LINK_T *Create_Node(int data); //创建二叉树结构点
_LINK_T *Construct(_LINK_T *head, _LINK_T *node); //构造二叉树
_LINK_T *Traverse_P(_LINK_T *head);//先序遍历
_LINK_T *Traverse_M(_LINK_T *head);//中序遍历
_LINK_T *Traverse_L(_LINK_T *head);//后序遍历
int main(int argc, const char *argv[])
{
int i = 0;
int data[8] = {'e', 'f', 'h', 'g', 'a', 'c', 'b', 'd'};
_LINK_T *head;
head = (_LINK_T*)malloc(sizeof(_LINK_T));
memset(head, 0, sizeof(head));
head->data = 0;
head->lchild = NULL;
head->rchild = NULL;
for (i = 0; i < 8; i++)
{
Construct(head, Create_Node(data[i]));
}
Traverse_P(head);
printf("\n");
Traverse_M(head);
printf("\n");
Traverse_L(head);
printf("\n");
return 0;
}
_LINK_T *Create_Node(int data)
{
_LINK_T *node;
node = (_LINK_T*)malloc(sizeof(_LINK_T));
memset(node, 0, sizeof(node));
node->data = data;
node->lchild = NULL;
node->rchild = NULL;
return node;
}
_LINK_T *Construct(_LINK_T *head, _LINK_T *node)
{
int i = 0;
_LINK_T *temp = head;
while(1)
{
if (temp->data == 0) //第一次插入根节点
{
temp->data = node->data;
break;
}
else if (node->data < temp->data) //如果插入数值小于节点数值,则插入到节点左侧
{
if (!temp->lchild) //如果左孩子为空,则数据保存到左孩子上
{
temp->lchild = node;
break;
}
else //左孩不为空,则将指针移向左孩
{
temp = temp->lchild;
continue;
}
}
else if (node->data > temp->data) //如果插入数值大于节点数值,则插入到节点右侧
{
if (!temp->rchild) //如果右孩子为空,则数据保存到右孩子上
{
temp->rchild = node;
break;
}
else //右孩不为空,则将指针移向右孩
{
temp = temp->rchild;
continue;
}
}
}
return;
}
_LINK_T *Traverse_P(_LINK_T *head)
{
if (!head)
{
return ;
}
/*访问根节点*/
printf("%c ",head->data);
/*遍历左子树*/
Traverse_P(head->lchild);
/*遍历右子树*/
Traverse_P(head->rchild);
return ;
}
_LINK_T *Traverse_M(_LINK_T *head)
{
if (!head)
{
return ;
}
/*遍历左子树*/
Traverse_M(head->lchild);
/*访问根节点*/
printf("%c ",head->data);
/*遍历右子树*/
Traverse_M(head->rchild);
return ;
}
_LINK_T *Traverse_L(_LINK_T *head)
{
if (!head)
{
return ;
}
/*遍历左子树*/
Traverse_L(head->lchild);
/*遍历右子树*/
Traverse_L(head->rchild);
/*访问根节点*/
printf("%c ",head->data);
return ;
}
代码执行结果如下:
e a c b d f h g
a b c d e f g h
b d c a g h f e
和刚刚分析的结果一致。
二、统计二叉树中叶子节点及统计二叉树的高度
二叉树的遍历是操作二叉树的基础,二叉树的很多特性都可以通过遍历二叉树来得到,在实际应用当中,统计二叉树叶子节点的个数是非常常见的一种操作。
统计二叉树叶子数可以使用3种遍历顺序中任意一种,只是需要将访问操作变成判断该节点是否为叶子节点,如果是,叶子节点累加器加1。
二叉树的叶子的判断方法是,左子树和右子树为空。
下面是实现代码:
int Leaf_Num(_LINK_T *head, int *count)
{
if (!head)
{
return 1;
}
else
{
/*判断节点是否为叶子节点*/
if (!head->lchild && !head->rchild)
{
(*count)++;
}
/*先序遍历左子树*/
if (Leaf_Num(head->lchild, count))
{
/*先序遍历右子树*/
if (Leaf_Num(head->rchild, count))
{
return 1;
}
}
return 0;
}
}
将该代码复制到上述程序中执行,得到最终叶子节点数为3,结果正确。
统计二叉树的高度也是很常见的操作,其中使用后序遍历的顺序更符合人们的思维方式,首先分别求出左右子树的高度,在此基础上求得该树的高度,即左右子树中高度较高的一个加上1即可。具体代码如下:
int Tree_High(_LINK_T *head)
{
int h1 = 0;
int h2 = 0;
if (!head)
{
return 0;
}
else
{
h1 = Tree_High(head->lchild);
h2 = Tree_High(head->rchild);
return h1 > h2 ? (h1 + 1) : (h2 + 1);
}
}
将该代码带入上述演示代码得到树的高度为4,正确。
这里我们需要注意的是,树高度的定义博主发现有两种定义方式:
1.在严蔚敏的《数据结构(C语言版)》中二叉树的深度定义是这样的,“结点的层次从根开始定义,根为第一层,树中结点的最大层次为树的深度或高度”。
2.在张乃孝《算法与数据结构中(C语言描述)》中规定,根的层数为0。
使用第一种方式定义的话,这样就可以使用上述代码。若是使用第二种定义方式,则需将上述代码中的return 0改为return -1。
仓促成文,不当之处,尚祈方家和读者批评指正。联系邮箱1772348223@qq.com