P50 - 霍夫曼编码

2026-09-07 00:00    #Haskell   #99题   #逻辑  

P50 - 霍夫曼编码

Huffman codes

官方模块:Problems.P50 核心函数:huffman


← P49 格雷码 | P51 纠错码 →


题目描述

给定字符及其频率,生成最优前缀码(霍夫曼编码)。频率越高的字符编码越短。

函数签名

1huffman :: [(Char, Int)] -> [(Char, String)]

实现

方法一:列表排序构建树

 1import Data.List (sortOn)
 2
 3data HuffmanTree = Leaf Char | Branch HuffmanTree HuffmanTree
 4
 5huffman :: [(Char, Int)] -> [(Char, String)]
 6huffman [] = []
 7huffman [(c,_)] = [(c,"0")]
 8huffman frequencies = codes (snd (head (build initial)))
 9  where
10    initial = sortOn fst [(w, Leaf c) | (c,w) <- frequencies]
11
12    build [tree] = [tree]
13    build ((w,a):(v,b):rest) =
14      build (sortOn fst ((w + v, Branch a b) : rest))
15    build [] = []
16
17codes :: HuffmanTree -> [(Char, String)]
18codes = walk ""
19  where
20    walk prefix (Leaf c) = [(c, prefix)]
21    walk prefix (Branch left right) =
22      walk (prefix ++ "0") left ++ walk (prefix ++ "1") right

反复取出权重最小的两棵树合并,用 sortOn 维护有序列表。

方法二:使用优先队列

 1import Data.List (insertBy, sortOn)
 2import Data.Ord (comparing)
 3
 4huffman :: [(Char, Int)] -> [(Char, String)]
 5huffman [] = []
 6huffman [(c,_)] = [(c,"0")]
 7huffman frequencies = codes (buildQueue initial)
 8  where
 9    initial = sortOn fst [(w, Leaf c) | (c,w) <- frequencies]
10
11    buildQueue [tree] = snd tree
12    buildQueue ((w1,a):(w2,b):rest) =
13      buildQueue (insertBy (comparing fst) (w1 + w2, Branch a b) rest)

insertBy (comparing fst) 只比较权重,不要求 HuffmanTree 具有 Ord 实例;每次合并后把新树插回正确位置。

方法三:codes 用差分字符串优化

1codesShowS :: HuffmanTree -> [(Char, String)]
2codesShowS tree = [(c, build "") | (c, build) <- walk id tree]
3  where
4    walk prefix (Leaf c) = [(c, prefix)]
5    walk prefix (Branch left right) =
6      walk (prefix . ('0' :)) left
7      ++ walk (prefix . ('1' :)) right

ShowS 就是 String -> String。追加一位编码变成函数组合,只有到叶子时才把构造器应用到空串,避免在每层复制前缀。

测试

1>>> let table = huffman [('a',45),('b',13),('c',12),('d',16),('e',9),('f',5)]
2>>> all (\(_,code) -> not (null code)) table
3True
4>>> huffman [('a',2),('b',1)]
5[('b',"0"),('a',"1")]
6>>> codesShowS (Branch (Leaf 'a') (Leaf 'b'))
7[('a',"0"),('b',"1")]

参考