赫夫曼树(Huffman Tree),又称最优二叉树,是一类带权路径长度最短的树。假设有n个权值{w1,w2,…,wn},如果构造一棵有n个叶子节点的二叉树,而这n个叶子节点的权值是{w1,w2,…,wn},则所构造出的带权路径长度最小的二叉树就被称为赫夫曼树。
树的带权路径长度指树中所有叶子节点到根节点的路径长度与该叶子节点权值的乘积之和,如果在一棵二叉树中共有n个叶子节点,用Wi表示第i个叶子节点的权值,Li表示第i个也叶子节点到根节点的路径长度,则该二叉树的带权路径长度 WPL=W1 x L1 + W2 x L2 + … Wn x Ln。
根据节点的个数以及权值的不同,赫夫曼树的形状也各不相同,赫夫曼树具有如下特性:
- 对于同一组权值,所能得到的赫夫曼树不一定是唯一的。
- 赫夫曼树的左右子树可以互换,因为这并不影响树的带权路径长度。
- 带权值的节点都是叶子节点,不带权值的节点都是某棵子二叉树的根节点。
- 权值越大的节点越靠近赫夫曼树的根节点,权值越小的节点越远离赫夫曼树的根节点。
- 赫夫曼树中只有叶子节点和度为2的节点,没有度为1的节点。
- 一棵有n个叶子节点的赫夫曼树共有2n-1个节点。
赫夫曼树的构建步骤如下:
- 将给定的n个权值看做n棵只有根节点(无左右孩子)的二叉树,组成一个集合HT,每棵树的权值为该节点的权值。
- 从集合HT中选出2棵权值最小的二叉树,组成一棵新的二叉树,其权值为这2棵二叉树的权值之和。
- 将步骤2中选出的2棵二叉树从集合HT中删去,同时将步骤2中新得到的二叉树加入到集合HT中。
- 重复步骤2和步骤3,直到集合HT中只含一棵树,这棵树便是赫夫曼树。
假如给定如下5个权值:
则按照以上步骤,可以构造出如下面左图所示的赫夫曼树,当然也可能构造出如下面右图所示的赫夫曼树,这并不是唯一的。
Huffman编码
赫夫曼树的应用十分广泛,比如众所周知的在通信电文中的应用。在等传送电文时,我们希望电文的总长尽可能短,因此可以对每个字符设计长度不等的编码,让电文中出现较多的字符采用尽可能短的编码。为了保证在译码时不出现歧义,我们可以采取如下图所示的编码方式
即左分支编码为字符0,右分支编码为字符1,将从根节点到叶子节点的路径上分支字符组成的字符串作为叶子节点字符的编码,这便是赫夫曼编码。我们根据上面左图可以得到各叶子节点的赫夫曼编码如下:
权值为5的也自己节点的赫夫曼编码为:11
权值为4的也自己节点的赫夫曼编码为:10
权值为3的也自己节点的赫夫曼编码为:00
权值为2的也自己节点的赫夫曼编码为:011
权值为1的也自己节点的赫夫曼编码为:010
代码实现:
#include<iostream>
using namespace std; //单独的命名空间
#define maxSize 100
#pragma warning(disable:4996)
//哈弗曼树的存储结构
typedef struct Node {
int weight; //权值
int parents; //父结点
int lchild; //左节点
int rchild; //右节点
}HTNode, *HuffmanTree;
//哈夫曼编码的存储结构
typedef char **HuffmanCode;
//声明
HuffmanTree create_HuffmanTree(int *wet, int n);
void select_minium(HuffmanTree HT, int k, int &min1, int &min2);
int min(HuffmanTree HT, int k);
void HuffmanCoding(HuffmanTree HT, HuffmanCode &HC, int n);
//构造哈弗曼树
HuffmanTree create_HuffmanTree(int *wet, int n) {
//wet:权值,n:数量
//度为0的节点数为n,度为1的节点数为n-1
int total = 2 * n - 1;//所有用到的节点数
//申请空间,空间类型为HuffmanTree,空间大小为total * sizeof(HTNode)
HuffmanTree HT = (HuffmanTree) malloc(total * sizeof(HTNode)