数据结构 | 第5章 Huffman编码

369 阅读4分钟

5.5 Huffman编码

5.5.1 PFC编码及解码

image-20220707102238662.png

总体框架:
 0001 int main ( int argc, char* argv[] ) { //PFC编码、解码算法统一测试入口
 0002    PFCForest* forest = initForest(); //初始化PFC森林
 0003    PFCTree* tree = generateTree ( forest ); release ( forest ); //生成PFC编码树
 0004    PFCTable* table = generateTable ( tree ); //将PFC编码树转换为编码表
 0005    for ( int i = 1; i < argc; i++ ) { //对于命令行传入的每一明文串
 0006       Bitmap codeString; //二进制编码串
 0007       int n = encode ( table, codeString, argv[i] ); //将根据编码表生成(长度为n)
 0008       decode ( tree, codeString, n ); //利用编码树,对长度为n的二进制编码串解码(直接输出)
 0009    }
 0010    release ( table ); release ( tree ); return 0; //释放编码表、编码树
 0011 } //release()负责释放复杂结构,与算法无直接关系,具体实现详见代码包

5.5.2 最优编码树

image-20220707161916167.png

同一字符集的所有编码方案中,平均编码长度最小者称作最优方案;对应编码树的ald()值也达到最小,故称之为最优二叉编码树,简称最优编码树(optimal encoding tree)。

  • 双子性:首先,最优编码树必为真二叉树:内部节点左右孩子全双。

    image-20220707214805629.png

  • 层次性:最优编码树中,叶节点位置的选取有严格限制——深度之差不得超过1。

    image-20220707214831818.png

上图中:交换y与子树p,平均编码长度更短。(交换:swap)

image-20220708111150526.png

  • 最优编码树的构造

由上可知,最优编码树中的叶节点只能出现于最低两层,故这类树的一种特例就是真完全树。由此,可以直接导出如下构造最优编码树的算法:创建一棵规模为2|Σ| - 1的完全二叉树T,再将Σ中的字符任意分配给T的|Σ|个叶节点。

5.5.3 Huffman编码树

  • 字符出现概率

    以上最优编码树算法的实际应用价值并不大,原因在于各字符在文本串中出现的频率不一致。

  • 带权平均编码长度与叶节点带权平均深度

    image-20220708123642131.png

  • 完全二叉树编码 != wald()最短

    image-20220708125003310.png

  • 最优带权编码树

    一方面,最优带权编码树必须满足双子性;

    另一方面,尽管最优编码树不一定仍是完全的,却依然满足某种意义上的层次性。

  • 层次性

    字符出现概率越低,越处于底层。

5.5.4 Huffman编码算法

  • 原理与构思

    设字符x和y在Σ中出现的概率最低,考查另一字符集Σ' = (Σ \ {x, y}) ∪ {z}

    其中\表示剔除的意思,而字符z出现的概率为 p(z) = p(x) + p(y)

    操作方法:任取Σ'的一棵最优带权编码树T',于是根据层次性,只需将T'中与字符z对应的叶节点替换为内

    部节点,并在其下引入分别对应于x和y的一对叶节点,即可得到Σ的一棵最优带权编码树。

    image-20220708152105636.png

  • 策略与算法

    已知:字符集Σ,其中所有字符概率已知

    求:最优带权编码树

    构造方法:

    1. 对Σ中每一字符分别建立一颗单个节点的树,其权重取作该字符的频率,这|Σ|棵树构成一个森林F
    2. F中选出权重最小的两棵树,创建一个新的节点,并分别以这两颗树作为其左、右子树,合并为一颗更高的树,其权重为二者之和。(合并的新树可以等效视作一个字符,称作超字符)
    3. 反复进行选取、合并的过程,每经过一轮迭代,F中的树就减少一颗。当最终F仅包含一棵树时,它就是一颗最优带权编码树

    以上构造过程称作Huffman编码算法,由其生成的编码树称作Huffman编码树(Huffman encoding tree)。

    需要强调的是,Huffman编码树只是最优带权编码树中的一棵。

    image-20220708153605837.png

  • 总体框架

     0001 /******************************************************************************************
     0002  * 无论编码森林由列表、完全堆还是左式堆实现,本测试过程都可适用
     0003  * 编码森林的实现方式采用优先级队列时,编译前对应的工程只需设置相应标志:
     0004  *    DSA_PQ_List、DSA_PQ_ComplHeap或DSA_PQ_LeftHeap
     0005  ******************************************************************************************/
     0006 int main ( int argc, char* argv[] ) { //Huffman编码算法统一测试
     0007    int* freq = statistics ( argv[1] ); //根据样本文件,统计各字符的出现频率
     0008    HuffForest* forest = initForest ( freq ); release ( freq ); //创建Huffman森林
     0009    HuffTree* tree = generateTree ( forest ); release ( forest ); //生成Huffman编码树
     0010    HuffTable* table = generateTable ( tree ); //将Huffman编码树转换为编码表
     0011    for ( int i = 2; i < argc; i++ ) { //对于命令行传入的每一明文串
     0012       Bitmap* codeString = new Bitmap; //二进制编码串
     0013       int n = encode ( table, codeString, argv[i] ); //将根据编码表生成(长度为n)
     0014       decode ( tree, codeString, n ); //利用Huffman编码树,对长度为n的二进制编码串解码
     0015       release ( codeString );
     0016    }
     0017    release ( table ); release ( tree ); return 0; //释放编码表、编码树
     0018 } //release()负责释放复杂结构,与算法无直接关系,具体实现详见代码包
    
  • 总体运行时间:每迭代一次,森林的规模减一,故共需迭代n - 1次,直到只剩最后一棵树。 O(n)+O(n1)+...+O(2)=O(n2)O(n) + O(n - 1) + ... + O(2) = O(n^2)

“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情