认识Huffman码的发展过程,学习Huffman算法思想,探索Huffman算法在数据压缩问题上的运用。
随着科技的发展,计算机成为了当今信息时代的产物。计算机的使用难免会存在信息的传输,由于计算机主要以二进制编码来进行交流,因此将信息编码成二进制位串的形式是必须的。如何将信息编码成合适的二进制位串使得数据的传输效率变得更高,对此人们进行了相当长的历史研究,从莫尔斯码到前缀码的最优前缀码,再到Huffman编码。目前对数据的压缩传输主要使用Huffman算法思想。
输入: 一个符号集 S = { x 1 , x 2 , ⋯ , x n > } S=\left\{ x_{1},x_{2}, \cdots ,x_{n} >\right \} S={x1,x2,⋯,xn>} 和每个符号出现的频率 f x i f_{x_{i}} fxi;
输出: 每个符号集中元素所对应的前缀码 γ j ( x i ) \gamma_{j} \left( x_{i} \right) γj(xi),频率高的符号二进制编码长度相对较短,而频率高的符号二进制编码长度相对较长,并且任何二进制编码的前缀不会与其它编码匹配;
前缀码: 是一种编码方式,使得符号集 S = { x 1 , x 2 , ⋯ , x n } S=\left\{ x_{1},x_{2}, \cdots ,x_{n} \right \} S={x1,x2,⋯,xn} 中每个符号 x i x_{i} xi 都有一个独一无二的二进制编码,这个编码不是其它任何一个编码的前缀,用于解决在计算机或其它设备之间传输或储存数据时候,莫尔斯码中因为“暂停”而产生的不确定性问题。
例如:
符号 x i x_{i} xi | 概率 f x i f_{x_{i}} fxi | 前缀码 γ 1 ( x i ) \gamma_{1} \left( x_{i} \right) γ1(xi) |
---|---|---|
A | 0.05 | 01 |
B | 0.2 | 10 |
C | 0.25 | 001 |
D | 0.1 | 110 |
E | 0.4 | 111 |
最优前缀码: 是一种前缀码,通过使用最短的二进制编码来表示一个符号集 S = { x 1 , x 2 , ⋯ , x n } S=\left\{ x_{1},x_{2}, \cdots ,x_{n} \right \} S={x1,x2,⋯,xn}。尽可能的将出现频率比较高的符号用较短的编码表示,而出现频率比较低的符号用比较长的编码表示,使得出来到平均编码长度 A B L ( γ j ) ABL\left( \gamma_{j} \right) ABL(γj) 最小。
例如:
符号 x i x_{i} xi | 概率 f x i f_{x_{i}} fxi | 前缀码 γ 1 ( x i ) \gamma_{1} \left( x_{i} \right) γ1(xi) | 最优前缀码 γ 2 ( x i ) \gamma_{2} \left( x_{i} \right) γ2(xi) |
---|---|---|---|
A | 0.05 | 01 | 1100 |
B | 0.2 | 10 | 111 |
C | 0.25 | 001 | 10 |
D | 0.1 | 110 | 01 |
E | 0.4 | 111 | 0 |
设: ∣ γ j ( x i ) ∣ |\gamma_{j} \left( x_{i} \right)| ∣γj(xi)∣ 为 γ j ( x i ) \gamma_{j} \left( x_{i} \right) γj(xi) 的长度,
编码长度 = ∑ x ∈ S n f x i ∣ γ j ( x i ) ∣ = n ∑ x ∈ S f x i ∣ γ j ( x i ) ∣ A B L ( γ j ) = ∑ x ∈ S f x i ∣ γ j ( x i ) ∣ \begin{align*} \text{编码长度}&=\sum_{x\in S}^{}n f_{x_{i}}|\gamma_{j} \left( x_{i} \right)|\\ &=n\sum_{x\in S}^{} f_{x_{i}}|\gamma_{j} \left( x_{i} \right)|\\ ABL\left( \gamma_{j} \right)&=\sum_{x\in S}^{} f_{x_{i}}|\gamma_{j} \left( x_{i} \right)| \end{align*} 编码长度ABL(γj)=x∈S∑nfxi∣γj(xi)∣=nx∈S∑fxi∣γj(xi)∣=x∈S∑fxi∣γj(xi)∣
A B L ( γ 1 ) = 0.05 × 2 + 0.2 × 2 + 0.25 × 3 + 0.1 × 3 + 0.4 × 3 = 2.75 A B L ( γ 2 ) = 0.05 × 4 + 0.2 × 3 + 0.25 × 2 + 0.1 × 4 + 0.4 × 1 = 2.10 \begin{align*} ABL\left( \gamma_{1} \right)&=0.05\times 2+0.2\times 2+0.25\times 3+0.1\times 3+0.4\times 3=2.75\\ ABL\left( \gamma_{2} \right)&=0.05\times 4+0.2\times 3+0.25\times 2+0.1\times 4+0.4\times 1=2.10 \end{align*} ABL(γ1)ABL(γ2)=0.05×2+0.2×2+0.25×3+0.1×3+0.4×3=2.75=0.05×4+0.2×3+0.25×2+0.1×4+0.4×1=2.10
∵ A B L ( γ 1 ) > A B L ( γ 2 ) \because ABL\left( \gamma_{1} \right)>ABL\left( \gamma_{2} \right) ∵ABL(γ1)>ABL(γ2)
∴ \therefore ∴ 可得出最优前缀码在表示符号时产生的二进制编码长度会比较短。
Huffman算法: 通过Huffman树的建立来获取Huffman编码。
Huffman编码: 是一种前缀码,也是最优前缀码,可以形象的通过Huffman树查找制定符号得到,向左孩子查找一次编码一个0,向右孩子查找一次编码一个1。
Huffman树: 又叫做最优树,是一种带权路径长度最短的树,符号结点都为叶子结点,且权值越大的结点越靠近树根结点。
Huffman树构建方法: 保持符号作为树的叶子结点,每次选取权值最小的结点作为孩子结点生成双亲结点,双亲结点可为两个孩子权值之和,再选择剩下的结点中包含新生成的双亲结点的两个最小权值结点,并重复上述过程。
CreateHuffmanTree(TreeHead,Symbol,Frequence)
create an priority queue Q by CrePriQueue(Symbol,Frequence)
while Q has more than one element
remove the two nodes t1 and t2 with the lowest frequence from Q
insert t1 and t2 into tree by TreeHead
create a new tree node t with t1 and t2 as its left and right children
frequence[t1] + frequence[t2] as its frequence
insert t into Q
return the head node of the tree : TreeHead
CreateHuffmanCode(HuffManTreeHead,Symbol)
SymbolLen = length of Symbol
create a two-dimensional array HC to store the Huffman codes
create a one-dimensional array Code to store temporary Huffman codes
for i=1 to SymbolLen
c = i
f = fatherNode
while fatherNode is not NULL
if Symbol[i] is fatherNode the left child
append "0" to Code
c = node of left child
else
append "1" to Code
c = node of right child
HC[i] = Code
reset the array Code
return HC
Huffman编码总时间复杂度: 主要由构建优先队列,优先队列的插入提取,构建Huffman树,求Huffman编码分别对于给定的符号种类数量 n n n 的时间复杂度决定,主要为 O ( n log 2 n ) O\left(n \log_{2}{n} \right) O(nlog2n);
构建优先队列: 通常使用堆来实现,也可以使用排序来实现,时间复杂度为 O ( n log 2 n ) O\left(n \log_{2}{n} \right) O(nlog2n);
优先队列取出和插入: 优先队列的取出和插入Huffman树的时间复杂度为 O ( log 2 n ) O\left( \log_{2}{n} \right) O(log2n),
构建Huffman树: 从优先队列中反复取出最小权值的两个结点组成一个新的结点,对于优先队列中的 n n n 个结点时间复杂度为 O ( n log 2 n ) O\left(n \log_{2}{n} \right) O(nlog2n);
求Huffman编码:通过从最小的叶子结点开始回溯到根节点,求出每个叶子结点符号所对应的Huffman编码,时间复杂度为 O ( n log 2 n ) O\left(n \log_{2}{n} \right) O(nlog2n);
算法拓展 :总的来说算法的时间复杂度为 O ( n log 2 n ) O\left(n \log_{2}{n} \right) O(nlog2n),在细节部分的算法时间复杂度仍可继续探讨;由于斐波拉契堆和二项队列在提取和插入最小值时候时间复杂度为 O ( 1 ) O(1) O(1),在构建优先队列时候能够优化算法,但是对于大多数数据集其性能差异并不显著。
反证法:
~~~~~~~~ 假设: ∃ x 0 ∈ S \exists x_{0}\in S ∃x0∈S 从 T T T 构成的 S S S 的编码,不是一个前缀码。
∵ T ~~~~~~~~\because T ∵T 是一棵二叉树,不存在环,
∴ ~~~~~~~~\therefore ∴ 从 T T T 中查找到 x 0 x_{0} x0 的路径必然是某个 S S S 中的符号往下查找的必经之路,
∵ S ~~~~~~~~\because S ∵S 中的符号都为 T T T 的叶子结点,任意一个符号被找到后往下不再有路可寻,
∴ ~~~~~~~~\therefore ∴ 与所设命题相互矛盾,原命题成立!
反证法:
~~~~~~~~ 假设:最优前缀码的二叉树 T T T 中存在一个结点 u u u 只有一个孩子结点 v v v。
~~~~~~~~ 当结点 u u u 为树根结点时,将 u u u 结点的位置与以结点 v 为根的子树位置交换,则结点 u u u 以 结点 v v v 为根的子树的平均编码位数会变小;
~~~~~~~~ 当结点 u u u 不为树根结点时,假设其双亲结点为 w w w ,则将 u u u 结点的位置与以结点 v v v 为根的子树位置交换,使得结点 v 作为结点 w w w 的孩子结点,同样使得结点 u u u 以 结点 v v v 为根的子树的平均编码位数会变小;
~~~~~~~~ 因此可知与所设命题相互矛盾,原命题成立!
反证法:
~~~~~~~~ 假设:最优前缀码的二叉树 T T T 中结点 u u u 和结点 v v v 对应的频率为 f y < f x f_{y}< f_{x} fy<fx。
~~~~~~~~ 当交换在结点 u u u 和结点 v v v的标记得到的编码后, f y f_{y} fy 的乘数由 d e p t h ( u ) depth\left(u\right) depth(u) 变为 d e p t h ( v ) depth\left(v\right) depth(v), f z f_{z} fz 的乘数由 d e p t h ( v ) depth\left(v\right) depth(v) 变为 d e p t h ( u ) depth\left(u\right) depth(u);
~~~~~~~~ 可得到每个符号平均位数的表达式变化为:
A B L ( T ∗ ) = ∑ x ∈ S f x d e p t h ( x ) + f y d e p t h ( v ) − f y d e p t h ( u ) + f z d e p t h ( u ) − f z d e p t h ( v ) = ∑ x ∈ S f x d e p t h ( x ) + [ d e p t h ( v ) − d e p t h ( u ) ] ( f y − f z ) \begin{align*} ABL\left( T^{*} \right)&=\sum_{x\in S}^{}f_{x}depth\left(x\right)+f_{y}depth\left(v\right)-f_{y}depth\left(u\right)+f_{z}depth\left(u\right)-f_{z}depth\left(v\right)\\ &=\sum_{x\in S}^{}f_{x}depth\left(x\right)+\left[depth\left(v\right)-depth\left(u\right)\right]\left(f_{y}-f_{z}\right) \end{align*} ABL(T∗)=x∈S∑fxdepth(x)+fydepth(v)−fydepth(u)+fzdepth(u)−fzdepth(v)=x∈S∑fxdepth(x)+[depth(v)−depth(u)](fy−fz)
∵ f y − f z < 0 ~~~~~~~~\because f_{y}-f_{z}<0 ∵fy−fz<0,交换后使得平均编码长度比原来的最优前缀码二叉树 T T T 更小了,
∴ ~~~~~~~~\therefore ∴与所设命题相互矛盾,原命题成立!
反证法:
~~~~~~~~ 设:最优前缀码的二叉树 T ∗ T^{*} T∗ 有最大深度的一片树叶 v v v,树叶 v v v 的双亲为 u u u,而 u u u 有另外一个孩子 w w w 是 v v v 的兄弟结点。
~~~~~~~~ 假设: w w w 不是 T ∗ T^{*} T∗ 的一片树叶。
~~~~~~~~ 就会使得 w w w 的子树中将会存在某片树叶 w ′ w^{'} w′,
~~~~~~~~ 则 w ′ w^{'} w′ 的深度将会大于最大深度树叶结点 v v v,
~~~~~~~~ 因此可知与所设命题相互矛盾,原命题成立!
证明:
~~~~~~~~ 设:最优前缀码的二叉树 T ∗ T^{*} T∗ 有最大深度的一片树叶 v v v,树叶 v v v 的双亲为 u u u,而 u u u 有另外一个孩子 w w w 是 v v v 的兄弟结点。
~~~~~~~~ 由定理 4.30 4.30 4.30 可知 v v v 和 w w w 是最大深度的兄弟树叶,然后通过一层一层的标记得到 T ∗ T^{*} T∗;
~~~~~~~~ 由定理 4.29 4.29 4.29 可知最后将到达包含 v v v 与 w w w 的层,在这一层上的树叶将会是最低频率的字母;
~~~~~~~~ 由于已经证明了在这层内部将这些字母分配给树叶顺序是互不相关的,
~~~~~~~~ 因此存在最优标记, v v v 与 w w w 得到全部字母中的两个最低频率的字母。
~~~~~~~~ 因此,原命题成立!
证明:
~~~~~~~~ 设: y ∗ y^{*} y∗ 和 z ∗ z^{*} z∗ 是 S S S 中频率最小的两个字母,合并它们后产生一个新的结点,其频率为 f ω = f y ∗ + f z ∗ f_{\omega}=f_{y^{*}}+f_{z^{*}} fω=fy∗+fz∗ 作为它们的双亲结点,并加入到 S S S,使得变为 S ′ S^{'} S′, T T T 变为 T ′ T^{'} T′; A B L ( T ) ABL\left(T\right) ABL(T) 为编码 S S S 中的字母使用的平均位数, A B L ( T ′ ) ABL\left(T^{'}\right) ABL(T′) 为编码 S ′ S^{'} S′ 中的字母使用的平均位数; y ∗ y^{*} y∗ 与 z ∗ z^{*} z∗ 在 T T T 中的深度每个都比 ω \omega ω 在 T ′ T^{'} T′ 中的深度大 1 1 1,则有:
A B L ( T ) = ∑ x ∈ S f x ⋅ d e p t h T ( x ) = f y ∗ ⋅ d e p t h T ( y ∗ ) + f z ∗ ⋅ d e p t h T ( z ∗ ) + ∑ x ≠ y ∗ , z ∗ f x ⋅ d e p t h T ( x ) = ( f y ∗ + f z ∗ ) ⋅ ( 1 + d e p t h T ′ ( ω ) ) + ∑ x ≠ y ∗ , z ∗ f x ⋅ d e p t h T ′ ( x ) = f ω ⋅ ( 1 + d e p t h T ′ ( ω ) ) + ∑ x ≠ y ∗ , z ∗ f x ⋅ d e p t h T ′ ( x ) = f ω + f ω ⋅ d e p t h T ′ ( ω ) + ∑ x ≠ y ∗ , z ∗ f x ⋅ d e p t h T ′ ( x ) = f ω + ∑ x ∈ S ′ f x ⋅ d e p t h T ′ ( x ) = f ω + A B L ( T ′ ) \begin{align*} ABL\left( T \right)&=\sum_{x\in S}^{}f_{x}\cdot depth_{T}\left(x\right)\\ &=f_{y^{*}}\cdot depth_{T}\left(y^{*}\right)+f_{z^{*}}\cdot depth_{T}\left(z^{*}\right)+\sum_{x\ne y^{*},z^{*}}^{}f_{x}\cdot depth_{T}\left(x\right)\\ &=\left(f_{y^{*}}+f_{z^{*}}\right)\cdot \left(1+depth_{T^{'}}\left(\omega \right)\right)+\sum_{x\ne y^{*},z^{*}}^{}f_{x}\cdot depth_{T^{'}}\left(x\right)\\ &=f_{\omega}\cdot \left(1+depth_{T^{'}}\left(\omega \right)\right)+\sum_{x\ne y^{*},z^{*}}^{}f_{x}\cdot depth_{T^{'}}\left(x\right)\\ &=f_{\omega}+ f_{\omega}\cdot depth_{T^{'}}\left(\omega \right)+\sum_{x\ne y^{*},z^{*}}^{}f_{x}\cdot depth_{T^{'}}\left(x\right)\\ &=f_{\omega}+ \sum_{x\in S^{'}}^{}f_{x}\cdot depth_{T^{'}}\left(x\right)\\ &=f_{\omega}+ABL\left( T^{'} \right) \end{align*} ABL(T)=x∈S∑fx⋅depthT(x)=fy∗⋅depthT(y∗)+fz∗⋅depthT(z∗)+x=y∗,z∗∑fx⋅depthT(x)=(fy∗+fz∗)⋅(1+depthT′(ω))+x=y∗,z∗∑fx⋅depthT′(x)=fω⋅(1+depthT′(ω))+x=y∗,z∗∑fx⋅depthT′(x)=fω+fω⋅depthT′(ω)+x=y∗,z∗∑fx⋅depthT′(x)=fω+x∈S′∑fx⋅depthT′(x)=fω+ABL(T′)
~~~~~~~~ 因此,原命题成立!
反证法:
~~~~~~~~ 假设:通过贪心算法产生的树 T T T 不是最优的,存在一颗被标记的二叉树 Z Z Z 使得 A B L ( Z ) < A B L ( Z ) ABL\left(Z\right)
~~~~~~~~ 由命题 4.31 4.31 4.31 可知这颗树 Z Z Z 存在且 y ∗ y^{*} y∗ 与 z ∗ z^{*} z∗ 的树叶是兄弟;
~~~~~~~~ 与命题 4.30 4.30 4.30 类似,通过从 Z Z Z 中 删除标记为 y ∗ y^{*} y∗ 与 z ∗ z^{*} z∗ 的树叶,并把它们的双亲结点标记为 ω \omega ω 得到一颗树 Z ′ Z^{'} Z′,他对于 S ′ S^{'} S′ 定义了一个前缀码;在 ω \omega ω 下面加上标记为 y ∗ y^{*} y∗ 与 z ∗ z^{*} z∗ 的树叶使得 Z ′ Z^{'} Z′ 变成 Z Z Z,这与 T ′ T^{'} T′ 变成 T T T 也是雷同;
~~~~~~~~ 由命题 4.32 4.32 4.32 可知: A B L ( Z ′ ) = A B L ( Z ) − f ω ABL\left( Z^{'} \right)=ABL\left( Z \right)-f_{\omega} ABL(Z′)=ABL(Z)−fω, A B L ( T ′ ) = A B L ( T ) − f ω ABL\left( T^{'} \right)=ABL\left( T \right)-f_{\omega} ABL(T′)=ABL(T)−fω;
∵ ~~~~~~~~\because ∵ A B L ( Z ) < A B L ( T ) ABL\left(Z\right)
\par ∴ ~~~~~~~~\therefore ∴ A B L ( Z ′ ) < A B L ( T ′ ) ABL\left(Z^{'}\right)
~~~~~~~~ 由于上面说明了任意 T ′ T^{'} T′ 都与 Z ′ Z^{'} Z′ 一样,通过相同方法一层层的保持着作为 S ′ S^{'} S′ 前缀码的树 T ′ T^{'} T′ 的最优性,必定不存在有另外一种作为 S ′ S^{'} S′ 前缀码的树 Z ′ Z^{'} Z′ 编码平均位长比 T ′ T^{'} T′ 还要小;
~~~~~~~~ 因此可知与所设命题相互矛盾,原命题成立!
#include
#include
#include
using namespace std;
#define OK 1 //Algorithm implementation success
#define ERROR 0 //Algorithm implementation failure
#define OVERFLOW 2 //overflow
#define Status int //Program state
typedef struct
{
char symb=' '; //Symbol of node
int weight=0; //The weight of the node
int parent,lchild,rchild; //The parent of the node, the left child and the right child
} HTNode,*HuffmanTree; //Dynamically allocated arrays store Huffman trees
typedef char** HuffmanCode; //Dynamically allocated arrays store Huffman encoding tables
//Select two nodes whose parent domain is 0 and whose weight is the smallest, and return the serial numbers s1 and s2 in HT
void Select(HuffmanTree &HT, int n, int &s1, int &s2)
{
//Find the first node whose parent domain is 0 and has the lowest weight
int min;
for (int i = 1; i <= n; i++) //The first parent field of 0 is found, and the subscript is temporarily stored to min
{
if (HT[i].parent == 0)
{
min = i;
break;
}
}
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0)
{
if (HT[i].weight < HT[min].weight)
{
min = i;
}
}
}
s1 = min;
//Find the second node with the lowest weight and parent domain 0
for (int i = 1; i <= n; i++) //The first parent field of 0 is found, and the subscript is temporarily stored to min
{
if (HT[i].parent == 0 && i != s1)
{
min = i;
break;
}
}
for (int i = 1; i <= n; i++)
{
if (HT[i].parent == 0 && i != s1)
{
if (HT[i].weight < HT[min].weight)
{
min = i;
}
}
}
s2 = min;
}
Status CreateHuffmanTree(HuffmanTree &HT,int n)
{
//Build the Huffman tree HT
if(n<=1) return OK;
int m=2*n-1; //The number of nodes used by Huffman tree
HT=new HTNode[m+1]; //Create space
if(HT==NULL) return OVERFLOW; //Space allocation failure
for(int i=1; i<=m; ++i) //Initialization tree
{
HT[i].parent=0;
HT[i].lchild=0;
HT[i].rchild=0;
}
cout<<"Please enter"<>HT[i].symb>>HT[i].weight; //Input data
}
// for(int i=1;i<=n;++i) cout<>n;
if(CreateHuffmanTree(HT,n)==1) //Build the Huffman tree
cout<<"Huffman Tree built successfully!"<
输入: A 5 B 20 C 25 D 10 E 40
输出:
HT表(Huffman树):
结点下标 | 符号 | 权值/频率 | 双亲 | 左孩子 | 右孩子 |
---|---|---|---|---|---|
1 | A | 5 | 6 | 0 | 0 |
2 | B | 20 | 7 | 0 | 0 |
3 | C | 25 | 8 | 0 | 0 |
4 | D | 10 | 6 | 0 | 0 |
5 | E | 40 | 9 | 0 | 0 |
6 | 15 | 7 | 1 | 4 | |
7 | 35 | 8 | 6 | 2 | |
8 | 60 | 9 | 3 | 7 | |
9 | 100 | 0 | 5 | 8 |
HC表(Huffman编码表):
结点下标 | 符号 | Huffman编码 |
---|---|---|
1 | A | 1100 |
2 | B | 111 |
3 | C | 10 |
4 | D | 1101 |
5 | E | 0 |
----以上为个人思考与见解,有误请指点,有想法也可联系交流!
~~~~~~~~~~~~~~ 谢谢观看!