哈夫曼树与哈夫曼编码
-
相关概念
- 路径: 树中一个结点到另一个结点之间的分支序列。
- 路径长度: 路径上分支的条数
- 结点的权: 给结点赋予的数值
- 带权路径长度:结点的权值与该结点到树根间路径长度的乘积
- 树的带权路径长度:树种所有叶结点的带权路径长度之和,记为: WPL=(W1*L1 + W2*L2 + W3*L3 +......+ Wn*Ln)
- 哈夫曼树:(最优二叉树)是指在叶子结点个数n和叶子权值Wi确定的情况下,树的带权路径长度值WPL为最小的二叉树。
【注】n为叶结点个数 ; Wi为第i个叶结点的权值; Li为第i个叶结点的带权路径长度
-
引例
【注】哈夫曼树依据最优二叉树的特点(权值越大,离根越近~)给出的构造方法,因此最优二叉树又称哈夫曼树
-
哈夫曼树的建立
- 初始化: 按给定n个权值{W1,W2,W3.......Wn},构造二叉树的集合F={T1,T2,T3......Tn}, 其中, ,每棵二叉树只含一个权值为Wi的根节点,左右子树为空。
- 在F中选取根节点权值最小的两棵二叉树,分别作为左右子树 并 将其和作为根节点 构造新的二叉树
- 在F中删除所选中的两棵小树,将新生成的二叉树插入F
- 重复2.和3.两步,直到F中只剩一棵树为止。
【注例】:已知权值W={5,8,4,9,3,4},构造哈夫曼树
步骤:
1.初始化:
2.建新树:
3.删结点:
4:直到F中只剩一个结点为止,结束:
-
哈夫曼树特点:
1. 哈夫曼树没有度为1的结点:n1==0;
2. n 个叶子结点的哈夫曼树共有 2n-1 个结点。(N=n0+n1+n2 与 n1=0与n2=n0-1可证)
3.哈夫曼树的任意非叶结点的左右子树交换顺序后仍是哈夫曼树
4. 对于同一组权值{W1,W2....Wn},当有重复权值时,存在不同构的哈夫曼树
-
哈夫曼算法实现:
【注】在哈夫曼树进行编码译码时,要用到双亲及孩子节点的信息,所以一般我们采用静态三叉链表来存储哈夫曼树.
-----------------------@定义存储结构:--------------
#define N
#define M 2*N+1
typedef struct node{
int weight;//权值
int parent;//父节点
int Lchild;//左孩子
int Rchild;//右孩子
}Hffuman,HuffmanT[M+1];
------------------@哈夫曼树的构建过程-----------------------
- 初始化: (下标为1--->n 的结点) 叶子节点赋权值,父子游标均初始化为0 (下标为n+1 --->2n-1 的结点) 所有域都赋为0;
- i=n+1;i<2n-1; i++重复执行以下两步构建哈夫曼树: a. 选择最小两棵最小树 : 在下标为1--->n选择根结点(父域为0的结点)权值最小的两棵二叉树 b. b. 构造新树: 计算新结点i的权值,置新节点的左右子域以及2棵子树的父域 (隐含了删除节点操作)
---------------- @代码实现:---------------------
void CreateHFM(Huffman HT,int w[],int n){
for(i=1;i<=n;i++){
HuffmanT[i]={w[i],0,0,0};
}//叶结点初始化
for(i=n+1;i<=2*n-1;i++){
HuffmanT[i]={0,0,0,0};
} //分支节点初始化
for(i=n+1;i<2*n-1;i++){
select(HuffmanT[i],&min1,&min2);
HuffmanT[i].weight=HuffmanT[min1].weight+HuffmanT[min2].weight;
HuffmanT[i].Lchild=HuffmanT[min1];
HuffmanT[i].Rchild=HuffmanT[min2];
HuffmanT[min1].parent=HuffmanT[i];
HuffmanT[min2].parent=HuffmanT[i];
}
}
-
哈弗曼编译码:
【定义】: 规定哈夫曼树中的左分支为0,右分支为1,则从根节点到每个叶结点所经过的分支对应的0和1组成的序列便为该节点对应字符的编码。因此哈夫曼编码属于二进制编码。
【特点】: 1.权值越大的字符编码越短,反之越长。
2.在一组哈弗曼编码中,不可能出现一个字符的哈夫曼编码是一个字符哈夫曼编码的前缀。
【结论】:1.哈夫曼编码是前缀编码 2.哈夫曼编码是最优编码
【编码】:由哈夫曼树构造哈夫曼编码,需要从叶子出发,走一遍从叶子到根的路径,编码是由后往前生成,每经过一条分支,就得到 一位哈夫曼编码值,其中,节点为双亲的左孩子时当前编码为0,为双亲的右孩子时当前编码值为1
【译码】:需要从根节点出发走一遍从根到叶子的路径,当前编码为0时走向左孩子,编码为1 时走向右孩子,当走到叶子节点时就完成了对一个字符的编码。
-
哈弗曼编码的算法实现:
【注】在算法中,生成的编码暂存在字符串cd中,一个叶子节点的编码构造完成,再将其转存到最终的字符数组中。
typedef char* Huffmancode[n+1];
char *cd; //从叶子到根逐个保存哈夫曼编码
int start;
CreateHuffmanCode(HuffmanT HT,HuffmanCode hc,int n){
cd=(char*)malloc(n*sizeof(char));
cd[n-1]='\0'; //从右向左逐位求编码,先置串尾结束符
for(i=1;i<=n;i++){
start=n-1;
//child指示当前节点,parent为其双亲
child=i;
parent=HT[child].parent;
while(parent!=0){ //只要没到根节点就一直循环
if(HT[parent].Lchild==child)
{
cd[start--]='0'; //是双亲的左孩子就编码为0;
}
else{
cd[start--]='1'; //是双亲的右孩子就编码为1;
}
//继续回溯
child=parent; //child更新为父亲
parent=HT[parent].parent; //parent变为爷爷
}
hc[i]=(char*)malloc((n-start)*sizeof(char));
strcpy(hc[i],&cd[start]);
}
}
-
正经致谢: