树是n(n>=0)个结点的有限集合;当n等于0时,称为空树;任意的一棵非空树应该满足:
1. 有且仅有一个特定的称为根的结点。(只有一个根)
2. 当n>1时,其余的结点可分为m(m>0)个互不相交的有限集T1、T2、...Tm,期中每个集合本身又是一棵树,并且称为根的子树。(树是一种递归的数据结构,是一种逻辑结构,同时也是一种分层结构)
树具有以下两个特点:
- 树的根没有前驱,除根结点外,所有结点有且只有一个前驱,
- 树中所有结点可以有0个或多个后继。(每层结点最多只和上层父节点有直接关系,根结点没有上层结点,因此树中有n-1条边)
如图:
祖先: 以k结点为例;根节点A到K的唯一路径的任意结点,称为k的 “祖先”
双亲: 路径上最接近k结点的E称为k的 “双亲结点”
孩子: k为E的 “孩子结点”
兄弟: k和L有相同的双亲,故L是k的 “兄弟结点”
堂兄弟: 双亲结点在同一层的结点互为堂兄弟 图中G E F H I J 均为堂兄弟
结点的度: 树中一个结点的孩子个数称为 ”结点的度“
树的度” 树中max{结点的度} 上图中结点D的度为3,则”树的度“为3
分支结点: 结点度大于0的结点,又称为非终端结点
叶子结点: 结点的度为0称为叶子结点,又称为终端结点
结点的层次: 结点的层次从树根开始定义,根结点为第一层,它的子结点为第二册,以此类推
结点的深度: 从根结点开始 ”自顶向下“逐层累加
结点的高度: 从叶子结点开始 ”自底向上“逐层累加
树的高度(深度): 树中结点的最大层数 图中树的高度为4
有序树和无序树: 树中各个子树从左到右是有次序的,不能呼唤称为有序树,反之称为无序树。图上为有序树,若将子结点互换位置,就变成了一棵不同的树
路径和路径长度: 路径长度是路径上所经过的边的个数; 路径:两个结点之间存在一条路径。
森林:森林是m(m>0)棵互不相交的树的集合
1. 树中的结点数等于所有结点的度数之和+1 (结点数=所有结点度数和+1)
2. 度为m的树中第i层上至多有 m的(i-1)次方 个结点 (i>=1)
3. 高度为h的m叉树至多有(m的h次方-1)/(m-1)个结点
4. 具有n个结点的m叉树最小高度为 向上取整:(logm[n(m-1)+1])
.
做题总结:
5. n个结点 度为m的树, 树的高度最多为 n-m+1
6. 度为m 高度为h的树 结点至少为h-1+m (有h-1个度为1,最后一层度为m可以画出来)
7. 公式运用 假设度为4的树 n=1+n1+2n2+3n3+4n4=n0+n1+n2+n3+n4
二叉树是另一种树形结构,特点是每个结点至多只有两颗树,树的度为2.二叉树子数分左右之分,次序不能任意颠倒
二叉树是n(n>=0)个结点的有限集合
1. n=0 为空二叉树
2. 由一个根节点和两个互不相交的被称为根的左子树和右子树组成。 左子树和右子树又分别是一颗二叉树
二叉树是有序树,左右子数不能颠倒,即使树中只有一颗子树也要分右子树还是左子树
二叉树有5种基本形态:
- 空二叉树 2.只有根节点 3. 只有左子树 4.只有右子树 5.左右子树都有
二叉树和度为2的有序树的区别
- 度为2的树至少有3个结点,但二叉树可以为空
- 度为2的有序树的孩子左右次序是相对于另一孩子而言的,若某一个结点只有一个孩子不用区分左右,但是二叉树不论孩子数是不是2,都需要区分左右子树; 二叉树结点次序不是相对于另一结点的而是确定的
一棵高度为h,且有2h-1个结点的二叉树称为满二叉树。树中每层含有最多的结点,满二叉树的叶子结点在最后一层,除叶子结点为其余每个结点均为2;
对满二叉树按层数编号,根为1,自上而下,自左向右,每个结点对应一个编号,若有双亲 双亲为: 2i(向下取整) 若有左孩子:为2i 若有右孩子:为2i+1
高度为h,有n个结点的二叉树,当且仅当每个结点都与高度h的满二叉树中结点的编号 一 一对应,则称为完全二叉树
有以下性质:
不存在度为1的结点的二叉树称为扩充二叉树,又称为2-树;
左子树所有结点关键字均小于根节点的关键字,右子树的所有结点的关键字均大于根节点的关键字;左子树和右子树又各是一棵二叉排序树;(根大于左孩子,根小于右孩子)
树上的任一结点的左子树和右子树的深度之差不超过1
顺序存储结构:
typedef struct BTBode{
int data;
struct BTNode *lchild,*rchild;
}BTNode;
做题总结:对于n个结点的二叉树的高度 为(向下取整 [log2n]) +1 ~n,对于完全二叉树是(向下取整 [log2n]) +1;
二叉树的递归遍历与非递归遍历
层次遍历用队列;
线索二叉树利用n+1个空指针进行快速的查找结点的前驱和后继的速度;
规定若无左子树,令lchild指向其前驱结点;若无右子树,令rchild指向其后继结点;还需要增加两个标志域识别指向左右孩子的是前驱还是后继
lchild | ltag | data | rtag | rchild |
---|
ltag =0 lchild指向结点的左孩子 rtag=0 rchild指向结点的右孩子
=1 lchild指向结点的前驱 =1 rchild指向结点的后继
结点结构如下:
typedef struct ThreadNode{
int data;
struct ThreadNode *lchild,*rchild;
int ltag,rtag;
}ThreadNode;
利用线索二叉树可以做的操作:
做题总结:
树的存储方式有很多种,既可以采用顺序结构,也可以采用链式结构;
采用一组连续空间存储每个结点,同时在每个结点增设一个伪指针,指示其双亲结点在数组中的位置,根结点下标为0,伪指针域为-1
这种结构就是利用了每个结点(除根结点以外)只有双亲的性质,可以很快得到每个结点的双亲结点,但求结点的孩子时需要遍历整个结构;
将每个结点的孩子结点都用单链表链接形成一个线性结构,此时n个结点就有n个链表,叶子结点的孩子链表为空表
这种存储方式寻找自诩的操作非常直接,而寻找双亲需要遍历n个结点中孩子链表指针域所指向的n个孩子链表;
孩子兄弟表示法又称为二叉树表示法,孩子兄弟表示法每个结点包括三部分内容:结点值,指向结点第一个孩子结点的指针,以及指向结点下一个兄弟结点的指针;
这种表示法:最大优点是可以方便地实现树转换为二叉树的皂搓,易于查找结点的孩子等,但缺点是从当前结点查找其双亲结点比较麻烦。
二叉树和树都可以用二叉链表作为存储结构,给定一棵树都可以找到唯一的二叉树与之对应;
先将森林中的每棵树转换成为二叉树,把森林中第二棵树根视为第一棵树根的右兄弟,第三棵为第二棵根的右兄弟,以此类推;图例:
这里其实只要记住 “左孩子右兄弟“ 这个口诀就行;
树 | 森林 | 二叉树 |
---|---|---|
先根遍历 | 先序遍历 | 先序遍历 |
后根遍历 | 中序遍历 | 中序遍历 |
做题总结:
哈夫曼树的定义:树中的结点被赋一个表示某种意义的数值,称为结点的权。 从树的根到任意结点的路径长度与该结点的权值的乘积称为该该结点的带权路径长度。
树中所有叶子结点的带权路径长度之和称为该树的带权路径长度:
WPL= wili(i从1到n的和)
在含有n个带权叶子结点的二叉树中,期中带权路径长度WPL最小的二叉树称为哈夫曼树,也称为最优二叉树
每个字符用相等的二进制位表示,称这种编码方式为固定长度编码;若允许对不同字符使用不等长的二进制位表示,则这种编码方式称为 可变长度编码
若没有一个编码是另一个编码的前缀,称这样的编码为 前缀编码。
将每个出现的字符当作一个独立的结点,其权值为它出现的频度或次数,构造出对应的哈夫曼树,所有的字符结点都在叶结点中,用0,1表示其左右孩子,0表示左孩子,1表示右孩子; 矩形数值表示字符及出现的次数
这个哈夫曼树的WPL=145+3(13+12+16)+4*(5+9)=224
此时得到的最终编码二进制编码长度为224位; 若采用3位固定长度编码,则得到的二进制长度为300位。因此哈夫曼编码共压缩了 25%的数据 224/300=0.746,利用哈夫曼树可以设计出总长度最短的二进制前缀编码;
题目总结: