数据结构 - 树 - 最优二叉树的基本介绍
最优二叉树的基本概念
路径:从一个祖先结点到子孙结点之间的分支构成这两个结点之间的路径。
路径长度:路径上分支数目称为路径长度。
结点的权:给树中的结点所赋的具有物理意义的值。
结点的带权路径长度:从根到该结点的路径长度与该结点权的乘积。
树的带权路径长度:树中所有叶子结点的带权路径长度之和,可表示为:
哈夫曼树:假设有 \(n\) 个权值 \((w_1,w_2,\cdots,w_n)\),构造有 \(n\) 个叶子结点的二叉树,每个叶子结点有一个 \(w_i\) 作为它的权值。则带权路径长度最小的二叉树称为哈夫曼(Huffman)树。
构造哈夫曼树
构造哈夫曼树可用如下算法:
-
根据给定的 \(n\) 个权值 \((w_1,w_2,\cdots,w_n)\),构造 \(n\) 棵只有根结点的二叉树,并令根结点权值为 \(w_i\)。
-
在森林中选取两棵根结点权值最小的树作为左右子树,构造一棵新的二叉树,置新二叉树的根结点权值为其左右子树根结点权值之和。
-
在森林中删除这两棵子树,同时将新得到的二叉树加入森林中。
-
重复二、三步骤,直到森林中只包含一棵树为止,这棵树即哈夫曼树。
下面图示一个例子。已知权值为 \(W=\{5,6,2,9,7\}\)。
哈夫曼树的存储
我们可以用静态三叉链表来存储哈夫曼树。
// 静态三叉链表类型定义
typedef struct {
unsigned int weight;
unsigned int parent, lchild, rchild;
} HTNode, * HuffmanTree;
哈夫曼树是正则的二叉树(也即没有度为 \(1\) 的结点)。
对于一棵哈夫曼树,假设有 \(n\) 个叶子结点,则哈夫曼树共有 \(2 n - 1\) 个结点。(证明用到二叉树的第三点性质:\(n_0 = n_2 + 1\))
哈夫曼编码
前缀编码:任何一个字符的编码都不是同一个字符集中另一个字符的编码的前缀。
我们利用哈夫曼树:
-
可以构造一种不等长的二进制编码;
-
并且构造所得的哈夫曼编码是一种最优前缀编码;
-
能够使所传电文的总长度最短。
编码
根据字符出现的频率构造哈夫曼树。
-
将树中结点引向其左孩子的分支标为 \(0\)。
-
将树中结点引向其右孩子的分支标为 \(1\)。
-
每个字符的编码为:从根到每个叶子结点的路径上得到的 \(0\)、\(1\) 序列。
例子:字符集 \(D=\{C,A,S,T,;\}\),字符频率 \(w = \{2,4,2,3,3\}\)。
译码
从哈夫曼树的树根出发,从待译码的电文中逐位取码。若编码是 \(0\),则向左走;若编码是 \(1\),则向右走。一直到达叶子结点,则译出一个字符。再重新从根出发,到达叶子结点再译出一个字符,一直下去直到电文结束。
考虑电文为 \(1101000\),可以得到相应的译文为 \(\text{CAT}\)。