数据结构-二叉树

在有序树中有一类最特殊,也是最重要的树,称为二叉树(binarytree)。二叉树是树结构中最简单
的一种,但却有着十分广泛的应用。

二叉树的定义
1.定义
二叉树是有n(n≥0)个结点的有限集合,它有如下一些特点。
(1)该集合可以为空(n=0)。
(2)该集合可以由一个根结点及两个不相交的子树组成非空树,这两个子树分别称为左子树和右子树。
(3)左子树和右子树同样又都是二叉树。
在一棵非空二叉树中,每个结点至多只有两棵子树,分别称为左子树和右子树,并且左、右子树的次序不能任意交换。因此,二叉树是特殊的有序树。

2.二叉树的形态
根据定义,二叉树可以有5种基本形态,
(a)空二叉树;
(b)仅有根结点的二叉树;
(c)右子树为空的二叉树;
(d)左子树为空的二叉树;
(e)左、右子树均非空的二叉树。

3.二叉树的基本操作
二叉树的基本操作通常有以下几种。
(1)CreateBT( )创建一棵二叉树。
(2)ShowTree(BTT)按凹入法(或圆括号法等方法)显示二叉树。
(3)Preorder(BT
T)按先序(根、左、右)遍历二叉树上所有结点。
(4)Inorder(BTT)按中序(左、根、右)遍历二叉树上所有结点。
(5)Postorder(BT
T)按后序(左、右、根)遍历二叉树上所有结点。
(6)Levelorder(BTT)按层次遍历二叉树上所有结点。
(7)Leafnum(BT
T)求二叉树叶结点总数。
(8)TreeDepth(BT*T)求二叉树的深度。

二叉树的性质
性质1 一棵非空二叉树的第i层上最多有2i-1个结点(i≥1)。
一棵非空二叉树的第一层有1个结点,第二层最多有2个结点,第三层最多有4个结点,依此类推,利用归纳法即可证明第i层上最多有个2i-1结点。

性质2 深度为h的二叉树中,最多具有2h-1个结点(h≥1)。
(1)满二叉树 一棵深度为h,且有2h-1个结点的二叉树称为满二叉树。深度为4的满二叉树,其特点是每一层上的结点都具有最大的结点数。如果对满二叉树的结点进行连续的编号,约定编号从根结点起,从上往下,自左向右,由此可以引出完全二叉树的定义。
(2)完全二叉树 深度为h,有n个结点的二叉树,当且仅当其每个结点的编号都与深度为h的满二叉树中从1至n的结点的编号一一对应时,称此二叉树为完全二叉树。
完全二叉树除最后一层外,其余各层都是满的,并且最后一层或者为满,或者仅在右边 缺少连续的若干个结点。

性质3 对于一棵有n个结点的完全二叉树,若按满二叉树的方法对结点进行编号,则对于任意序号为i的结点,有以下性质。
(1)若i=1,则序号为i的结点是根结点;若i>1,则序号为i的结点的父结点的序号为i/2。
(2)若2i≤n,则序号为i的结点的左孩子结点的序号为2i;若2i>n,则序号为i的结点无左孩子。
(3)若2i+1≤n,则序号为i的结点的右孩子结点的序号为2i+1;若2i+1>n,则序号为i的结点无右孩子。

性质4 具有n(n>0)个结点的完全二叉树(包括满二叉树)的深度(h)为└log2n」+1。

性质5 对于一棵非空的二叉树,设n0、n1、n2分别表示度为0、1、2的结点个数,则有:n0=n2+1。

二叉树的存储
二叉树的存储结构也分为顺序存储和链接存储两种存储结构。
1.顺序存储结构
二叉树的顺序存储,就是用一组连续的存储单元存放二叉树中的结点。一般可以采用一维数组或二维数组的方法进行存储。
1)一维数组存储法
二叉树中各结点的编号与等深度的完全二叉树中对应位置上结点的编号相同。其编号过程为:首先把根结点的编号定为1,然后按照层次从上至下、从左到右的顺序,对每一个结点进行编号。当双亲结点为i时,其左孩子的编号为2i,其右孩子的编号为2i+1。
对于一般的二叉树,如果按从上至下和从左到右的顺序将树中的结点顺序存储在一维数组中,则数组元素下标之间的关系不能够反映二叉树中结点之间的逻辑关系,只有增加一些并不存在的空结点,使之成为一棵完全二叉树的形式,才能用一维数组进行存储。显然,这种存储结构会造成大量的空间浪费。
对于完全二叉树和满二叉树,这种顺序存储结构既能够最大限度地节省存储空间,又可以利用数组元素的下标值确定结点在二叉树中的位置,因为完全二叉树上编号为i的结点元素存储在一维数组中下标为i-1的分量中。
2)静态链表存储法
用数组描述的链表,称为静态链表。静态链表这种存储结构与顺序表一样,需要预先为其分配一个较大的数组空间,但与顺序表不同的是,静态链表在插入和删除操作时不需移动元素,仅需修改指针的指向关系即可,故仍具有链式存储结构的主要优点。
静态链表除了可以用来描述线性结构之外,也可以用来存储二叉树这种非线性结构。静态链表的二叉树结点结构可定义如下。
# define MAXLEN 10
typedef struct
{
datatype data; //存储结点标志
int lchild; //存储左孩子结点在静态链表数组中的下标
int rchild; //存储右孩子结点在静态链表数组中的下标
}StaticLinkListNode; //静态链表的结点类型
typedef struct
{
StaticLinkListNode list[MAXLEN]; //存储结点标志
int root; //二叉树根结点的下标
}StaticLinkList; //静态链表的类型
设二叉树的结点数为n,则静态链表的预设长度MAXLEN≥n。

顺序存储结构小结:
(1)当二叉树为满二叉树或完全二叉树时,采用一维数组存储可以节省存储空间。
(2)当二叉树层数高而结点较少时,采用静态链表存储比较好,并且这种结构插入或删除结点时均不需移动任何结点,比较方便。
(3)一维数组存储的优点是查找父子结点的位置非常方便,其缺点是进行插入或删除操作要进行大量的数据移动。
(4)静态链表存储结构便于在没有指针类型的高级程序设计语言中使用链表结构。
(5)顺序存储的这两种实现方式的共同缺点是均需要预设结点存储空间,并且存储空间的扩充不太方便。
2.链式存储结构
二叉树的链式存储结构是用链表来表示二叉树,即用链指针来指示结点的逻辑关系。通常有下面两种形式。
1)二叉链表存储
二叉链表结点由一个数据域和两个指针域组成,其具体结构如下。
[插图]
其中,data为数据域,用于存放结点的数据信息;lchild为左指针域,用于存放该结点左子树根结点的地址;rchild为右指针域,用于存放该结点右子树根结点的地址。
当左子树或右子树不存在时,相应的指针域值为空,用符号“∧”表示。

容易证明,在含有n个结点的二叉链表中有n+1个空指针域。利用这些空指针域存储其他有用信息,从而可以得到另外一种存储结构——线索化链表。
二叉链表是二叉树最常用的存储方式,二叉树的二叉链表描述。
typedef struct BT //定义二叉树结构体
{
datatype data; //定义数据域
struct bt *lchild; //定义结点的左指针
struct bt *rchild; //定义结点的右指针
}BT; //定义二叉树结点结构体的类型
2)三叉链表存储
三叉链表结点由一个数据域和三个指针域组成,其具体结构如下。
[插图]
其中:data为数据域,用于存放结点的数据信息;lchild为左指针域,用于存放该结点左子树根结点的地址;rchild为右指针域,用于存放该结点右子树根结点的地址;parent为父指针域,用于存放该结点的父结点的存储地址。
这种存储结构既便于查找左、右子树中的结点,又便于查找父结点及其祖先结点,但付出的代价是增加了存储空间的开销。

遍历二叉树
二叉树的遍历是指按某种顺序访问二叉树中的所有结点,使得每个结点都被访问,并且仅被访问一次。通过一次遍历,使二叉树中结点的非线性序列转变为线性序列。也就是说,使遍历的结点序列之间产生一对一的关系。
由二叉树的递归定义可知,一棵二叉树由根结点(D)、根结点的左子树(L)和根结点的右子树(R)三部分组成。因此,只要依次遍历这三个部分,就可以遍历整个二叉树。若以D、L、R分别表示访问根结点、遍历根结点的左子树、遍历根结点的右子树,则二叉树的遍历方式有6种不同的组合,即DLR、LDR、LRD、DRL、RDL和RLD。如果限定先左后右的次序,那么,就只有DLR、LDR和LRD三种遍历方式。
1.先序遍历
先序遍历(DLR)也称为先根遍历,其递归过程如下。
若二叉树为空,则遍历结束。否则,按以下顺序遍历:访问根结点;先序遍历根结点的左子树;先序遍历根结点的右子树。
先序遍历的递归算法如下。
void PreOrder(BT *T) //先序遍历二叉树BT
{
if (T! =NULL) //树不为空才能访问其结点
{
printf(T-> data); //输出结点的数据域
Preorder(T-> lchild); //先序递归遍历左子树
Preorder(T-> rchild); //先序递归遍历右子树
}
}

2.中序遍历
中序遍历(LDR)也称为中根遍历,其递归过程如下。
若二叉树为空,则遍历结束。否则,按以下顺序遍历:中序遍历根结点的左子树;访问根结点;中序遍历根结点的右子树。
中序遍历递归算法如下。
void Inorder(BT *T) //中序遍历二叉树BT
{
if(T! =NULL) //树不为空才能访问其结点
{
Inorder(T-> lchild); //中序递归遍历左子树
printf(T-> data); //输出结点的数据域
Inorder(T-> rchild); //中序递归遍历右子树
}
}

3.后序遍历
后序遍历(LRD)也称为后根遍历,其递归过程如下。
若二叉树为空,则遍历结束。否则,按以下顺序遍历:后序遍历根结点的左子树;后序遍历根结点的右子树;访问根结点。
后序遍历递归算法如下。
void Postorder (BT *T) //后序遍历二叉树BT
{
if(T! =NULL) //树不为空才能访问其结点
{
Postorder(T-> lchild); //后序递归遍历左子树
Postorder(T-> rchild); //后序递归遍历右子树
printf(T-> data); //输出结点的数据域
}
}

4.层次遍历
按照自上而下(从根结点开始),从左到右(同一层)的顺序逐层访问二叉树上的所有结点,这样的遍历称为按层次遍历。
按层次进行遍历时,当一层结点访问完后,接着访问下一层的结点,先遇到的结点先
访问,这与队列的操作原则是一致的。因此,在进行层次遍历时,可设置一个数组来模拟队列,用于保存被访问结点的子结点的地址。遍历从二叉树的根结点开始,首先将根结点指针入队列,然后从队头取出一个元素,每取一个元素,则执行下面的两个操作。
(1)访问该元素所指结点。
(2)若该元素所指结点的左、右孩子结点非空,则将该元素所指结点的左孩子指针和右孩子指针依次入队。
此过程不断进行,直到队空为止。
在下面的层次遍历算法中,二叉树以二叉链表方式存储,一维数组q[MAXLEN]用于实现队列,lchild和rchild分别是被访问结点的左、右指针。
层次遍历算法如下。
void Levelorder(BT *T) //按层次遍历二叉树BT
{
int i=0,j=0;
BT *q[MAXLEN],*p; //设置一个数组来模拟队列
p=T;
if(p! =NULL) //若二叉树非空,则根结点地址入队
{
q[i]=p; j++;
} //i指示队头元素的下标,j指示队尾后面的空单元
while(i! =j) //i! =j时表示队列不为空
{
p=q[i];
i++; //出队一个元素
printf(p-> data); //访问出队元素结点的数据域
if(p-> lchild! =NULL) //将出队元素结点的左孩子结点入队列
{
q[j]=p-> lchild;
j++;
}
if(p-> rchild! =NULL) //将出队元素结点的右孩子结点入队列
{
q[j]=p-> rchild;
j++;
}
}
}

恢复二叉树
任意一棵二叉树结点的先序序列和中序序列都是唯一的。那么能否根据结点的先序序列和中序序列来唯一地确定一棵二叉树呢?答案是肯定的。
在统一绘图软件或其他绘图软件中存在着这样的问题:如何存储一个用树表示的图形数据结构?在研制统一绘图软件系统时是采用如下办法来处理的。
(1)对于用链表结构表示的图形数据结构,去掉每一个结点的指针项,只按结点的中序序列存储,并给出这棵树的前序(或后序)“序表”。
(2)图形结构调入内存时,由中序的结点表及“序表”形成的前序和中序数组(或后序和中序数组),来恢复图形数据结构。
二叉树的先序遍历是先访问根结点,然后再遍历根结点的左子树,最后遍历根结点的右子树。即在先序序列中,第一个结点必定是二叉树的根结点。
中序遍历则是先遍历左子树,然后访问根结点,最后再遍历右子树。这样根结点在中序序列中必然将中序序列分割成两个子序列,前一个子序列是根结点的左子树的中序序列,而后一个子序列则是根结点的右子树的中序序列。
根据这两个子序列,先由先序序列确定第一个结点为根结点;知道根结点后,按中序序列可以划分左、右子树。在先序序列中,左子树序列的第一个结点是左子树的根结点,右子序列的第一个结点是右子树的根结点。这样,就确定了二叉树的3个结点。同时,左子树和右子树的根结点又可以分别把左子序列和右子序列划分成两个子序列,如此递归下去,当取尽先序序列中的结点时,便可以恢复一棵二叉树。
1.由前序和中序恢复二叉树
由前序和中序恢复二叉树的具体步骤如下。
(1)根据前序序列确定树的根结点(第一个结点),根据中序序列确定左子树和右子树。
(2)分别找出左子树和右子树的根结点,并把左、右子树的根结点连到父(father)结点上去。
(3)再对左子树和右子树按此法找根结点和左、右子树,直到子树只剩下1个结点或2个结点,或者为空为止。

2.由中序和后序恢复二叉树
由二叉树的后序序列和中序序列也可唯一地确定一棵二叉树,其具体方法如下。
(1)根据后序序列找出根结点(最后一个结点),根据中序序列确定左、右子树。
(2)分别找出左子树和右子树的根结点,并把左、右子树的根结点连到父(father)结点上去。
(3)再对左子树和右子树按此法找根结点和左、右子树,直到子树只剩下一个结点或两个结点,或者为空为止。然后,再对左子树进行分解,由后序序列可知,B是左子树的根结点;又从中序序列知道,B的左子树只有一个结点C,B的右子树有E、D两个结点。接着对右子树进行分解,由后序序列可知,F为右子树的根结点;再根据右子树的中序可知,结点F把其余结点分成两部分,即左子树有G、H两个结点,右子树仅有J、I两个结点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值