为了账号安全,请及时绑定邮箱和手机立即绑定

数据结构基础剖析——二叉树的原理和实现

树是一种数据结构,由n(n>=1)个有限结点组成的一个具有层次关系的集合。每颗树都有一个根结点,根结点下有若干个子结点,每一个非根结点有且只有一个父结点(根节点没有父结点)。除了根结点外,每个子结点又可以分为多个不相交的子树。
树的相关术语有:
树的结点(node):包含一个数据元素及若干指向子树的分支;
孩子结点(child node):结点的子树的根称为该结点的孩子;
双亲结点:B 结点是A 结点的孩子,则A结点是B 结点的双亲;
兄弟结点:同一双亲的孩子结点;
堂兄结点:同一层上结点;
祖先结点: 从根到该结点的所经分支上的所有结点子孙结点:以某结点为根的子树中任一结点都称为该结点的子孙
结点层:根结点的层定义为1;根的孩子为第二层结点,依此类推;
树的深度:树中最大的结点层
结点的度:结点子树的个数
树的度: 树中最大的结点度。
叶子结点:也叫终端结点,是度为 0 的结点;
分枝结点:度不为0的结点;
有序树:子树有序的树,如:家族树;
无序树:不考虑子树的顺序;

二叉树是一种特殊的树,每个结点最多有两个子树的树结构,即使某结点只有一个子树,也要区分左右子树,左子树和右子树是有顺序的。它有五种基本形态:空二叉树、根和空的左右子树、根和左子树、根和右子树、根和左右子树。

图片描述
二叉树的性质表现为以下几点:
(1)深度为k的二叉树至多有2k-1个结点(k>=1)
(2)二叉树第i层上的结点数目最多为2i-1(i>=1)
(3)包含n个结点的二叉树的高度至少为(log2n)+1
(4)在任意一棵二叉树中,若终端结点的个数为n0,度为2的结点数为n2,则n0=n2+1。推论过程如下:
因为二叉树中所有结点的度数均不大于2,不妨设n0表示度为0的结点个数,n1表示度为1的结点个数,n2表示度为2的结点个数。三类结点加起来为总结点个数,于是便可得到:n=n0+n1+n2 (1)
由度之间的关系可得第二个等式:n=n00+n11+n2*2+1即n=n1+2n2+1 (2)
将(1)(2)组合在一起可得到n0=n2+1

1、满二叉树

先看下特殊的二叉树——满二叉树。满二叉树:除最后一层叶子节点(没有子节点的节点)外,每一层上的所有结点都有两个子结点。也就是说,如果一个二叉树的层数为K,且节点总数是(2^k) -1 ,比如图中层数为4,节点数为15,叶子节点为8、9~~15。在同样深度的二叉树中,满二叉树的节点个数最多,叶子树最多。

2、完全二叉树

完全二叉树是由满二叉树引出的一种效率很高的数据结构。对一棵具有n个节点的二叉树按层序排号,如果编号为i的节点与同样深度的满二叉树编号为i节点在二叉树中位置完全相同,就是完全二叉树。其中关键点是按层序编号,然后对应查找。满二叉树必须是完全二叉树,反过来不一定成立。

完全二叉树的特性:
1、同样结点数的二叉树,完全二叉树的高度最小
2、完全二叉树的叶子结点仅出现在最下边两层,并且最底层的叶子结点一定出现在左边,倒数第二层的叶子结点一定出现在右边。
3、完全二叉树中度为1的结点只有左孩子。
图片描述

3、二叉树遍历

为什么研究二叉树的遍历?因为计算机只会处理线性序列,我们研究遍历,就是把树中的结点变成某种意义的线性序列,这给程序的实现带来了好处。
二叉树遍历指从树的根节点出发,按照某种次序依次访问二叉树中所有的结点,使得每个结点被访问仅且一次。从二叉树的递归定义可知,一棵非空的二叉树由根结点及左、右子树这三个基本部分组成,因此,在任一给定结点上,可以按某种次序执行三个操作:访问结点本身(N);遍历该结点的左子树(L);遍历该结点的右子树(R)。
以上三种操作有六种执行次序:NLR、LNR、LRN、NRL、RNL、RLN。前三种次序与后三种次序是对称的,此处讨论下前三种次序。

1、NLR为前序遍历(Preorder Traversal 亦称先序遍历)访问根结点的操作发生在遍历其左右子树之前。先访问根结点,再先序遍历左子树,最后再先序遍历右子树即根—左—右。

2、LNR为中序遍历(Inorder Traversal)。访问根结点的操作发生在遍历其左右子树之中(间)。先中序遍历左子树,然后再访问根结点,最后再中序遍历右子树即左—根—右。

3、LRN为后序遍历(Postorder Traversal)。访问根结点的操作发生在遍历其左右子树之后。先后序遍历左子树,然后再后序遍历右子树,最后再访问根结点即左—右—根。

由于被访问的结点必是某子树的根,所以N(Node)、L(Left subtree)和R(Right subtree)又可解释为根、根的左子树和根的右子树。NLR、LNR和LRN分别又称为先根遍历、中根遍历和后根遍历。

以下附上源码程序,编译命令为gcc -o hello tree_exam.c。需要注意键盘输入一个结点时需要对应两个子结点,为空则用#表示。

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

//二叉树的存储结构,一个数据域,2个指针域
typedef struct BiTNode
{
    char data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;

void PreOrderTraverse(BiTree T)//二叉树的先序遍历
{
    if(T==NULL)
        return ;
    printf("%c ",T->data);
    PreOrderTraverse(T->lchild);
    PreOrderTraverse(T->rchild);
}
void InOrderTraverse(BiTree T)//二叉树的中序遍历
{
   if(T==NULL)
       return ;
   InOrderTraverse(T->lchild);
   printf("%c ",T->data);
   InOrderTraverse(T->rchild);
}
void PostOrderTraverse(BiTree T)//后序遍历
{
    if(T==NULL)
        return;
    PostOrderTraverse(T->lchild);
    PostOrderTraverse(T->rchild);
    printf("%c ",T->data);
}

void CreateBiTree(BiTree *T)
{
    char ch;
    scanf("%c",&ch);//1C#S##D##A#1## 一个结点对应两个子结点,为空则用#表示
    if(ch=='#')
        *T=NULL;
    else
    {
        *T=(BiTree)malloc(sizeof(BiTNode));
        if(!*T)
            exit(-1);
        (*T)->data=ch;//给节点的数据域赋值
		printf("输入%c的左子树\n", ch);
        CreateBiTree(&(*T)->lchild);//递归创建左子树
		printf("输入%c的右子树\n", ch);
        CreateBiTree(&(*T)->rchild);//递归创建右子树
    }
}


void DestroyBiTree(BiTree T)
{
    if(T)//如果T存在                                        
    {
        DestroyBiTree(T->lchild);
        DestroyBiTree(T->rchild);
        free(T);
    }
}

int main()
{
    BiTree T;
	printf("请输入二叉树的数据,并以#为空节点
");
    CreateBiTree(&T);
	printf("该树的先序遍历结果为:");
    PreOrderTraverse (T);
	printf("\n");
    printf("该树的中序遍历结果为:");
    InOrderTraverse(T);
	printf("\n");
    printf("该树的后序遍历结果为:");
    PostOrderTraverse(T);
	printf("\n");	
	DestroyBiTree(T);
    return 0;
}

图片描述

点击查看更多内容
6人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
Python工程师
手记
粉丝
1.8万
获赞与收藏
1565

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消