Huffman编码树
ASCII码与莫尔斯电报码
在计算机中ASCII标准编码将每个字符表示为一个包含七个二进制位的序列,采用七个二进为能够区分27=128种情况,即128个可能不同的字符。一般而言,如果我们要区分n个字符,那么我们就要为每个字符使用log2n个二进制位。这种是定长编码,每一个字符都是用相同个数的二进制位表示的。然而莫尔斯电报码并不是这么做得,莫尔斯电报码对于出现很频繁的字符采用较短的编码,如字母E只用一个点来表示。使用变长的编码能够节省很多得存储空间。
变长编码需要解决的一个问题
虽然变长编码能够节省空间,但是有这样一个问题。在定长的编码中,比如编码长度是3,那么读三个01序列就可以去解码了。但是在变长编码中如何确定一个字符结束了呢?看看莫尔斯电报码怎么做得,莫尔斯电报码是在每个字符结束后用一个特殊的分隔符(间歇)。这对于计算机编码来讲显然不是一个好的方法。计算机编码采用这样的一种方式:设计一种编码的方式,使得每个字符的完整编码都不是另一个字符编码开始的一段,这样的编码叫做前缀码。
Huffman编码树
一个Huffman编码可以表示为一棵二叉树,其中树叶是被编码的符号。树上每个非叶节点代表一个集合,其中包含了这一个结点之下的所有树叶上得符号。因为只有叶节点表示了一个字符,所以不会出现一个字符的编码是另一个字符前缀的情况。除此之外每个字符被赋予了一个权重,权重值越大表示出现的频率越高非叶节点的权重是这个节点之下所有树叶的权重和。
编码过程
给定一个Huffman树,怎么编码呢?首先从树根开始走。看要编码的字符在左子树还是右子树,就是看要编码的字符在左子结点的符号集合中还是右子结点的。在哪边就往哪边走,每次往左走就编个0,往右走就编个1,这样一直遵循这个规律走一定会走到这个字符的叶节点。产生的01序列就是这个字符的编码。
生成Huffman编码树
生成Huffman树的算法实际十分简单,其想法就是设法安排这棵树,使得那些带有最低频度的符号出现在离树根最远的地方。这一构造过程从叶节点的集合开始,这种节点中包含各个符号和它们的频度,这就是开始构造编码的初始数据。现在要找出两个具有最低权重的叶,并归并它们,产生出一个以这两个结点为左右分支的结点。新结点的权重就是那两个结点的权重之和。现在我们从原来集合里删除前面两个叶结点,并用这一新结点代替它们。继续这一过程直到只剩下一个结点。这个结点就是树根。
解码过程
解码过程就更简单了。从树根开始,0往左走,1往右走。到了叶结点,这个叶结点的符号就是解出来的字符。然后继续从根开始解码。直至整个01序列都被解码成字符。
Scheme实现Huffman树
;构建形如(leaf E 1)的叶节点
(define (make-leaf symbol weight)
(list 'leaf symbol weight))
;判断一个分支是不是叶结点,看它第一个元素是不是leaf
(define (leaf? object)
(eq? (car object) 'leaf))
;拿到一个叶结点所代表的符号
(define (symbol-leaf x) (cadr x))
;拿到一个叶节点所代表的字符
(define (weight-leaf x) (caddr x))
;生成一个编码树 args(左分支 右分支 字符集合 新的权重值)
(define (make-code-tree left right)
(list left
right
(append (symbols left) (symbols right))
(+ (weight left) (weight right))))
;拿到编码树的左分支
(define (left-branch tree)
(car tree))
;拿到编码树的右分支
(define (right-branch tree)
(cadr tree))
;如果是树叶获取符号,如果是编码树获取符号集
(define (symbols tree)
(if (leaf? tree)
(list (symbol-leaf tree))
(caddr tree)))
;如果是树叶拿到权重,如果是树那个树的权重(这个结点下面所有叶结点的权重和)
(define (weight tree)
(if (leaf? tree)
(weight-leaf tree)
(cadddr tree)))
;
;编码过程
(define (decode bits tree)
(define (decode-1 bits current-branch)
(if (null? bits)
'()
(let ((next-branch
(choose-branch (car bits) current-branch)))
(if (leaf? next-branch)
(cons (symbol-leaf next-branch)
(decode-1 (cdr bits) tree))
(decode-1 (cdr bits) next-branch)))))
(decode-1 bits tree))
;选择分支 0左 1右
(define (choose-branch bit branch)
(cond ((= bit 0) (left-branch branch))
((= bit 1) (right-branch branch))
(else (error "bad bit -- CHOOSE-BRANCH" bit))))
;将一个结点添加结点集合中,按照从小到大排列,简单的插入排序。
(define (adjoin-set x set)
(cond ((null? set) (list x))
((< (weight x) (weight (car set))) (cons x set))
(else (cons (car set)
(adjoin-set x (cdr set))))))
;把给定的字母权重序对集生成叶节点集
;即((E 1) (F 2)) -> ((leaf E 1) (leaf F 2))
(define (make-leaf-set pairs)
(if (null? pairs)
'()
(let ((pair (car pairs)))
(adjoin-set (make-leaf (car pair)
(cadr pair))
(make-leaf-set (cdr pairs))))))
;编码过程
(define (encode message tree)
(if (null? message)
'()
(append (encode-symbol (car message) tree)
(encode (cdr message) tree))))
;将单个字符编码
(define (encode-symbol symbol tree)
(define (encode-symbol-1 current-branch bits)
(if (leaf? current-branch)
bits
(let ((l-branch (left-branch current-branch))
(r-branch (right-branch current-branch)))
(cond ((contains (symbols l-branch) symbol)
(encode-symbol-1 l-branch
(append bits (list 0))))
((contains (symbols r-branch) symbol)
(encode-symbol-1 r-branch
(append bits (list 1))))
(else (error "Bad Char --ENCODE-SYMBOL" symbol))))))
(if (leaf? tree)
(if (eq? symbol (symbol-leaf tree))
(encode-symbol-1 tree (list 0))
(error "Bad Char --ENCODE-SYMBOL" symbol))
(encode-symbol-1 tree '())))
;确定字符集中是否包含指定字符
(define (contains set symbol)
(cond ((null? set) #f)
((eq? (car set) symbol) #t)
(else (contains (cdr set) symbol))))
;生成Huffman树
(define (generate-huffman-tree pairs)
(successive-merge (make-leaf-set pairs)))
;合并分支的过程
;只剩一个结点就是生成的树了,拿出来
;大于等于两个结点进行归并
(define (successive-merge branches)
(cond ((null? branches) '())
((= 1 (length branches))
(car branches))
(else (successive-merge (adjoin-set (make-code-tree (car branches)
(cadr branches))
(cddr branches))))))