第六章:树和二叉树

1.树:是n(n≥0)个结点的有限集。(1)有且仅有一个特定的称为根(root)的结点;(2)当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称作这个根的子树 。

2.二叉树:是n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成。

二叉树的性质,存储结构。

性质1: 在二叉树的第i层上至多有2^(i-1)个结点(i>0)。

性质2: 深度为k的二叉树至多有2^k-1个结点(k>0)。

性质3: 对于任何一棵二叉树,如果其终端结点数为n0,度为2的结点数有n2个,则叶子数n0=n2+1

性质4: 具有n个结点的完全二叉树的深度必为 [log2n]+1

性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)。

二叉树的存储结构:

1).顺序存储结构

按二叉树的结点“自上而下、从左至右”编号,用一组连续的存储单元存储。

若是完全/满二叉树则可以做到唯一复原。

不是完全二叉树:一律转为完全二叉树!

方法很简单,将各层空缺处统统补上“虚结点”,其内容为空。

缺点:①浪费空间;②插入、删除不便 

2).链式存储结构

用二叉链表即可方便表示。一般从根结点开始存储。

lchild
data
rchild
优点:①不浪费空间;②插入、删除方便

3.二叉树的遍历。

指按照某种次序访问二叉树的所有结点,并且每个结点仅访问一次,得到一个线性序列。

遍历规则:二叉树由根、左子树、右子树构成,定义为D、 L、R

若限定先左后右,则有三种实现方案:

  DLR               LDR                   LRD

先序遍历           中序遍历          后序遍历

4.线索二叉树

1)线索二叉树可以加快查找前驱与后继结点,实质就是将二叉链表中的空指针改为指向前驱或后继的线索,线索化就是在遍历中修改空指针。

通常规定:对某一结点,若无左子树,将lchild指向前驱结点;若无右子树,将rchild指向后继结点。

还需要设置左右两个tag,用来标记当前结点是否有子树。

若ltag==1,lchild指向结点前驱;若rtag==1,rchild指向结点后继。

2)线索二叉树的存储结构如下:

typedef struct ThreadNode{

ElemType data;

struct ThreadNode *lchild, *rchild;

int ltag, rtag;

}ThreadNode, *ThreadTree;
 

第六章:树和二叉树

5.树和森林

1)树有三种常用存储方式:

①双亲表示法     ②孩子表示法    ③孩子—兄弟表示法

2)森林、树、二叉树的转换

(1)将树转换为二叉树

树中每个结点最多只有一个最左边的孩子(长子)和一个右邻的兄弟。按照这种关系很自然地就能将树转换成相应的二叉树:a.在所有兄弟结点之间加一连线

b.对每个结点,除了保留与其长子的连线外,去掉该结点与其它孩子的连线。
 

第六章:树和二叉树

(2)将一个森林转换为二叉树:

具体方法是:a.将森林中的每棵树变为二叉树;

b.因为转换所得的二叉树的根结点的右子树均为空,故可将各二叉树的根结点视为兄弟从左至右连在一起,就形成了一棵二叉树。

第六章:树和二叉树

(3)二叉树转换为树

是树转换为二叉树的逆过程。

a.加线。若某结点X的左孩子结点存在,则将这个左孩子的右孩子结点、右孩子的右孩子结点、右孩子的右孩子的右孩子结点,都作为结点X的孩子。将结点X与这些右孩子结点用线连接起来。

b.去线。删除原二叉树中所有结点与其右孩子结点的连线。
 

第六章:树和二叉树

(4)二叉树转换为森林:

假如一棵二叉树的根节点有右孩子,则这棵二叉树能够转换为森林,否则将转换为一棵树。

a.从根节点开始,若右孩子存在,则把与右孩子结点的连线删除。再查看分离后的二叉树,若其根节点的右孩子存在,则连线删除。直到所有这些根节点与右孩子的连线都删除为止。

b.将每棵分离后的二叉树转换为树。
 

第六章:树和二叉树

6.树和森林的遍历

树的遍历

① 先根遍历:访问根结点;依次先根遍历根结点的每棵子树。

② 后根遍历:依次后根遍历根结点的每棵子树;访问根结点。

 

森林的遍历

① 先序遍历

若森林为空,返回;

访问森林中第一棵树的根结点;

先根遍历第一棵树的根结点的子树森林;

先根遍历除去第一棵树之后剩余的树构成的森林。

② 中序遍历

若森林为空,返回;

中根遍历森林中第一棵树的根结点的子树森林;

访问第一棵树的根结点;

中根遍历除去第一棵树之后剩余的树构成的森林。

 

7.哈夫曼树及其应用

Huffman树:最优二叉树(带权路径长度最短的树)

Huffman编码:不等长编码。

树的带权路径长度:第六章:树和二叉树(树中所有叶子结点的带权路径长度之和)

构造Huffman树的基本思想:权值大的结点用短路径,权值小的结点用长路径。

构造Huffman树的步骤(即Huffman算法):

(1) 由给定的 n 个权值{ w1, w2, …, wn }构成n棵二叉树的集合F = { T1, T2, …, Tn } (即森林) ,其中每棵二叉树 Ti 中只有一个带权为 wi 的根结点,其左右子树均空。

(2) 在F 中选取两棵根结点权值最小的树 做为左右子树构造一棵新的二叉树,且让新二叉树根结点的权值等于其左右子树的根结点权值之和。

(3) 在F 中删去这两棵树,同时将新得到的二叉树加入 F中。

(4) 重复(2) 和(3) , 直到 F 只含一棵树为止。这棵树便是Huffman树。

具体操作步骤:
 

第六章:树和二叉树

第六章:树和二叉树

应用:用于通信编码

  在通信及数据传输中多采用二进制编码。为了使电文尽可能的缩短,可以对电文中每个字符出现的次数进行统计。设法让出现次数多的字符的二进制码短些,而让那些很少出现的字符的二进制码长一些。假设有一段电文,其中用到 4 个不同字符A,C,S,T,它们在电文中出现的次数分别为 7 , 2 , 4 , 5 。把 7 , 2 , 4 , 5 当做 4 个叶子的权值构造哈夫曼树如图(a) 所示。在树中令所有左分支取编码为 0 ,令所有右分支取编码为1。将从根结点起到某个叶子结点路径上的各左、右分支的编码顺序排列,就得这个叶子结点所代表的字符的二进制编码,如图(b) 所示。这些编码拼成的电文不会混淆,因为每个字符的编码均不是其他编码的前缀,这种编码称做前缀编码。
 

第六章:树和二叉树