目录
1. 树的定义
树是一种 非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。
2. 树的基本术语
节点的度:一个节点含有的子树的个数称为该节点的度; 如上图:A的为6;
叶节点或终端节点:度为0的节点称为叶节点; 如上图:B、C、H、I...等节点为叶节点;
非终端节点或分支节点:度不为0的节点; 如上图:D、E、F、G...等节点为分支节点;
双亲节点或父节点:若一个节点含有子节点,则这个节点称为其子节点的父节点; 如上图:A是B的父节点;
孩子节点或子节点:一个节点含有的子树的根节点称为该节点的子节点; 如上图:B是A的孩子节点;
兄弟节点:具有相同父节点的节点互称为兄弟节点; 如上图:B、C是兄弟节点;
树的度:一棵树中,最大的节点的度称为树的度; 如上图:树的度为6;
节点的层次:从根开始定义起,根为第1层,根的子节点为第2层,以此类推;
树的深度:树中节点的最大层次; 如上图:树的高度为4;
堂兄弟节点:双亲在同一层的节点互为堂兄弟;如上图:H、I互为兄弟节点;
节点的祖先:从根到该节点所经分支上的所有节点;如上图:A是所有节点的祖先;
子孙:以某节点为根的子树中任一节点都称为该节点的子孙。如上图:所有节点都是A的子孙;
森林:由m(m>0)棵互不相交的树的集合称为森林
3. 二叉树的定义
为什么要研究二叉树?二叉树的结构最简单,规律性最强
一棵二叉树是结点的一个有限集合。
二叉树的特点:
1. 二叉树不存在度大于2的结点(二叉树的度可以小于2
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
3. 二叉树可以是空集合,根可以有空的左子树或空的右子树
注意!二叉树和树是两个不同的概念!!!
4. 二叉树的性质
1. 若规定根节点的层数为1,则一棵非空二叉树的 第 i 层上最多有2^(i-1)个结点.
2. 若规定根节点的层数为1,则 深度为 k的二叉树的最大结点数是2^k-1.
3. 对任何一棵二叉树, 如果度为 0 其叶结点个数为n0, 度为 2 的分支结点个数为n2, 则有 n0=n2+1
两种特殊形式的二叉树
·满二叉树:每一层节点数都达到最大值
--叶子结点全在最底层
·完全二叉树:一棵具有n个结点且深度为k的二叉树若前k-1层的结点数都达到最大,剩余的结点在第k层中从左至右连续分布
--与满二叉树编号一一对应
--满二叉树一定是完全二叉树
4. 若规定根节点的层数为1,具有 n 个结点的满二叉树的深度h, h=log2n + 1(向下取整
5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有节点从0开始编号,则对于序号为 i 的结点有:
① 若 i>0 , i 位置节点的双亲序号: i / 2(向下取整;i=0,i为根节点编号,无双亲节点
②若 2i<n ,左孩子序号: 2i ,否则无左孩子
③ 若 2i+1<n ,右孩子序号: 2i+1 否则无右孩子
5. 二叉树的存储结构
5.1 顺序存储结构
按满二叉树的结点层次编号,一次存放二叉树中的数据元素,即按照从上到下、从左到右的顺序依次存放所有结点。
特点:
1. 结点间的关系蕴含在其存储位置中
2. 浪费空间,适于存满二叉树和完全二叉树
5.2 链式存储结构
二叉链表(找孩子容易,找双亲不易
n个结点的二叉树链表中只有n-1个非空指针域
三叉列表
6. 遍历二叉树
二叉树的遍历实际上就是将一个非线性结构的二叉树转化为一个线性结构。
三种遍历方法:先根序遍历、中根序遍历、后根序遍历
遍历是递归进行的
若二叉树中各个结点的值均不相同,则二叉树结点的先序序列、中序序列、后序序列都是唯一的。
由二叉树的先序序列和中序序列,或二叉树的后序序列和中序序列可以确定唯一一个二叉树 。
层次遍历
从根结点开始,从上到下,从左到右的顺序访问每一个结点。每个结点仅访问一次
二叉树遍历的应用
二叉树的建立
Staus CreateBiTree(BiTree &T)
{
cin>>ch;
if(ch==c) #c为特殊字符,用以表示空树
T=NULL;
else{
T= new BTNode<ElemType>;
T -> data = ch;
CreateBiTree(T -> lchild);
CreateBiTree(T -> rchild);
}
return OK;
}
复制二叉树
int Copy (BiTree T, BiTree &NewT)
{
if(T==NULL) {NewT= NULL; return 0;}
else {
NewT = new BiTNode;
NewT->data =T -> data;
Copy(T -> lchild , NewT -> lchild);
Copy(T -> rchild , NewT -> rchild);
}
}
计算二叉树的深度
int Depth(BiTree T)
{
if (T==NULL)
return 0;
else {
m=Depth(T->lchild)
n=Depth(T->rchild)
return m>n?m+1:n+1;
}
}
计算二叉树的结点总数
int NodeCount(BiTree T)
{
if(T==NULL)
return 0;
else{
NodeCount (T ->lchild) +NodeCount (T-> rchild)+1;
}
}
计算二叉树的叶子总数
int LeafCount( BiTree T)
{
if(T==NULL)
return 0;
if(T ->lchild ==NULL && T->rchild ==NULL)
return 1;
else{
return LeafCount(T->lchild)+LeafCount(T->rchild);
}
}
7. 线索二叉树
为什么要研究线索二叉树?
二叉链表无法直接找到该结点在某种遍历序列中的前驱和后继结点。利用二叉链表中的空指针。
在n个结点的二叉链表中有n+1个空指针域
如何利用空指针?
如果某个结点的左孩子是空的,则将空的左孩子的指针域改为指向其前驱。右孩子同理。
为了区分指向的是孩子还是前驱或者后继,增加标志域:
8. 树和森林
8.1 树的存储结构
8.1.1 双亲表示法
8.1.2 孩子链表表示法
8.1.3 树的二叉链表(孩子-兄弟)存储表示法
两个指针域分别指向,第一个孩子结点和下一个兄弟结点
特点:操作容易,但是破坏了树的层次性
8.2 树与二叉树的转换
给定一棵树,可以找到唯一一棵二叉树与之对应
将树转化为二叉树方法:
1.加线: 在兄弟之间加线
2.抹线:对每个结点,除其左孩子外,除去其与其余孩子之间的连线
3.旋转:以树的根结点为轴心,将整棵树顺时针旋转45°
将二叉树转换成树方法:
1.加线:若p结点是双亲结点的左孩子,则将p的右孩子,右孩子的有孩子,。。沿分支找到所有右孩子,都与p的双亲用线连接起来
2.抹线:抹掉原二叉树中双亲与右孩子之间的连线
3.调整:将结点按层次排列,形成树的结构
由森林转换成二叉树方法:
1.将各棵树分别转换成二叉树
2.将每棵树的根结点用线连起来
3.以第一棵树根结点为二叉树的根,再以根结点为轴心,顺时针旋转,构成二叉树型的模型
由二叉树转换成森林方法:
1.抹线:将二叉树中根结点与其右孩子连线,及沿右分支搜索到的所有右孩子间连线全部抹掉,使之变成孤立的二叉树
2.还原: 将孤立的二叉树还原成树
8.3 树和森林的遍历
树的遍历:先根遍历、后根遍历、按层次遍历
树没有中根遍历:树的一个根结点可能有2个以上的结点,无法确定根结点的遍历次序
森林的遍历:
森林由三部分组成:
1.森林中的第一棵树的根结点
2. 森林中的第一棵树的子树森林
3.森林中的其他树构成的森林
先序遍历、中序遍历
没有后序遍历:因为会把一棵树割裂开来
8.4 树遍历的应用
1.求树的深度(设树的存储结构为孩子兄弟链表
int TreeDepth(CSTree T)
{
if (!T) return 0;
else {
h1=TreeDepth(T-> firstchild);
h2=TreeDepth(T-> nextsibling);
return (max(h1+1,h2));//注意看这个h1+1和h2
}
}
2.输出树中所有从根到叶子的路径的算法
void AllPath( Bitree T, Stack & S)
{
if(T){
Push(S,T->data);
if(!T->Lchild && !T->Rchild ) PrintStack(S);//输出路径
else {
AllPath(T->Lchild , S);
AllPath(T->Rchild , S);
}
Pop(S);
}
}
9. 哈夫曼树及其应用
定义:
又称最优二叉树,它是n个带权叶子结点构成的所有二叉树中,带权路径长度最小的二叉树
哈夫曼树中权越大的叶子,离根越近。
具有相同带权结点的哈夫曼树不唯一!
1.由n个叶子结点构成的哈夫曼树共有2n-1个结点
2.度为2 的结点的个数为n-1
3.哈夫曼树仅有度为2的结点
哈夫曼树构造需要注意:
1. 使用权值最小的两个节点构建新的树时,应该将权值较小的节点T1作为左子树,权值较大的节点T2作为右子树。(如果T1和T2的权值相同,则将深度较小的作为左子树)
2.合并值排在叶子值得后面
哈夫曼的应用:哈夫曼编码
1.哈夫曼编码可以保证任一字符的编码不是另一字符编码的前缀,因为没有一个叶子结点是另一个叶子结点的祖先
2.哈夫曼树的带权路径长度最短,所以字符编码最短
文件的编码和解码,这个在课程实践课做过一个小实践,可以看