目录
树和二叉树的定义
树的定义
树的基本术语
二叉树的定义
二叉树的性质和存储结构
二叉树的性质
二叉树的存储结构
顺序存储结构
链式存储结构
遍历二叉树和线索二叉树
遍历二叉树
先序遍历
中序遍历
后序遍历
前序遍历的递归算法
中序遍历的递归算法
后序遍历的递归算法
树和二叉树的定义
树结构是一类重要的非线性数据结构。
树的定义
树是n(n >= 0)个节点的有限集,它或为空树(n == 0),或为非空树。
对于非空树:
1.有且仅有一个称之为根的节点
2.除根结点意外的其余节点可分为m(m > 0)个互不相交的有限集T1、T2、T3...Tn,其中每一个集合本身又是一棵树,并且称为根的子树
树的结构定义是一个递归的定义,即在树的定义中又用到树的定义。它道出了树的固有特性。树还有其他的表示方式,如:嵌套集合、广义表、凹入表示法
树的基本术语
1.节点:书中的一个独立的单元,包含一个数据元素及若干个指向其子树的分支
2.节点的度:节点拥有的子树数称为节点的度
3.树的度:树的度是树内各节点度的最大值
4.叶子:度为0的节点称为叶子或终端节点
5.非终端节点:度不为0的节点称为非终端节点或分支节点。除根结点之外,非终端节点也称为内部节点
6.双亲和节点:节点的子树的根称为该节点的孩子,相应地,该节点称为孩子的双亲
7.兄弟:同一个双亲的孩子之前互称兄弟
8.祖先:从根到该节点所经分支上的所有节点
9.子孙:以某节点为根的子树中的任意节点都称为该节点的子孙
10.层次:节点的层次从根开始定义,根为第一层,根的孩子为第二层。书中任意节点的层次等于其双亲结点的层次加一
11.堂兄弟:双亲在同一层的节点互为堂兄弟
12.树的深度:树中节点的最大层次称为树的深度或者高度
13.有序树和无序树:如果将树中节点各子树堪称从左至右是有次序的(不能互换),则称该数为有序树,否则为无序树。在有序树当中最左边的子树的根称为第一个孩子,最右边的孩子称为最后一个孩子
14.森林:m(m >= 0)棵互不相交的树的集合,对于树中每个节点而言,其子树的集合即为森林
就逻辑结构而言,任何一颗树都是一个二元组Tree = (root,F),其中root是数据元素,称作树的根节点;F是包含m(m >= 0)棵树的森林,F = (T1,T2,...Tn),其中Ti = (r1,Fi)称作根root的第i棵子树;当m != 0时,在树根和其子树森林之间存在下列关系
这个定义将有助于得到森林和树与二叉树之间转换的递归定义
二叉树的定义
二叉树是n(n >= 0)个节点所构成的集合,它或为空树(n == 0),或为非空树(n > 0)。对非空树T:
1.有且仅有一个称之为根的节点
2.除根节点以外的其余节点分为两个互不相交的子集T1和T2,分别称为T的左子树和右子树,且T1和T2本身又都是二叉树
二叉树与树一样具有递归的性质,二叉树与树的区别主要有以下两点:
1.二叉树每个节点至多只有两棵子树(二叉树中不存在度大于2的节点)
2.二叉树的子树有左右之分,其次徐不能任意颠倒
二叉树的递归定义表示二叉树或为空,或由一个根节点加上两颗分别称为左子树和右子树的,互不相交的二叉树组成。由于这两棵树也是二叉树,则由二叉树的定义,它们也可以是空树,所以也就诞生了5中二叉树的基本形态:
空二叉树,仅有根节点的二叉树,右子树为空的二叉树,左子树为空的二叉树,左右节点均非空的二叉树
二叉树的性质和存储结构
二叉树的性质
二叉树具有以下重要性质
性质一:在二叉树的第i层上至多有(i >= 1)个节点
证明:利用归纳法容易证明此结论
i = 1时,只有一个根节点,显然是对的
现在假定所有的j(1 <= j < i)命题成立,即第j层上至多有个节点,那么可以证明j = i时也成立
由归纳假设,第i - 1层上至多有个节点,由于二叉树特性可得:每个节点的度至多为2,故在第i层上的最大节点数为在第i - 1层的最大节点数的二倍,也就是
性质二:深度为k的二叉树最多有(k >= 1)个节点
性质三:对任何一棵二叉树T,如果其终端节点数为n0,度为2的节点数为n2,则n0 = n2 + 1
下面介绍两种特殊形态的二叉树:满二叉树和完全二叉树
满二叉树:深度为k且含有个节点的二叉树
满二叉树的特点是:每一层上的节点数都是最大节点数,即每一层i的节点数都具有最大值
可以对满二叉树的节点进行连续编号,约定编号从根节点起,自上而下,从左至右。由此可以引出完全二叉树的定义
完全二叉树:深度为k的,有n个节点的二叉树,当且进档器每一个节点都与深度为k的满二叉树中编号从1至n的节点一一对应时,称之为完全二叉树
完全二叉树的特点是:
1.叶子节点只可能在层次最大的两层上出现
2.对任一节点,若其右分支下的子孙的最大层次为l,则其左分支下的子孙的最大层次必为l或l + 1
性质四:具有n个节点的完全二叉树的深度为
表示不大于x的最大整数,反之,也可表示为不小于x的最小整数
性质五:如果对一棵有n个节点的完全二叉树(其深度为)的节点按层序编号(从第1层到第层,每层从左到右),则对任意节点i(1 <= i <= n),以下结论成立
1.如果i = 1,则节点i是二叉树的根,无双亲;如果i > 1,则其双亲PARENT(i)是节点(i / 2)
2.如果2i > n,则节点i无左孩子(节点i为叶子节点),否则其左孩子LCHILD(i)是节点(2i)
3.如果2i + 1 > n,则节点无右孩子;否则其有孩子RCHILD(i)是节点(2i + 1)
二叉树的存储结构
顺序存储结构
#define MAXSIZE 100
typedef TELemType SqBiTree[MAXSIZE];
SqBiTree bt;
顺序存储结构使用一组地址连续的存储单元来存储数据元素,为了能够在存储结构中反映出节点之间的逻辑关系,必须将二叉树中的节 依照一定的规律安排在这组单元中
对于完全二叉树,只要从根起按层序存储即可,依次自上而下、自左至右存储节点元素,即将完全二叉树上编号为i的节点元素存储在如上定义的一维数组中下标为i - 1的分量中
对于一般二叉树,则应将其每个节点与完全二叉树上的节点相对照,存储在一维数组的相应分量中
由此可见,这种顺序存储结构仅适用于完全二叉树,因为在最坏的情况下,一个深度为k且只有k个节点的单支树(树中不存在度为2的节点)却需要长度为的一维数组,这造成了存储空间的极大浪费,因此对于一般二叉树,更适合采取链式存储结构
链式存储结构
设计不同的节点结构可构成不同形式的链式存储结构。由二叉树定义可知,二叉树的节点由一个数据元素和分别指向其左、右子树两个分支构成,则表示二叉树的链表中的节点至少包含三个域:数据域和左、右指针域。又是,为了便于找到节点的双亲,还可在节点结构中增加一个指向其双亲节点的指针域。利用这两种结构所得的二叉树的存储结构分别称为二叉链表和三叉链表。链表的头指针指向二叉树的根节点。容易证得,在含有n个节点的二叉链表中有n + 1个空链域
在不同的存储结构中,实现二叉树的操作方法也不同,例如找节点x的双亲PARENT(T,e),在三叉链表中很容易实现,而在二叉链表中则需从根指针出发巡查。由此,在具体应用中采用什么存储结构,除考虑二叉树的形态之外还应考虑需进行哪儿种操作。
typedef struct BiNode
{
TElemTpye data;
struct BiNode *lchild,*rchild;
}BiNode,*BiTree;
遍历二叉树和线索二叉树
遍历二叉树
遍历二叉树是指按某条搜索路径寻访树中的每个节点,使得每个节点被访问依次,而且仅被访问依次。访问的含义很广,可以是对节点进行各种处理,包括输出节点的信息,对节点进行运算和修改等。遍历二叉树是二叉树最基本的操作,也是二叉树其他各种操作的基础。遍历的是指是对二叉树进行线性化,即遍历的结果是讲非线性结构树中的节点排成一个线性序列。由于二叉树的每个节点都有可能有两颗子树,因此需要找寻一种规律,一遍是二叉树上的节点能排列在一个线性队列上,从而便于遍历
回顾二叉树的递归定义可知,二叉树由三个基本单元组成:根节点,左子树,右子树。因此,若能依次遍历这三部分,便是遍历了整个二叉树,假设用LDR分别表示左子树、访问根节点和右子树,那么可有LDR、LRD、DLR、DRL、RLD、RDL六中国遍历二叉树的方案,若限定先左后右,则只剩三种情况:DLR、LDR、LRD,我们称之为:先序遍历、中序遍历、后序遍历。基于二叉树的递归定义,可得下述遍历二叉树的递归算法定义:
先序遍历
先序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空,否则:
1.访问根节点
2.先序遍历左子树
3.先序遍历右子树
先序遍历5.5二叉树得:-+a*b-cd/ef
中序遍历
中序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空,否则:
1.中序遍历左子树
2.访问根节点
3.中序遍历右子树
中序遍历5.5二叉树得:a+b*c-d-e/f
后序遍历
后序遍历二叉树的操作定义如下:
若二叉树为空,则操作为空,否则:
1.后序遍历左子树
2.后序遍历右子树
3.访问根节点
后序遍历5.5二叉树得:abcd-*+ef/-
前序遍历的递归算法
void PreOrderTraverse(BiTree T)
{
if(T)
{
cout << T -> data;
PreOrderTraverse(T -> lchild);
PreOrderTraverse(T -> rchild);
}
}
中序遍历的递归算法
void InOrderTraverse(BiTree T)
{
if(T)
{
InOrderTraverse(T -> lchild);
cout << T -> data;
InOrderTraverse(T -> rchild);
}
}
后序遍历的递归算法
void PoOrderTraverse(BiTree T)
{
if(T)
{
PoOrderTraverse(T -> lchild);
PoOrderTraverse(T -> rchild);
cout << T -> data;
}
}
也可利用我们之前学过的栈来将递归算法改为非递归算法
1.工作记录中包含两项,其一是递归调用的语句编号,其二是指向根节点的指针,则当栈顶记录中的指针非空时,应遍历左子树,即左子树根的指针进栈
2.若栈顶记录中的指针值为空,则应退至上一层,若是从左子树返回,则应当访问当前层(栈顶记录)中指针所指的根节点
3.若是从右子树返回,则表明当前层的遍历结束,应继续退栈。从另一个角度看,这意味着遍历右子树是不再需要保存当前层的根指针,直接修改栈顶记录中的指针即可
现给出中序遍历的非递归算法
void InOrderTreaverse(BiTree T)
{
InitStack(S);
BiNode p = T,q = new BiNode;
while(p || !StackEmpty(S))
{
if(p)
{
Push(S,p);
p = p -> lchild;
}
else
{
Pop(S,q);
cout << q -> data;
p = q -> rchild;
}
}
}
无论是递归还是非递归遍历二叉树,因为每个节点被访问依次,则不论按哪儿一种次序进行遍历,对含n个节点的二叉树,其时间复杂度为O(n)。所需辅助空间为遍历过程中栈的最大容量,即树的深度,最坏情况下为n,则空间复杂度也为O(n)
二叉树的先序、中序、后续遍历是最常用的3种遍历方式。此外,还有一种按层次遍历二叉树的方式,这种方式按照“从上到下,从左到右”的顺序遍历二叉树,即先遍历二叉树第一层的节点,然后是第二层的节点,直至最底层的节点,对每一层的遍历按照从左到右的次序进行。层序遍历不是一个递归过程,层序遍历算法的实现可以借助队列这种数据结构