什么是哈夫曼树
带权路径长度:设二叉树有n个叶子结点,每个叶子结点带有权值Wk,从根结点到每个叶子结点的长度为lk,则每个叶子结点的带权路径长度之和就是:
【例】有五个叶子结点,权值分别为[1,2,3,4,5],用此权值序列可以构造出不同的多个二叉树。
分别计算他们的带权路径长度:
1、WPL = 5X1 + 4X2 + 3X3 + (1+2)X4 = 34
2、WPL = 1X1 + 2X2 + 3X3 + (5+4)X4 = 50
3、WPL = 3X2 + (4+5)X2 + (1+2)X3 = 33
哈夫曼树的构造
构造过程即为在权值中找到两个最小的元素,求和后,删除他们,并将和最为新的元素。
这个过程若排序实现的话,每次计算都要重新排序找最小,比较繁琐,可以根据最小堆实现,
时间复杂度为N*logN。
这是最大堆的删除函数,最小堆与其相似。
MaxHeap DeleteMax(MaxHeap H)
{
int Parent,Child;
int Maxitem,temp;
if(IsEmpty(H)){
printf("最大堆已经为空");
return;
}
Maxitem=H->element[1];//先把要删除的要素保存起来
temp=H->element[H->size--];//找到最边界的元素存起来,因为删除了了个元素,size要再减去1.
for(Parent=1;Parent*2<H->size;Parent=Child){//parent*2就是其左儿子,如果存在就可以进行循环。
Child=Parent*2;
if((Child!=H->size)&&(H->element[Child]<H->element[Child+1]))
Child++;//如果右儿子大,就切换到右儿子
if(temp>=H->element[Child])break;
else//否则就与左儿子交换
H->element[Parent]=H->element[Child];//循环目的就是找到其左右儿子中较大的那个
}
H->element[Parent]=temp;
return H;
}
建造哈夫曼树的代码:
typedef struct TreeNode *HuffmanTree;
struct TreeNode{
int Weight;//权值
HuffmanTree Left,Right;//左右指针
};
HuffmanTree Huffman(MinHeap H){
int i;
HuffmanTree T;
BuildMinHeap(H);//将权值按照堆的特性构造成最小堆
for(i = 1; i < H->Size; i++){
T=malloc(sizeof(struct TreeNode));
T->Left = DeleteMin(H);//从最小堆中删除最小的元素,作为树的左儿子
T->Right = DeleteMin(H);//在删除一个最小的元素,作为树的右儿子
T->Weight = T->Left->Weight+T->Right->Weight;//父亲结点等于左右儿子的权值之和。
Insert(H,T);//将新生成的结点插入哈夫曼树中。
}
T = DeleteMin(H);
return T;
}
哈夫曼树的特点
1、没有度为1的结点。
2、n个叶子结点的哈夫曼树共有2n-1个结点。
3、哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树。
4、对同一组权值,存在不同构的两棵哈夫曼树。
哈夫曼编码
给定一段字符串,如何对字符进行编码,使得该字符串的编码存储空间最小。
【例】假设有一段文本包含58个字符,并由以下7个字符字符构成:a,e,i,s,t,空格(sp),换行(nl),只是出现的次数不同。如何对这7个字符编码(只能用0和1表示一个字符)?
方法1:用等长ASCII码:(每个字符用8位表示)58X8=464位。
方法2:用等长3位编码:(3位数,每位选择0或1表示,能表示8种字符)58X3=174位。
方法3:出现频率高的字符编码短,出现频率低的字符编码长。
方法3要求避免编码二义性,可以用二叉树进行编码。
为了避免二义性,首先要了解前缀码。任何一字符的编码都不是另一字符的前缀码,因此树形结构可以避免二义性的出现。
例题
根据边与结点数目的对应关系,并且哈夫曼树没有只有一个儿子的节点,列出等式求解。
根据0或者1尝试画出每棵树,发现A选项中存在含有一个儿子的节点。
根据出现次数构造哈夫曼树,结果应为每个结点的高度与出现次数乘积的和。