数据结构(三)—— 树(9):哈夫曼树和哈夫曼编码
作者:互联网
数据结构系列内容的学习目录 → \rightarrow →浙大版数据结构学习系列内容汇总。
- 9. 哈夫曼树和哈夫曼编码
-
- 9.1 什么是哈夫曼树
- 9.2 哈夫曼树的构造
- 9.3 哈夫曼编码
- 9.4 最小堆实现哈夫曼树
9. 哈夫曼树和哈夫曼编码
9.1 什么是哈夫曼树
给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
例: 将百分制的考试成绩转换成五分制的成绩。
if(score < 60 ) grade =1;
else if(score <70 ) grade =2;
else if( score < 80 ) grade =3;
else if( score <90 ) grade =4;
else grade =5;
判定树如下图所示。
如果考虑学生成绩的分布的概率:
分数段 | 0-59 | 60-69 | 70-79 | 80-89 | 90-100 |
---|---|---|---|---|---|
比例 | 0.05 | 0.15 | 0.40 | 0.30 | 0.10 |
查找效率:0.05×1+0.15×2+0.4×3+0.3×4+0.1×4= 3.15。
修改判定树为如下图所示。
if(score <80)
{
if(score < 70)
if(score < 60) grade =1;
else grade = 2;
else grad=3;
}
else if(score < 90) grade =4;
else grade =5;
查找效率:0.05×3+0.15×3+0.4×2+0.3×2+0.1×2=2.2。
哈夫曼树要解决的问题: 如何根据结点不同的查找频率构造更有效的搜索树?
哈夫曼树的定义:
⋄ \diamond ⋄ 带权路径长度(WPL): 设二叉树有n个叶子结点,每个叶子结点带有权值 w k w_{k} wk,从根结点到每个叶子结点的长度为 l k l_{k} lk,则每个叶子结点的带权路径长度之和就是 W P L = ∑ k = 1 n w k l k WPL=\sum _{k=1}^{n}w_{k}l_{k} WPL=∑k=1nwklk
⋄ \diamond ⋄ 最优二叉树或哈夫曼树: WPL最小的二叉树。
例: 有五个叶子结点,它们的权值为{1,2,3,4,5},用此权值序列可以构造出形状不同的多个二叉树,如下图所示。
9.2 哈夫曼树的构造
每次把权值最小的两棵二叉树合并,如下图所示。
哈夫曼树的构造代码如下所示。
typedef struct TreeNode *HuffmanTree;
struct TreeNode {
int weight;
HuffmanTree Left,Right;
}
HuffmanTree Huffman(MinHeap H)
{ //假设H->Size个权值已经存在H->Elements[]->weight里
int i;
HuffmanTree T;
BuilGMinHeap(H); //将H->Elements[]按权值调整为最小堆
for(i = 1; i <H->Size; i++) //做H->Size-1次合并
{
T = malloc(sizeof(struct TreeNode)); //建立新结点
T->Left = DeleteMin(H); //从最小堆中删除一个结点,作为新T的左子结点
T->Right = DeleteMin(H); //从最小堆中删除一个结点,作为新T的右子结点
T->weight =T->Left->weight + T->Right->weight; //计算新权值
Insert(H,T); //将新T插入最小堆
}
T = DeleteMin(H);
return T;
}
整体复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)。
哈夫曼树的特点:1. 没有度为 1 的结点;
2. n 个叶结点的哈夫曼树共有 2n-1 个结点;
n 0 n_{0} n0:叶结点总数
n 1 n_{1} n1:只有一个儿子的结点总数(没有度为 1 的结点, n 1 = 0 n_{1}=0 n1=0)
n 2 n_{2} n2:有2个儿子的结点总数( n 2 = n 0 − 1 n_{2}=n_{0}-1 n2=n0−1)
总结点数: n 0 + n 1 + n 2 = 2 n 0 − 1 n_{0}+n_{1}+n_{2} = 2n_{0} -1 n0+n1+n2=2n0−1
3. 哈夫曼树的任意非叶结点的左右子树交换后仍是哈夫曼树;
4. 对同一组权值 { w 1 , w 2 , . . . , w n } \{ w_{1}, w_{2}, ..., w_{n} \} {w1,w2,...,wn},是否存在不同构的两棵哈夫曼树呢? 答案是可能的!
例: 对一组权值{1,2,3,3},不同构的两棵哈夫曼树如下图所示。
9.3 哈夫曼编码
给定一段字符串,如何对字符进行编码,可以使得该字符串的编码存储空间最少?
例: 假设有一段文本,包含58个字符,并由以下7个字符构:a,e,i,s, t,空格(sp),换行(nl) ;这7个字符出现的次数不同。如何对这7个字符进行编码,使得总编码空间最少?
分析: (1)用等长ASCII编码:58×8= 464位;
(2)用等长3位编码:58×3=174位;
(3)不等长编码:出现频率高的字符用的编码短些,出现频率低的字符则可以编码长些?
问题: 怎么进行不等长编码? 如何避免二义性?
前缀码prefix code: 任何字符的编码都不是另一字符编码的前缀,可以无二义地解码。
二叉树用于编码:(1)左右分支:0、1;
(2)字符只在叶结点上。
例: 四个字符的频率:a:4, u:1,x:2,z:1,如下图所示。
怎么构造一颗编码代价最小的二叉树?
对上面例子中包含58个字符的一段文本进行编码,使得总编码空间最少,如下图所示。
C i C_{i} Ci | a | e | i | s | t | sp | nl |
---|---|---|---|---|---|---|---|
f i f_{i} fi | 10 | 15 | 12 | 3 | 4 | 13 | 1 |
9.4 最小堆实现哈夫曼树
最小堆实现哈夫曼树代码如下所示。
#include<iostream>
using namespace std;
#define MaxSize 1000
#define MinData -1000
int A[] = { 1,3,5,8 }; // 预先定义好一组权值
int A_length = 4; // 定义其长度
typedef struct HeapStruct *MinHeap;
typedef struct TreeNode *HuffmanTree;
struct HeapStruct { // 存放哈夫曼树的堆
HuffmanTree *data; // 存值的数组
int size; // 堆的当前大小
int capacity; // 最大容量
};
struct TreeNode { // 哈夫曼树
int weight; //权值
HuffmanTree Left; // 左子树
HuffmanTree right; // 右子树
};
MinHeap Create_H(); // 初始化堆
HuffmanTree Create_T(); // 初始化哈夫曼树
void sort(MinHeap H, int i); // 调整子最小堆
void adjust(MinHeap H); // 调整最小堆
void BuildMinHeap(MinHeap H); // 建堆
HuffmanTree Delete(MinHeap H); // 删除最小堆元素
void Insert(MinHeap H, HuffmanTree Huff); // 插入最小堆元素
void PreOrderTraversal(HuffmanTree Huff); // 先序遍历
HuffmanTree Huffman(MinHeap H); // 哈夫曼树的构建
// 初始化堆
MinHeap Create_H()
{
MinHeap H;
HuffmanTree Huff;
H = (MinHeap)malloc(sizeof(struct HeapStruct));
H->data = (HuffmanTree *)malloc(sizeof(struct TreeNode) * (MaxSize + 1));
H->capacity = MaxSize;
H->size = 0;
// 给堆置哨兵
Huff = Create_T(); // 初始化哈夫曼树
Huff->weight = MinData;
H->data[0] = Huff;
return H;
}
// 初始化哈夫曼树
HuffmanTree Create_T()
{
HuffmanTree Huff;
Huff = (HuffmanTree)malloc(sizeof(struct TreeNode));
Huff->weight = 0;
Huff->Left = NULL;
Huff->right = NULL;
return Huff;
}
// 调整子最小堆
void sort(MinHeap H, int i)
{
int Parent, Child;
int temp = H->data[i]->weight; // 取出当前"根结点"值
for (Parent = i; Parent * 2 <= H->size; Parent = Child)
{
Child = 2 * Parent;
if ((Child != H->size) && (H->data[Child + 1]->weight < H->data[Child]->weight))
Child++;
if (H->data[Child]->weight >= temp)
break;
else
H->data[Parent] = H->data[Child];
}
H->data[Parent]->weight = temp;
}
// 调整最小堆
void adjust(MinHeap H)
{
for (int i = H->size / 2; i > 0; i--)
sort(H, i);// 每个"子最小堆"调整
}
// 建堆
void BuildMinHeap(MinHeap H)
{
// 将权值读入堆中
HuffmanTree Huff;
for (int i = 0; i < A_length; i++)
{
Huff = Create_T();
Huff->weight = A[i];
H->data[++H->size] = Huff;
}
// 调整堆
adjust(H);
}
// 删除最小堆元素
HuffmanTree Delete(MinHeap H)
{
int Parent, Child;
HuffmanTree T = H->data[1]; // 取出根结点的哈夫曼树
HuffmanTree temp = H->data[H->size--]; // 取出最后一个结点哈夫曼树的权值
for (Parent = 1; Parent * 2 <= H->size; Parent = Child)
{
Child = 2 * Parent;
if ((Child != H->size) && (H->data[Child + 1]->weight < H->data[Child]->weight))
Child++;
if (H->data[Child]->weight >= temp->weight)
break;
else
H->data[Parent] = H->data[Child];
}
H->data[Parent] = temp;
// 构造一个 HuffmanTree 结点,附上刚才取出来的权值,返回该结点
return T;
}
// 插入一个哈夫曼树
void Insert(MinHeap H, HuffmanTree Huff)
{
int weight = Huff->weight; // 取出权值
int i = ++H->size;
for (; H->data[i / 2]->weight > weight; i /= 2)
H->data[i] = H->data[i / 2];
H->data[i] = Huff;
}
//先序遍历
void PreOrderTraversal(HuffmanTree Huff)
{
if (Huff)
{
cout << Huff->weight << " ";
PreOrderTraversal(Huff->Left);
PreOrderTraversal(Huff->right);
}
}
// 哈夫曼树的构造
HuffmanTree Huffman(MinHeap H)
{
HuffmanTree T;
BuildMinHeap(H); // 建堆
int times = H->size;
// 做 times-1 次合并
for (int i = 1; i < times; i++)
{
T = (HuffmanTree)malloc(sizeof(struct TreeNode));
T->Left = Delete(H); // 从堆中删除一个结点,作为新T的左子结点
T->right = Delete(H); // 从堆中删除一个结点,作为新T的右子结点
T->weight = T->Left->weight + T->right->weight; // 重新计算权值
Insert(H, T); // 再加进堆中
}
T = Delete(H);
return T;
}
int main()
{
MinHeap H;
HuffmanTree Huff;
H = Create_H();
Huff = Huffman(H);
PreOrderTraversal(Huff);
cout << endl;
system("pause");
return 0;
}
代码运行结果如下图所示。
标签:编码,结点,哈夫曼,weight,Huff,HuffmanTree,数据结构,data 来源: https://blog.51cto.com/u_15178976/2981919