《数据结构》第六章 树和二叉树 笔记整理
本章要求
1.二叉树的概念、性质、存储结构
2.熟练掌握二叉树的前、中、后序遍历方法及代码
3.熟练掌握哈夫曼树的实现方法、构造哈弗曼编码的方法
4.了解森林与二叉树的转换,树的遍历
文章目录
一、树、二叉树的相关术语
1.结点的度:结点的子树的个数
2.结点的的层次:从根结点到该结点的层数(根结点算一层)
3.叶子、终端节点
4.树的度:所有结点度中的最大值,几叉树树的度就是几。
5.树的深度(高度):最大是层数
6.兄弟、双亲、祖先
7.森林:m棵互不相交的树的集合
二、二叉树
1.二叉树的定义和特点
①二叉树不是特殊的树,而是两个概念;②每个结点的度最大为2(二叉树的度为2)③二叉树为有序树!!!左右子树不能任意互换
2.特殊二叉树,二叉树(含特殊)的性质和存储结构
1)特殊二叉树:
a.满二叉树:每一层上的结点数都是最大结点数。
b.完全二叉树:
①若此树有n个结点,深度为k,与其满二叉树的结点从1到n编号一一对应则为完全二叉树。
②叶子结点仅在层次最大的两层上,除最后一层,前k-1层都是满的!
③从满二叉树的最后一个结点开始连续去掉任意结点,即为完全二叉树。
2) 二叉树性质
性质1:第i层上至少有1个结点。
性质2:深度为k至少有k个结点。
性质3:
性质4:图中的符号表示:不大于X的最大整数(取底),而且做题时可以求左右,就能把这个数求出来,记得还有**+1**。
性质5:
3)二叉树的存储结构
① 顺序存储
表示如下:
#define MAX_TREE_SIZE 100//二叉树最大节点数
typedef TElemType SqBiTree[MAX_TREE_SIZE];//0号单元存储根结点
SqBiTree T
特点:浪费空间,适用于满二叉树和完全二叉树
②链式存储
二叉链表
C语言的类型描述如下:
typedef struct BiTNode{//结点结构
TElemType data;
struct BiTNode *lchild,*rchild;//左右孩子指针
}BiTNode,*BiTree
结点结构:
练习:在n个结点的二叉链表中,有 n+1 个空指针域;
分析::n个结点,必有2n个链域,每个结点除了根结点之外都只有一个双亲,占用一个链域,即n-1个,
所以有2n-(n-1)=n+1个空的。
考试作业常用浙大版:
typedef struct TNode *Position;
typedef Position BinTree;
struct TNode{
ElementType Data;
BinTree Left;
BinTree Right;
};
3.遍历二叉树和线索二叉树
1)遍历二叉树
为了将非线性结构转化为线性结构来解决问题,对其进行遍历,顺着一条路径,使每个结点都被访问到,且仅被访问1次,访问是指对结点的各种操作
例:
①先序遍历:根左右
思想:若为空树则空操作;
否则
(1)访问根结点;
(2)先序遍历左子树;
(3)先序遍历右子树;
算法的递归描述:
void PreorderTraversal( BinTree BT )
{
if(BT)//如果不为空树
{
printf(" %c",BT->Data);//访问结点
PreorderTraversal(BT->Left);//遍历左子树
PreorderTraversal(BT->Right);//遍历右子树
}
}
便于理解,附加图:
②中序遍历:左根右
思想:若为空树则空操作;
否则
(1)中序遍历左子树;
(2)访问根节点
(3)中序遍历右子树;
算法的递归描述:
void InorderTraversal( BinTree BT )
{
if(BT)
{
InorderTraversal(BT->Left);//中序遍历左子树
printf(" %c",BT->Data);//访问根结点
InorderTraversal(BT->Right);//中序遍历右子树
}
}
算法的非递归描述——运用 栈
遇到一个结点,就把它压入栈,并且去遍历它的左子树;
当左子树遍历完,从栈顶弹出这个结点并去访问它;
再去按照这个流程去遍历该结点的右子树;
void InOrderTra(BinTree BT)
{
BinTree T=BT;
Stack S=CreatStack(MAXSIZE);//创建并初始化栈S;
while(T||!IsEmpty(S))
{
while(T)//一直向左将沿途结点压入栈;
{
Push(S,T);
T=T->Left;
}
if(!IsEmpty(S))
{
T=Pop(S);//当左边结点没了,弹出栈顶元素,并且T当前就退回到该结点,也就是最近路过的
printf(" %c",T->Data);//访问结点
T=T->Right;//左根右,开始向右
}
}
}
③后序遍历:左右根
思想:若为空树则空操作;
否则
(1)后序遍历左子树;
(2)后序遍历右子树;
(3)访问根节点。
算法的递归描述:
void PostorderTraversal( BinTree BT )
{
if(BT)
{
PostorderTraversal(BT->Left);
PostorderTraversal(BT->Right);
printf(" %c",BT->Data);
}
}
④层次遍历:从上到下,从左到右,一层一层
算法思想:
a.根结点先入队;
b.从队列取出一个元素;
c.访问该元素指的结点;
d.若该结点的左右孩子都不为空,则将左、右孩子的指针入队;
可以用队列实现,也可以用一维数组,本质相同;
队列Queue算法如下:
void LevelOrderTraversal(BinTree BT)
{
Queue Q;
BinTree T;
if(!BT) return ;//树为空则直接返回
Q=CreatQueue(MAXSIZE);//初始化队列
EnQueue(Q,BT);//将根结点入队
while(!Qempty(Q))//如果队列不为空的话进入循环
{
T=DeQueue(Q);//队首元素出队
printf(" %c",T->Data);//访问该出队结点
if(T->Left)//若左右不为空,将左右孩子入队,利用队列的特点:先入先出。
EnQueue(Q,T->Left);
if(T->Right)
EnQueue(Q,T->Right);
}
}
数组算法描述:
void LevelOrderTraversal(BinTree BT)
{
BinTree T[1000];//建立数组;
BinTree p;//定义一个指针类型,便于遍历
int f=0,r=0;//相当于队列里的头指针,尾指针
if(BT)//若不为空树,将根结点压入数组
{
T[r++]=BT;//相当于尾指针指向队尾元素的下一个
}
while(f!=r)
{
p=T[f];//注意这一步!!!每次输出的都是队首!
printf(" %c",p->Data);
f++;//相当于Q.f=(Q.f+1)%MAXSIZE;
if(p->Left) T[r++]=p->Left;//是P,也就是原队首的左右孩子
if(p->Right) T[r++]=p->Right;
}
}
常见题型:
已知中序、后序;已知先序、中序则可以唯一确定一棵二叉树,但是先、后序不行。
例:
2)遍历算法的应用举例
①统计二叉树中的结点个数
基本思想:先序(中、后均可)遍历二叉树,在遍历过程中查找叶节点,计数–>相当于把遍历算法中的访问操作改为计数–>若为叶节点,加一。
算法1(用于C++,C不能用引用):
void CountLeaf(BinTree T,int &a)//传的计数器的引用-->int main()里 定义了a=0;
{
if(T)
{
if(!T->Left&&!T->Right)
a++;//对叶结点计数;
CountLeaf(T->Left,a);
CountLeaf(T->Right,a);
}
}
算法2 C
int CountLeaf(BinTree T)
{
if(!T) return 0;
else if(!T->Left&&!T->Right)
return 1;//如果为叶节点返回1,
else
return CountLeaf(T->Left)+CountLeaf(T->Right);
}
②求二叉树的深度
二叉树的深度是左右子树的最大深度加1,因此–>分别求左右子树的深度,相当于把访问函数变为求左右子树的深度最大值加1。
算法如下:
int GetHeight( BinTree BT )
{
int HL,HR,Hmax;//定义左子树高度,右子树高度,最大高度
if(BT)
{
HL=GetHeight( BT->Left );
HR=GetHeight( BT->Right );
Hmax=HL>HR?HL:HR;//求左右子树高度的最大值
return Hmax+1;//加1才是树的高度,不然是子树的高度
}
else
return 0;
}
3)线索二叉树
①定义
由遍历二叉树可知:用一定的规则(前中后序)将二叉树结点排列成线性序列,使每个结点有一个前驱和后继,但是,当用二叉链表作为存储结构时只能得到左右孩子的信息,无法得到任意结点的直接前驱和后继,这种信息只有在遍历的动态过程中才能得到,因此引入 线索二叉树,利用空链域来存放前驱和后继的信息
②方法
---->若结点有左孩子,则Left指向左孩子;否则指向直接前驱(给定序列的)
---->若结点有右孩子,则Right指向右孩子;否则指向直接后继(给定序列的)
为避免混淆,设定两个标志域LTag和RTag
例:
后序和中序同上
三、树和森林
1、树的存储结构
1)双亲表示法
利用的是静态链表
特点:找双亲容易,找孩子难
C语言描述:
#define MAX_SIZE 100
typedef struct PTNode{
ELem data;
int parent;//双亲位置域
}PTNode;//结点结构
typedef struct {
PTNode nodes [MAX_SIZE];
int r,n;//根结点的位置和结点个数
}PTree;//树结构
2) 孩子链表表示法
多重链表,把每个结点的孩子结点排列起来,看成一个线性表,n个结点n个孩子链表(叶子的孩子链表为空表),n个头指针又组成一个线性表,可采用顺序存储
特点:找孩子容易,找双亲难
C语言描述如下:
typedef struct CTNode//孩子结点结构
{
int child;
struct CTNode *next;//指向的它的下一个兄弟,也就是他双亲的下一个孩子;
}*ChildPtr;
typedef struct//双亲结点结构
{
Elem data;
ChildPtr firstchild;//孩子链表的头指针
}CTBox;
typedef struct //树结构
{
CTBox nodes[MAXSIZE];
int n,r;//结点数和根结点的位置
}CTree;
3)孩子兄弟表示法(二叉链表表示法)
链表中两个链域分别指向该结点的第一个孩子和下一个兄弟结点
由于二叉树和树均可以用二叉链表来表示,树的操作可对应二叉树的操作来完成
但是解释不同,由于树的根结点无兄弟,树对应的二叉树无右子树
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild,*nextsibling;
}CSNode,*CSTree;
2.树、二叉树、森林之间的转换
1)树转换为二叉树(兄弟相连留长子)
①加线:在兄弟间加连线;
②抹线:对每一个结点,除左孩子外,去除与其他孩子的连线;
③:以根结点为轴心,将整棵树顺时针旋转45°
2)二叉树转换为树(左孩右右连双亲,去掉原来右孩线)
①加线:p为双亲结点的左孩子,则将p的右孩子,右孩子的右孩子…沿分支找到所有右孩子,与双亲连接起来
②抹线:抹去原二叉树中双亲与右孩子之间的连线;
③调整
3)森林转换为二叉树(树变二叉根相连)
①将每棵树分别转化为二叉树
②每棵树的根结点相连
③以第一棵树的根结点为大树的根
4)二叉树变森林(去掉全部右孩线,孤立二叉再还原)
①抹线:去掉二叉树根结点与其右孩线,及沿右分支搜索到的所有右孩子间的连线全部抹掉,变为孤立的二叉树;
②还原
3、树、森林的遍历
1)树的遍历:
①先根遍历:若树不为空,先访问根结点,然后先根遍历各棵子树;
②后根遍历:若树不为空,进行后根遍历各棵子树,然后访问根结点;
③按层次遍历:自上至下,自左至右;
2)森林的遍历:
首先清楚:将森林分为三部分,第1部分是第一棵树的根结点,第2部分是第一棵树的子树,第3部分是除第一棵树外的所有树。
①先序遍历
若森林不空,则访问森林中第一 棵树的根结点;先序遍历森林中第一棵树的子树森林;先序遍历森林中(除第一棵树之外)其余树构成的森林。
②中序遍历
若森林不空,则中序遍历森林中第一棵树的子树森林;访问森林中第一棵树的根结点; 中序遍历森林中(除第一棵树之外)其余树构成的森林。