第五章 树
5.1 树的基本概念-非线性结构(分支关系和层次特性)
根结点,叶(子)结点。
树的定义是递归的:一棵树是由子树构成,子树由更小的子树构成。一棵树至少有一个结点。
结点的次数是该结点子树的个数。叶子结点次数为0。树的次数是各结点次数的最大值。m次完全树:非叶子结点的次数都为m。
父结点,子结点,兄弟(同胞)结点。
根结点所在层次为0,其他结点层次为父结点所在层次加1。
树中层次最大的结点的层次称为树的深度(Depth)或高度 (Height)。
路径:自上而下得到达,也叫树枝。路径长度,也叫树枝长度。根结点到树中其余节点一定存在路径。祖先、后代都不包括结点自身。
有序树:从左到右标好序号,尽量左不空。
5.2 树的(链式)存贮结构
一般形式(链式存贮结构)
data | pointer |
---|---|
(一)标准形式存贮结构
data | c h i l d 0 child_0 child0、 c h i l d 1 child_1 child1…… c h i l d m − 1 child_{m-1} childm−1 |
---|---|
(二)逆形式存贮结构
data | parent |
---|---|
(三)扩充标准形式
data | c h i l d 0 child_0 child0、 c h i l d 1 child_1 child1…… c h i l d m − 1 child_{m-1} childm−1 | parent |
---|---|---|
5.4 树的遍历
(1)前序遍历——先访问根结点,再前序遍历各子树
(2)后序遍历——先后序遍历各子树,再访问根结点
(3)层次遍历——先访问第0层上的根结点,然后访问第一层上各结点,依次访问结点
前序后序遍历的递归程序
//m次树 结点的常用结构
struct node {
char data; //结点的标识
struct node * child[MAXN]; };
Typedef struct node NODE;
void r_preorder(NODE *t, int m) //递归前序遍历,t:根,m:次数
{ int i;
if (t!=NULL) i
{ printf(“%c”,t->data);
for (i=0;i<m;i++)
r_preorder(t->child[i],m);
}
}
非递归前序、层次遍历程序
//非递归程序实现树的前序遍历
void s_preorder((NODE *t, int m)
{ NODE * s[MAXN];//栈:用于保存尚未遍历树的根节点的地址
int top,i;
if (t==NULL) return; //t为空则停止
S[0]=t; //入栈 top=1; //top:下一次入栈的位置
while (top>0)//栈不空
{ t=s[--top]; //栈顶结点出栈,
printf(“%c”,t->data); //访问当前“根”节点
for (i=m-1;i>=0;i--) //从右向左依次入栈
if(t ->child[i] != NULL)
s[top ++] = t ->child[i];
}
}
//实现按层次遍历
使用一个顺序存储的队列存放还没有处理的子树的根结点的地址。
void levorder(NODE *t, int m) {
NODE *q[100],*p ;
int head,tail,i;
if (t==NULL) return;
q[0]=t;
head=0; tail=1; //队列
while (head<tail)
{ p=q[head++]; //出队
printf(“%c”,p->data);
for (i=0;i<m;i++) //从左到右连续入队
if(p ->child[i] != NULL)
s[tail++] = p ->child[i];
} }
1.初值入栈
2.当栈不为空
3.退栈一个、打印
4.连续入栈。
注:非递归层次把栈换为队即可。
5.5 树的顺序存贮结构
一、双亲存储法
(1)编号:从上到下,从左到右,从0(根结点)开始,依次递增编号
(2)存储:一维数组tree[n],下标即为该结点编号,每个元素为一个结构体,包含两个成员,data和parent,data为值,parent为父结点下标。根结点的tree [0].parent 为-1
编程:已知双亲表示存储法—>标准形式存贮结构
二、括号表示法,不考5.6 二叉树
左子结点,右子结点,左子树,右子树,左儿子,右儿子。二叉树可以为空
任意次数转化为二叉树:儿子结点做左子树,同胞结点做右子树。
森林到二叉树的转换,图会画,递归程序会写。
5.7 二叉树的遍历
前序(根,左,右)、中序(左,根,右)、后序遍历(左,右,根)
三个递归程序:if(t!=null),printf……
//中序遍历+
void r_midorder(NODE *t)
{
if(t!=NULL)
{
r_midorder(t->lchild);
printf("%c",t->data);
r_midorder(t->rchild);
}
}
//前序遍历
void r_preorder(NODE *t)
{
if(t!=NULL)
{
printf("%c",t->data);
r_preorder(t->lchild);
r_preorder(t->rchild);
}
}
//后序遍历
void r_posorder(NODE *t)
{
if(t!=NULL)
{
r_posorder(t->lchild);
r_posorder(t->rchild);
printf("%c",t->data);
}
}
四个应用:
1.复制二叉树。
2.判断两棵二叉树是否等价。
3.求二叉树的高度。
4.求结点的数量。
5.求叶子结点的数量。
NODE *copy(NODE *t)//复制二叉树
{
if(t==NULL) return NULL;
root=(NODE *)malloc(sizeof(NODE *));
root->data=t->data;
root->lchild=copy(t->lchild);
root->rchild=copy(t->rchild);
return root;
}
//求树的高度:
int BiTreeDepth(BiTree t)
{
if (!T) return (-1)
else
{ if ((!T->lchild)&&(!T->rchild)) return (0)
else hL= BiTreeDepth(T->lchild);
rL= BiTreeDepth(T->人child);
return (max {hL,rL}+1)
}
}
//结点的数量
int count (struct NODE * root)
{
if (root==NULL) return (0);
lcount=count(root->lchild);
rcount=count(root->rchild);
return (lcount+rcount+1);
}
//求叶子结点的个数
void CountLeaf(BiTree T, int& count)
{ if (T) //递归程序,先考虑好结束条件
{ if ((!T->lchild)&&(!T->rchild)) //T是叶子结点
count++;
CountLeaf(T->lchild, count);
CountLeaf(T->rchild, count);
}
}
Int equaltree(t1,t2)
NODE *t1,*t2
{ if t1空并且t2空 return (1);
if (t1不空且t2不空)
if (t1和t2的数据字段相等)
if ( equaltree(t1->lchild,t2->lchild) ) return(equaltree(t1->rchild,t2->rchild);
return (0);
}
5.8 二叉树的顺序存储
满二叉树:高度为h且有 2 h + 1 − 1 2^{h+1}-1 2h+1−1个结点的二叉树
完全二叉树:也叫拟满树,除最后一层外,是满二叉树。最后一层从最右结点向左看无空位置。
二叉树的性质:
(1)如果从0开始计数二叉树的层次,则在第i层最多有
2
i
2^i
2i个 结点。(i≥0);
(2)高度为h的二叉树,最多有
2
h
+
1
−
1
2^{h+1}-1
2h+1−1个结点( h≥0 );
(3)任意一棵二叉树,如果其叶结点有n0个,次数为2的非叶 结点有n2个,则有n0=n2+1;
(4)具有n(n>0 )个结点的完全二叉树的深度为
⌊
log
2
n
⌋
\lfloor \log_2 n \rfloor
⌊log2n⌋。
注:二叉树的高度:沿用树的定义,只有一个根节点的时候,高度为0 ,空二叉树高度为-1.
完全二叉树具有如下性质:具有n(n>0)个结点的完全二叉树 的深度为 ⌊ log 2 n ⌋ \lfloor \log_2 n \rfloor ⌊log2n⌋
完全二叉树的层次存储法:
一维数组,自上到下自左到右,从0(根结点)编号,用编号做下标值。对于非完全树,可将其补成完全二叉树,添加null结点。
父结点, ⌊ ( i − 1 ) / 2 ⌋ \lfloor (i-1)/2 \rfloor ⌊(i−1)/2⌋
按前序的存储结构
(1)附加左标志位和右指针。(ltag,data,rchild)
ltag=0:结点k后面的结点是k的左子结点,有左结点。
ltag=1:结点k无左子结点。
rchild:记录其右子结点数组下标。(用-1表示空结点)
(2)附加两个标志位—左标志位和右标志位。(ltag,data,rtag)
ltag=0/rtag=0:有左结点,有右结点。
ltag=1/rtag=1:没有左结点,没有右结点。
5.9 穿线树和穿线排序
对于一棵具有n个结点的二叉树,如果按标准形式存贮,那么总共有2n个指针,其中只有(n-1)个用来指向子结点,另外(n+1)个指针是空的。
按中序遍历得到序列。
5个域: l c h i l d 、 l t a g 、 d a t a 、 r t a g 、 r c h i l d lchild、ltag、data、rtag、rchild lchild、ltag、data、rtag、rchild
两个特殊指针:根指针root,head指针指向中序遍历的第一个结点。
两个特殊结点:中序遍历第一个结点:ltag=0,最后一个结点:rtag=0。
图会画。
5.10 计算二叉树的数目
如果有n个结点,那么将有多少棵不同的子树?
1 n + 1 C 2 n n \frac{1}{n+1}C{^n_{2n}} n+11C2nn。卡塔兰数
给定前序,中序,画出二叉树。