树和二叉树
一、树的类型定义
树型结构是一种非线性数据结构。
1、树
- 有向树:有确定的根,树根和子树根之间为有向关系。
- 有序树:子树之间存在确定的次序关系。(若子树互换就变成另外一棵树了)
- 无序树:子树之间不存在确定的次序关系。
2、对比树型结构和线性结构的结构特点:
3、一些基本术语:
结点、结点的度、树的度、叶子结点、分支结点、路径、孩子结点、双亲结点、兄弟结点、堂兄弟结点、祖先结点、子孙结点、结点的层次、树的深度、森林
二、二叉树的类型定义
树与二叉树的关系:二叉树是树的基础,一般的树可以转化为二叉树来处理。
1、定义:
二叉树或为空树,或是由一个根节点加上两颗分别称为左子树和右子树的、互不交的二叉树组成。
注:二叉树是一颗度数<=2的有序树。而且二叉树是递归的,即二叉树里面包含二叉树,这是由二叉树的特点决定的。
2、二叉树的五种基本形态:
有三个结点的二叉树的基本形态:
3、两类特殊的二叉树:
- 满二叉树:指的是深度为k且含有2k-1个结点的二叉树。(即二叉树中的所有子二叉树或为空或为满)
- 完全二叉树:树中所含的 n 个结点和满二叉树中编号为 1 至 n 的结点一一对应。
4、二叉树的主要基本操作:
查找类
插入类
删除类
5、二叉树的重要特性:
- 性质1:在二叉树的第i层上至多有 2i-1个结点 。 (i≥1)
- 性质2:深度为k的二叉树上至多含 2k-1 个结点。 (k≥1)
- 性质 3 : 对任何一棵二叉树,若它含有n0 个叶子结点、n2 个度为 2 的结点,则必存在关系式:n0 = n2+1。
- 性质 4 : 具有 n 个结点的完全二叉树的深度为 ⌊ l o g 2 n ⌋ \lfloor log~2~n \rfloor ⌊log 2 n⌋+1 。
- 性质5:若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点:(1) 若 i=1,则该结点是二叉树的根,无双亲,否则,编号为 ⌊ i / 2 ⌋ \lfloor i/2 \rfloor ⌊i/2⌋ 的结点为其双亲结点;(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。
6、牛刀小试:
1、已知二叉树有50个叶子结点,则该二叉树的总结点数至少应有多少个?
2、任意一个有n个结点的二叉树,已知它有m个叶子结点,试证明非叶子结点中有(m-1)个结点的度为2,其余度为1。
3、含n个结点的完全三叉树的高度为多少?
三、二叉树的存储结构(顺序存储于链式存储)
1、顺序存储
由性质5可知:完全二叉树的父——左子、父——右子之间的关系可以通过相应结点的下标之间的简单数学关系完全地表示 出来,因此可以采用顺序存储结构来存储完全二叉树。
由于一般二叉树必须仿照完全二叉树那样存储,可能会浪费很多存储空间,单支树就是一个极端情况。
只有5个结点却需要 25-1个结点来存。
顺序存储结构用于动态性较小的完全二叉树的存储不失为一种简单有效的方法,但不通用。树型数据结构一般采用链式存储方式来存储。
2、二叉树的链式存储表示
- 二叉链表
- 三叉链表
- 双亲链表
- 线索链表
(1)二叉链表
(2)三叉链表
(3)双亲链表
四、二叉树的遍历
1、遍历的概念:
按照某种顺序不重复地访问遍二叉树中的所有结点。此处的访问可以是输出、修改等操作,根据实际需要而定。 使得每个结点均被访问一次,而且仅被访问一次。
对“二叉树”而言,可以有两条搜索路径:
- 先上后下的按层次遍历
- 先左(子树)后右(子树)的遍历
2、先左后右的遍历算法
(1)先(根)序遍历
若二叉树为空树,则空操作;
否则,
1)访问根结点;
2)先序遍历左子树;
3)先序遍历右子树。
(2)中(根)序遍历
若二叉树为空树,则空操作;
否则,
1)中序遍历左子树;
2)访问根结点;
3)中序遍历右子树。
(3)后(根)序遍历
若二叉树为空树,则空操作;
否则,
1)后序遍历左子树;
2)后序遍历右子树;
3)访问根结点。
4、先序遍历算法的递归描述
void Preorder (BiTree T){ // 先序遍历二叉树
if (T) {
visit(T->data); // 访问结点
Preorder(T->lchild); // 遍历左子树
Preorder(T->rchild);// 遍历右子树
}
}
5、遍历算法的应用举例
(1)统计二叉树中叶子结点的个数(先序遍历)
(2)求二叉树的深度(后序遍历)
(3)建立二叉树的存储结构
6、建立二叉树的存储结构
(1)以字符串的形式:根、左子树、右子树,(先序)定义一颗二叉树。
(2)由二叉树的先序和中序序列建树仅知二叉树的先序序列不能唯一确定一颗二叉树。如果同时已知二叉树的中序序列便可以确定一颗二叉树
五、线索二叉树
遍历二叉树的结果是,求得结点的一线性序列,对非线性结构进行线性化操作
1、概念
(1)指向该线性序列中的“前驱”和“后继”的指针称作“线索”。
包含“线索”的存储结构称作“线索链表”,与其相对应的二叉树称作“线索二叉树”。
(2)对线索链表中结点的约定:在二叉链表的结点中增加两个标志域,并作如下规定:
1)若该结点的左子树不空,则Lchild域的指针指向其左子树, 且左标志域的值为“指针 Link=0”;否则,Lchild域的指针指向其“前驱”, 且左标志的值为“线索 Thread=1” 。
2)若该结点的右子树不空,则rchild域的指针指向其右子树, 且右标志域的值为 “指针 Link=0”;否则,rchild域的指针指向其“后继”, 且右标志的值为“线索 Thread=1”。
2、 线索链表的遍历算法
eg: 对中序线索化链表的遍历算法
void InOrder(BiThrTree T,void (*Visit)(TElemType e)){
p = T->lchild; // p指向根结点
while (p != T)
{
// 空树或遍历结束时,
p= =T
while (p->LTag==Link)
p = p->lchild; // 第一个结点
while (p->RTag==Thread && p->rchild!=T)
{
//为右线索时找其右线索
p = p->rchild; Visit (p->data); // 访问后继结点
}
p = p->rchild; // p进至其右子树根
}
} // InOrder
3、建立线索链表
注:红虚线表示前驱、绿虚线表示后继
六、树和森林的表示方法
1、树的三种存储结构:
(1)双亲表示法
(2)孩子链表表示法
(3)树的二叉链表(孩子-兄弟)存储表示法
2、森林和二叉树的对应关系
树和二叉树可以相互转换,所以树的各种操作均可由对应的二叉树的操作来完成。
注意:和树对应的二叉树,其左、右孩子的概念已变为:左是孩子,右是兄弟
eg:
七、树和森林的遍历
1、树的遍历
先根(次序)遍历
后根(次序)遍历
按层次遍历
2、森林的遍历森林
由三部分构成:
(1)森林中第一颗树的根结点
(2)森林中第一颗树的子树森林
(3)森林中其他树构成的森林
森林的先序遍历:
依次从左至右对森林中的每一颗树进行先根遍历
森林的中序遍历:
依次从左至右对森林中的每一颗树进行后根遍历
八、哈夫曼树与哈夫曼编码
- 路径:一个结点到另一个结点所经过的分支。
- 路径长度:分支数目树的路径长度:从根到每一个结点的路径长度之和。(完全二叉树是路径长度最短的二叉树)
1、最优树的定义
结点的路径长度定义为:从根结点到该结点的路径上分支的数目
树的路径长度定义为:树中每个结点的路径长度之和。
树的带权路径长度(WPL)定义为:树中所有叶子结点的带权路径长度之和
eg :
在所有含 n 个叶子结点、并带相同权值的 m 叉树中,必存在一棵其带权路径长度取最小值的树,称为“最优树”。
2、如何构造最优树
(赫夫曼算法)以二叉树为例:
(1)根据给定的 n 个权值 {w1, w2, …, wn},构造 n 棵二叉树的集合 F = {T1, T2, … , Tn}, 其中每棵二叉树中均只含一个带权值 为 wi 的根结点,其左、右子树为空树;
(2)在 F 中选取其根结点的权值为最 小的两棵二叉树,分别作为左、右子树构造一棵新的二叉树,并置这棵新的二叉树根结点的权值为其左、右子树根结点的权值之和;
(3)从F中删去这两棵树,同时加入 刚生成的新树;
(4)重复 (2) 和 (3) 两步,直至 F 中只 含一棵树为止。
eg:已知权值 W={ 5 ,6 , 2 , 9 , 7 }
3、前缀编码
指的是任何一个字符的编码都不是同一字符集中另一个字符的编码的前缀。
利用赫夫曼树可以构造一种不等长的二进制编码,并且构造所得的赫夫曼编码是一种最优前缀编码,即使所传电文的总长度最短。
牛刀小试(1)
牛刀小试(2)
以数据集{2,5,7,9,13}为权值构造一棵huffman树,并计算其带权路径长度。