数据结构期末复习(第六章 树与二叉树)
文章目录
Part 1、知识点总结
1.1树的基本概念
树(Tree)是n(n≧0)个结点的有限集合T。
若n=0时称为空树,否则:
- 有且只有一个特殊的称为树的根(Root)结点;它没有直接前驱,但是有零个或多个直接后继。
- 若n>1时,其余的结点被分为m(m>0)个互不相交的子集T1, T2, T3…Tm,其中每个子集本身又是一棵树,称其为根的子树(Subtree)。
- 每个子树的根节点有且仅有一个直接前驱,但有零个或多个直接后继。
1.2 树的基本术语
- 结点(node):一个数据元素及其若干指向其子树的分支。
- 结点的度(degree) :结点所拥有的子树数称为结点的度。
- 树的度:树中结点度的最大值称为树的度。
- 层次:规定树中根结点的层次为1,其余结点的层次等于其双亲结点的层次加1。若某结点在第l(l≧1)层,则其子结点在第l+1层。
- 树的深度(depth):树中结点的最大层次值,又称为树的高度。
- 有序树和无序树:对于一棵树,若其中每一个结点的子树(若有)具有一定的次序,则该树称为有序树,否则称为无序树。
- 森林(forest):是m(m≧0)棵互不相交的树的集合。显然,若将一棵树的根结点删除,剩余的子树就构成了森林。
- 叶子(left)结点、非叶子结点:
树中度为0的结点称为叶子结点(或终端结点)。
相对应地,度不为0的结点称为非叶子结点(非终端结点或分支结点)。
除根结点外,分支结点又称为内部结点。 - 孩子结点、双亲结点、兄弟结点、堂兄弟结点
一个结点的直接后继称为该结点的孩子结点;
一个结点的直接前驱称为该结点的双亲结点;
同一双亲结点的所有子结点互称为兄弟结点;
同一层次不同双亲的子结点互称为堂兄弟结点; - 结点的层次路径、祖先、子孙
从根结点开始,到达某结点p所经过的所有结点成为结点p的层次路径(有且只有一条)。
结点p的层次路径上的所有结点(p除外)称为p的祖先(ancester) 。
一个结点的直接后继和间接后继都称为该结点的子孙结点(descent)。
1.3 树的表示形式
-
倒悬树。是最常用的表示形式。
-
嵌套集合。是一些集合的集体,对于任何两个集合,或者不相交,或者一个集合包含另一个集合。
-
广义表形式。
-
凹入法表示形式(类似磁盘目录)。
2.1 二叉树的概念
- 定义:满足以下两个条件的树型结构叫做二叉树(Binary tree) 每个结点的度都不大于2; 每个结点的左右孩子结点不能颠倒。
- 基本形态:
2.2 二叉树的性质
-
性质1:在非空二叉树的第i层上至多有2i-1个结点(i≧1)。
-
性质2:深度为k的二叉树至多有2k-1个结点(k≧1) 。
-
性质3:对任何一棵二叉树,若其叶子结点数为n0,度为2的结点数为n2,则n0=n2+1。
-
满二叉树:一棵深度为k且有2k-1个结点的二叉树称为满二叉树(Full Binary Tree)。
-
满二叉树的特点:
每层结点都是满的,即每层结点都具有最大结点数;
可对满二叉树的结点进行连续编号,若规定从根结点开始,按“自上而下、自左至右”的原则进行。 -
完全二叉树(Complete Binary Tree):如果深度为k,结点为n的二叉树,结点1—n的位置序号与深度为k的满二叉树的结点1~n位置序号一一对应,该二叉树称为完全二叉树。
-
注意:满二叉树必为完全二叉树,而完全二 叉树不一定是满二叉树。
-
完全二叉树的特点:
若完全二叉树的深度为k ,则所有的叶子结点都出现在第k层或k-1层。
对于任一结点,如果其右子树的最大层次为l,则其左子树的最大层次为l或l+1。 -
性质4:n个结点的完全二叉树深度为 ㏒2n +1
-
性质5:具有n个结点的完全二叉树按自上而下和自左至右的顺序对所有结点从1进行编号,
则对于编号为i(1≦i≦n)的结点:
若i=1:则结点i是二叉树的根,无双亲结点;若i>1,则其双亲结点编号是 i\2 。
若2i>n:则结点i无左孩子;否则其左孩子结点编号是2i。
如果2i+1>n:则结点i无右孩子;否则,其右孩子结点编号是2i+1。
2.3 二叉树的顺序存储结构
顺序存储结构:用一组连续的存储单元来存放二叉树的数据元素(下标从1开始)
对于一般二叉树,我们必须按照完全二叉树的形式来存储。
2.4 二叉树的链式存储结构
二叉树的每个结点最多有两个孩子结点和一个双亲结点。
二叉链表结点。每个结点有三个域:数据域,左孩子域、右孩子域。
结点:
typedef struct BTNode
{ ElemType data ;
struct BTNode *Lchild , *Rchild ;
}BTNode ;
三叉链表结点。除二叉链表的三个域外,再增加一个指针域,指向该结点的父结点。
3.1 二叉树的遍历及其应用
3.1.1 概念
- 遍历:是指按指定的规律对二叉树中的每个结点访问一次且仅访问一次。
- 二叉树基本组成:根结点(D)、左子树(L)、右子树®。
- 六种遍历方案:DLR、DRL、LDR、RDL、LRD、RLD。
- 规定先左后右,则只有三种情况: DLR——先(根)序遍历。 LDR——中(根)序遍历。 LRD——后(根)序遍历。
3.1.2 先序遍历
递归算法 若二叉树为空,则遍历结束;否则:
- ⑴ 访问根结点
- ⑵ 先序遍历左子树(调用算法本身)
- ⑶ 先序遍历右子树(调用算法本身)
void PreTraverse(BTNode *T)
{ if (T!=NULL) {
visit(T->data) ; /* 访问根结点 */
PreTraverse(T->Lchild) ;/* 访问左孩子 */
PreTraverse(T->Rchild) ;/* 访问右孩子 */
}
}
(2) 非递归算法
- 若二叉树为空,则返回;否则,令p=T;
⑴ 访问p所指向的结点;
⑵ q=p->Rchild ,若q不为空,则q进栈;
⑶ p=p->Lchild ,若p不为空,转(1),否则转(4);
⑷ 退栈到p ,转(1),直到栈空为止。
#define MAX_NODE 50
void PreTraverse( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T, *q ;
int top=0 ;
if (T==NULL) printf(“ Binary Tree is Empty!\n”) ;
else { do
{ visit( p-> data ) ; q=p->Rchild ;
if ( q!=NULL ) stack[top++]=q ;
p=p->Lchild ;
if (p==NULL) { p=stack[top] ; top-- ; }
}while (p!=NULL) ;
}
}
3.1.2 中序遍历
- (1) 递归算法 若二叉树为空,则遍历结束;否则:
- ⑴ 先序遍历左子树(调用算法本身)
- ⑵ 访问根结点
- ⑶ 先序遍历右子树(调用算法本身)
void InTraverse(BTNode *T)
{ if (T!=NULL) {
InTraverse(T->Lchild) ;/* 访问左孩子 */
visit(T->data) ; /* 访问根结点 */
InTraverse(T->Rchild) ;/* 访问右孩子 */
}
}
-
(2) 非递归算法 若二叉树为空,则返回;否则,令p=T;
-
⑴ 若p不为空,p进栈, p=p->Lchild ;
-
⑵否则(即p为空),退栈到p,访问p所指向的结点
-
⑶ p=p->Rchild ,转(1); 直到栈空为止。
#define MAX_NODE 50
void InorderTraverse( BTNode *T)
{ BTNode *Stack[MAX_NODE] ,*p=T ;
int top=0 , bool=1 ;
if (T==NULL) printf(“ Binary Tree is Empty!\n”) ;
else { do
{ while (p!=NULL)
{ stack[++top]=p ; p=p->Lchild ; }
if (top==0) bool=0 ;
else { p=stack[top] ; top-- ;
visit( p->data ) ; p=p->Rchild ; }
} while (bool!=0) ;
}
}
3.1.3 后序遍历
(1) 递归算法
若二叉树为空,则遍历结束;否则:
⑴ 先序遍历左子树(调用算法本身)
⑵ 先序遍历右子树(调用算法本身)
⑶ 访问根结点
void LaTraverse(BTNode *T)
{ if (T!=NULL) {
LaTraverse(T->Lchild) ;/* 访问左孩子 */
LaTraverse(T->Rchild) ;/* 访问右孩子 */
visit(T->data) ; /* 访问根结点 */
}
}
(2) 非递归算法
在后序遍历中,根结点是最后被访问的。
因此,在遍历过程中,当搜索指针指向某一根结点时,不能立即访问,而要先遍历其左子树,此时根结点进栈。
当其左子树遍历完后再搜索到该根结点时,还是不能访问,还需遍历其右子树。所以,此根结点还需再次进栈。
当其右子树遍历完后再退栈到到该根结点时,才能被访问。
因此,设立一个状态标志变量tag :
其次,设两个堆栈S1、S2 ,S1保存结点,S2保存结点的状态标志变量tag 。S1和S2共用一个栈顶指针。
(2) 非递归算法
若二叉树为空,则返回;否则,令p=T;
⑴ 第一次经过根结点p,不访问:
p进栈S1 , tag 赋值0,进栈S2,p=p->Lchild 。
⑵ 若p不为空,转(1),否则,取状态标志值tag
⑶ 若tag=0:对栈S1,不访问,不出栈;修改S2栈顶元素值(tag赋值1) ,取S1栈顶元素的右子树,即p=S1[top]->Rchild ,转(1);
⑷ 若tag=1:S1退栈,访问该结点;
直到栈空为止。
#define MAX_NODE 50
void PostorderTraverse( BTNode *T)
{ BTNode *S1[MAX_NODE] ,*p=T ;
int S2[MAX_NODE] , top=0 , bool=1 ;
if (T==NULL) printf(“Binary Tree is Empty!\n”) ;
else { do{ while (p!=NULL)
{ S1[++top]=p ; S2[top]=0 ; p=p->Lchild ; }
if (top==0) bool=0 ;
else if (S2[top]==0){ p=S1[top]->Rchild ; S2[top]=1 ; }
else { p=S1[top] ; top-- ; visit( p->data ) ; p=NULL ; }
/* 使循环继续进行而不至于死循环 */
} while (bool!=0) ;
}
}
3.1.4 层次遍历
层次遍历二叉树:是从根结点开始遍历,按层次次序“自上而下,从左至右”访问树中的各结点。
若二叉树为空,则返回;否则,令p=T,p入队;
⑴ 队首元素出队到p;
⑵访问p所指向的结点;
⑶将p所指向的结点的左、右子结点依次入队。直到队空为止。
#define MAX_NODE 50
void LevelorderTraverse( BTNode *T)
{ BTNode *Queue[MAX_NODE] ,*p=T ;
int front=0 , rear=0 ;
if (p!=NULL)
{ Queue[++rear]=p; /* 根结点入队 */
while (front<rear)
{ p=Queue[++front]; visit( p->data );
if (p->Lchild!=NULL)
Queue[++rear]=p->Lchild; /* 左结点入队 */
if (p->Rchild!=NULL)
Queue[++rear]=p->Rchild; /* 右结点入队 */
}
}}
3.2 二叉树的遍历应用
二叉树的遍历算法是一个重要基础
注意:
(1)重点理解访问根结点操作的含义
(2)对具体的问题需要考虑遍历的次序
遍历应用(二叉树):
(1)输出所有结点
(2)输出二叉树的叶子节点
(3)统计叶子结点数目
(4)建立二叉链表方式存储的二叉树
(5)求二叉树的高度
3.2.1 输出二叉树的所有结点
思路:按照先序遍历走遍树种所有结点,并输出
void PreOrder(BTNode *T)
{ if(T!=NULL){
printf(“%c”,T->data);
PreOrder(T->Lchild);
PreOrder(T->Rchild);
}
}
3.2.2 输出二叉树的叶子节点
思路:遍历过程中判断结点是否为叶子结点
void PreOrder(BTNode *T)
{ if(T!=NULL){
if(T->Lchild==NULL&&T->Rchild==NULL)
printf(“%c”,T->data);
PreOrder(T->Lchild);
PreOrder(T->Rchild);
}}
3.2.3 统计叶子结点数目
思路:遍历过程中判断结点是否为叶子结点,是num++
int num=0;
int PreOrder(BTNode *T)
{ if(T!=NULL){
if(T->Lchild==NULL&&T->Rchile==NULL)
num++;
PreOrder(T->Lchild);
PreOrder(T->Rchild);}
return num;
3.2.4二叉树的二叉链表创建
对一棵二叉树进行“扩充”,就可以得到由该二叉树所扩充的二叉树。
每读入一个结点值就进行分析:
◆ 若是扩充结点值:令根指针为NULL;
◆ 若是(正常)结点值:动态地为根指针分配一个结点,将该值赋给根结点,然后递归地创建根的左子树和右子树。
else
{ T=(BTNode *)malloc(sizeof(BTNode)) ;
T–>data=ch ;
T->Lchild=Preorder_Create_BTree(T->Lchild) ;
T->Rchild=Preorder_Create_BTree(T->Rchild) ;
return(T) ;
}
}
3.2.5 求二叉树的高度
利用层次遍历算法可以直接求得二叉树的深度
#define MAX_NODE 50
int search_depth( BTNode *T)
{ BTNode *Queue[MAX_NODE] ,*p=T;
int front=0 , rear=0, depth=0, level ;
// level总是指向访问层的最后一个结点在队列的位置
if (T!=NULL)
{ Queue[++rear]=p; /* 根结点入队 */
level=rear ; /* 根是第1层的最后一个节点 */
while (front<rear)
{ p=Queue[++front]; /*根结点出队*/
if (p->Lchild!=NULL)
Queue[++rear]=p->Lchild; /* 左结点入队 */
if (p->Rchild!=NULL)
Queue[++rear]=p->Rchild; /* 右结点入队 */
if (front==level) // 正访问的是当前层的最后一个结点
{ depth++ ; level=rear ; }
}} return depth; }
3.3 线索二叉树
3.3.1 基本概念
遍历二叉树是按一定的规则将树中的结点排列成一个线性序列。
不能直接得到结点在遍历序列中的直接前驱和直接后继。
充分利用二叉链表中的空链域,将遍历过程中的结点前驱、后继信息保存下来。
问题:设一棵二叉树有n个结点,有多少个空链域?
设一棵二叉树有n个结点,则有n-1条边(指针连线) , 而n个结点共有2n个指针域(Lchild和Rchild) ,显然有n+1个空闲指针域未用。
则可以利用这些空闲的指针域来存放结点的直接前驱和直接后继信息。
对结点的指针域做如下规定:
若结点有左孩子,则Lchild指向其左孩子,否则,指向其直接前驱;
若结点有右孩子,则Rchild指向其右孩子,否则,指向其直接后继;
为避免混淆,对结点结构加以改进,增加两个标志域:
typedef struct BiThrNode
{
ElemType data;
struct BiTreeNode *Lchild , *Rchild ;
int Ltag , Rtag ;
}BiThrNode ;
在这种存储结构中,指向结点前驱和后继的指针叫做线索;
对二叉树以某种次序遍历并且加上线索的过程叫做线索化;
用这种结点结构构成的二叉树的存储结构叫做线索链表;
按照某种次序遍历,加上线索的二叉树称之为线索化二叉树。
3.3.2 线索化
线索化是在遍历过程中修改空链域的过程。
有左右孩子就指,无就指前驱后继
//根结点线索化
if(root->Lchild==NULL){//设置前驱线索
root->Ltag=1;root->Lchild=pre; }
//设置后继线索
if(pre!=NULL&&pre->Rchild==NULL){
pre->Rchild=root;pre->Rtag=1;
}
pre=root;
3.3.3 线索二叉树找前驱后继
以中序遍历LDR为例:
(1)找结点的中序前驱结点
P有左子树,其中序遍历序列如下:
问题:P的中序前驱X结点在树中应该位于什么位置?
解答:X结点应在P的左子树的最右下端。即q=p->Lchild,直到q->Rtag==1。
//找结点的中序前驱结点
void InPre(BTNode * p,BiTNode * pre){
/* 在中序线索二叉树中查找 p 的中序前驱, 并用 pre 指针返回结果 */
if(p->Ltag==1) pre= p->LChild; /*直接利用线索*/
else{ /* 在 p 的左子树中查找“最右下端”结点 */
for(q= p->LChild;q->Rtag==0;q=q->RChild);
pre=q;
}
return(pre);
}
(2)找结点的中序后继结点
P有右子树,其中序遍历序列如下:
P的中序后继结点即为其右子树的“最左下端”的结点。即q=p->Rchild,直到q->Ltag==1。
当p->Rtag=1 时,p->RChild 即为p 的后继结点
//找结点的中序后继结点
void InNext(BTNode * p,BiTNode * Next){
/* 在中序线索二叉树中查找 p 的中序后继, 并用 next 指针返回结果 */
if(p->Rtag==1) Next= p->RChild; /*直接利用线索*/
else{ /* 在 p 的左子树中查找“最右下端”结点 */
for(q= p->RChild;q->Ltag==0;q=q->LChild);
Next=q;
}
return(Next);
}
3.3.4 线索二叉树遍历
遍历线索树的问题可以分解成两步,
第一步是求出某种遍历次序下第一个被访问结点;
然后连续求出刚访问结点的后继结点,直至所有的结点均被访问。
以中序遍历为例
//在中序线索树上求中序遍历的第一个结点
BiTNode * InFirst(BiTree Bt)
{
BiTNode *p=Bt;
if(p) return (NULL);
while(p->LTag==0) p=p->Lchild;
return p;
}
通过调用 InFirst和 InNext ,可以实现对中序线索树的中序 遍历
void TInOrder(BiTree Bt){
BITNode *p;
P=InFirst(Bt);
While(p)
{
Visit(p);
p = InNext(p);
}}
4 树与森林
4.1 树
树和二叉树的区别:
(1)二叉树至多有两个子树,树则不然;
(2)二叉树的子树有左右之分,而树的子树没有;
(3)二叉树允许树为空,树一般不允许为空(个别教材允许为空)
4.2 树的存储结构
- 双亲表示法------->最常用
- 孩子表示法
- 孩子兄弟表示法
双亲表示法
用一组连续的存储空间来存储树的结点,同时在每个结点中附加一个指示器(整数域) ,用以指示双亲结点的位置(下标值) 。
树的结点结构定义:
typedef struct PTNode
{ ElemType data ;
int parent ;
}PTNode ;
#define MAX_SIZE 100
typedef struct
{ PTNode Nodes[MAX_SIZE] ;
int num ; /* 结点数 */
}Ptree ;
- 孩子表示法
每个结点的孩子结点构成一个单链表,即有n个结点就有n个孩子链表;
n个孩子的数据和n个孩子链表的头指针组成一个顺序表;
结点结构定义:
typedef struct PTNode
{ ElemType data ;
struct PTNode *next ;
}PTNode ;
顺序表定义:
#define N 6 /* 结点数 */
PTNode Nodes[N] ;
- 孩子兄弟表示法(二叉树表示法)
以二叉链表作为树的存储结构。
链表中每个结点有两个链域,分别指向第一个孩子结点和下一个兄弟(右兄弟)结点。
typedef struct CSnode
{
ElemType data ;
struct CSnode *firstchild, *nextsibing ;
}CSNode;
4.3 树、森林和二叉树的转换
- 树转换为二叉树
将树转换成二叉树在“孩子兄弟表示法”中已给出,其步骤是:
⑴ 加线。在树的所有相邻兄弟结点之间加一条连线。
⑵ 去连线。除最左的第一个子结点外,父结点与所有其它子结点的连线都去掉。
⑶ 旋转。将树以根结点为轴心,顺时针旋转450,使之层次分明。
- 森林转化为二叉树
将F={T1, T2,⋯,Tn} 中的每棵树转换成二叉树。
按森林中树的次序,后一棵树作为前一棵树的根结点的右子树,依次类推,则第一棵树的根结点就是转换后生成的二叉树的根结点。
- 二叉树转换为树或森林
其步骤是:
⑴ 加虚线。若某结点是其父结点的左孩子,则将该结点的右孩子、右孩子的右孩子、…,都与该结点的父结点加虚线相连,如图(a)所示。
⑵ 去连线。去掉原二叉树中所有父结点与其右孩子之间的连线,如图(b)所示。
⑶ 规整化。将图中各结点按层次排列且将所有的虚线变成实线,整理得到树或者森林,如图©所示。
4.4 树和森林的遍历
- 树的遍历
由树结构的定义可知,树的遍历有二种方法。
⑴ 先序遍历:先访问根结点,然后依次先序遍历完每棵子树。
⑵ 后序遍历:先依次后序遍历完每棵子树,然后访问根结点。 - 森林的遍历
⑴ 先序遍历:按先序遍历树的方式依次遍历F中的每棵树。
(2) 中序遍历:按中序遍历树的方式依次遍历F中的每棵树。
5 最优二叉树(Huffman树)
5.1 基本概念
① 结点路径:从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径。
② 路径长度:结点路径上的分支数目称为路径长度。
③ 树的路径长度:从树根到每一个结点的路径长度之和。
问题1:什么样的二叉树路径长度PL最小?
结论:完全二叉树具有最小路径的性质,但不具有唯一性。
④ 结点的带权路径长度:树的根结点到该结点之间的路径长度与结点的权(值)的乘积。
权(值):给树的结点赋予某种实际意义的实数。
⑤ 树的带权路径长度:树中所有叶子结点的带 权路径长度之和,记做WPL
⑥ Huffman树:由n个带权的叶子结点(每个结点的权值为wi) 构成的WPL值最小的二叉树,称为Huffman树(或称最优二叉树) 。
5.2 Huffman树构造
① 根据n个权值{w1, w2, ⋯,wn},构造森林F={T1, T2, ⋯,Tn},其中Ti为单根树,权值为wi;
② 在F中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和;
③ 在F中删除这两棵树,同时将新得到的树加入F中;
④ 重复②、③,直到F只含一颗树为止。
构造原则1:根值较小者作为左子树,较大者作为右子树
构造原则2:根值相同时,优先选择深度较小者。
5.3 Huffman编码
- 前缀码:如果在一个编码系统中,任何一个编码都不是其他编码的前缀,则称该编码系统中的编码为前缀码。
- 哈夫曼编码:对一颗有n个叶子的哈夫曼树,规定左分支为0,右分支为1,可得到对应叶子结点的哈夫曼编码。
- 性质:哈夫曼编码是最优前缀码。
- 由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。
PPT课后题
⑴ 假设在树中, 结点x是结点y的双亲时,用(x,y)来表示树边。已知一棵树的树边集合为 { (e,i), (b,e), (b,d), (a,b), (g,j), (c,g), (c,f), (h,l), (c,h), (a,c) } ,用树型表示法表示该树,并回答下列问题:
① 哪个是根结点? 哪些是叶子结点? 哪个是g的双亲? 哪些是g的祖先? 哪些是g的孩子? 那些是e的子孙? 哪些是e的兄弟? 哪些是f的兄弟?
② b和n的层次各是多少? 树的深度是多少? 以结点c为根的子树的深度是多少?
⑵ 一棵深度为h的满k叉树有如下性质: 第h层上的结点都是叶子结点,其余各层上每个结点都有k棵非空子树。 如果按层次顺序(同层自左至右)从1开始对全部结点编号,问:
① 各层的结点数是多少?
② 编号为i的结点的双亲结点(若存在)的编号是多少?
③ 编号为i的结点的第j个孩子结点(若存在)的编号是多少?
④ 编号为i的结点的有右兄弟的条件是什么? 其右兄弟的编号是多少?
⑶ 设有如图6-27所示的二叉树。
① 分别用顺序存储方法和链接存储方法画出该二叉树的存储结构。
② 写出该二叉树的先序、中序、后序遍历序列。
⑷ 已知一棵二叉树的先序遍历序列和中序遍历序列分别为ABDGHCEFI和GDHBAECIF,请画出这棵二叉树,然后给出该树的后序遍历序列。
⑸ 设一棵二叉树的中序遍历序列和后序遍历序列分别为BDCEAFHG和DECBHGFA ,请画出这棵二叉树,然后给出该树的先序序列。
⑹ 已知一棵二叉树的中序遍历序列和后序遍历序列分别为dgbaekchif和gdbkeihfca,请画出这棵二叉树对应的中序线索树和后序线索树。
⑺ 以二叉链表为存储结构,请分别写出求二叉树的结点总数及叶子结点总数的算法。
⑻ 设图6-27所示的二叉树是森林F所对应的二叉树,请画出森林F。
⑼ 设有一棵树,如图6-28所示。
① 请分别用双亲表示法、孩子表示法、孩子兄弟表示法给出该树的存储结构。
② 请给出该树的先序遍历序列和后序遍历序列。
③ 请将这棵树转换成二叉树。
⑽ 设给定权值集合w={3,5,7,8,11,12} ,请构造关于w的一棵huffman树,并求其加权路径长度WPL 。
⑾ 假设用于通信的电文是由字符集{a, b, c, d, e, f, g, h}中的字符构成, 这8个字符在电文中出现的概率分别为{0.07, 0.19, 0.02, 0.06, 0.32, 0.03, 0.21, 0.10} 。
① 请画出对应的huffman树(按左子树根结点的权小于等于右子树根结点的权的次序构造)。
② 求出每个字符的huffman编码。
自己康的一点题
- 在树中除根结点外,其余结点分成m(m≥0)个互不相交的集合T1,T2,T3…Tm,每个集合又都是树,此时结点T称为Ti的父结点,Ti称为T的子结点(1≤i≤m)。
- 根据先序序列ABDC和中序序列DBAC确定对应的二叉树,该二叉树是完全二叉树
- 在完全二叉树中,当i为奇数且不等于1时,结点i的左兄弟是结点i-1,否则没有左兄弟
- 线索二叉树中,结点p没有左子树的充要条件是p->ltag=1
- Huffman树的总结点个数(多于1时)不能为偶数。
- 由二叉树的先序序列和后序序列不可以唯一确定一棵二叉树。
- 由树转换成二叉树,其根节点的右子树总是空的。
- 二叉树的前序遍历中,任意结点均处在其子女结点之前。
- 一棵有n个结点的树所有结点度数之和为n-1,边数为n-1,度数==边数
- 在二叉树中一个结点的深度为3,高度为4,该树的高度至少为6
- 完全二叉树,编号为i的结点层次为|log以2为底(i+1)+1|
Part 2、代码
#include "stdio.h"
#include "stdlib.h"
#define MaxSize 100
typedef char Elemtype;
typedef struct BTNode{
Elemtype data;
struct BTNode *parent,*Lchild,*Rchild;
}BTNode;
typedef struct queue{
struct BTNode* numQ[MaxSize];
int front;
int rear;
}Queue;
Queue Q;
void initilize() {
Q.front = 0;
Q.rear = 0;
}
void Push(BTNode* root) {
Q.numQ[++Q.rear] = root;
}
BTNode* Pop() {
return Q.numQ[++Q.front];
}
int empty() {
return Q.rear == Q.front;
}
BTNode* create(BTNode *s)
{
BTNode *l;
Elemtype e;
e = getchar();
getchar();
if(e == '#')
l = NULL;
else{
l = (BTNode*)malloc(sizeof(BTNode));
if(l == NULL){
printf("ERROR!\n");
return NULL;
}
l->parent = s;
s = l;
l->data = e;
l->Lchild = create(s);
l->Rchild = create(s);
}
return l;
}
void print1(BTNode *l)
{
if(l!=NULL)
{
printf("%c ",l->data);
print1(l->Lchild);
print1(l->Rchild);
}
}
void print2(BTNode *l)
{
if(l!=NULL)
{
print2(l->Lchild);
print2(l->Rchild);
printf("%c ",l->data);
}
}
void print3(BTNode *l)
{
if(l!=NULL)
{
print3(l->Lchild);
printf("%c ",l->data);
print3(l->Rchild);
}
}
int PreOrder(BTNode *l,int num)
{
if(l!=NULL)
{
if(l->Lchild==NULL&&l->Rchild==NULL)
{
printf("%c ",l->data);
num++;
}
num = PreOrder(l->Lchild,num);
num = PreOrder(l->Rchild,num);
}
return num;
}
int length(BTNode *l,int max)
{
int len = 1;
BTNode *p;
if(l!=NULL)
{
if(l->Lchild==NULL&&l->Rchild==NULL)
{
p = l->parent;
while(p!=NULL)
{
p = p->parent;
len++;
}
if(len>max) max = len;
}
max = length(l->Lchild,max);
max = length(l->Rchild,max);
}
return max;
}
void LevelOrderTraversal (BTNode* root) {
BTNode* temp;
Push(root);
while (!empty()) {
temp = Pop();
printf("%c ", temp->data);
if (temp->Lchild)
Push(temp->Lchild);
if (temp->Rchild)
Push(temp->Rchild);
}
}
int main()
{
BTNode *l,*s;
s = NULL;
l = create(s);
initilize();
printf("先序遍历:\n");
print1(l);
printf("\n中序遍历:\n");
print3(l);
printf("\n后序遍历:\n");
print2(l);
printf("\n层次遍历:\n");
LevelOrderTraversal(l);
int num = 0;
printf("\n叶结点为:\n");
num = PreOrder(l,num);
printf("\n共有%d个叶结点\n",num);
int max=0;
max = length(l,max);
printf("深度为%d\n",max);
return 0;
}
Part 3、总结
头都大了
做题最重要的
一个是搞清楚祖宗结点、双亲结点、兄弟结点啥的
一个是弄明白二叉树和树的转化,包括森林和树的转化
一个是满二叉树、完全二叉树的区别和联系
还有二叉搜索树和二叉排序树是一个东西。。。
Huffman编码、Huffman树一定要搞懂
然后就是各种遍历了
最后记得重要的n0 = n2 +1