赫夫曼树及其应用

赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用。

 

最优二叉树(Huffman树)

1 基本概念

① 结点路径:从树中一个结点到另一个结点的之间的分支构成这两个结点之间的路径。

② 路径长度:结点路径上的分支数目称为路径长度。

③ 树的路径长度:从树根到每一个结点的路径长度之和。

以下图为例:

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

A到F :结点路径 AEF ;

路径长度(即边的数目) 2 ;

树的路径长度:3*1+5*2+2*3=19;

 

④ 结点的带权路径长度:从该结点的到树的根结点之间的路径长度与结点的权(值)的乘积。

权(值):各种开销、代价、频度等的抽象称呼。

⑤ 树的带权路径长度:树中所有叶子结点的带权路径长度之和,记做:

WPL=w1*l1+w2*l2+⋯+wn*ln=∑wi*li (i=1,2,⋯,n)

其中:n为叶子结点的个数;wi为第i个结点的权值; li为第i个结点的路径长度。

⑥ Huffman树:具有n个叶子结点(每个结点的权值为wi) 的二叉树不止一棵,但在所有的这些二叉树中,必定存在一棵WPL值最小的树,称这棵树为Huffman树(或称最优树) 。

 

在许多判定问题时,利用Huffman树可以得到最佳判断算法。

下图是权值分别为2、3、6、7,具有4个叶子结点的二叉树,它们的带权路径长度分别为:

(a) WPL=2*2+3*2+6*2+7*2=36 ;

(b) WPL=2*1+3*2+6*3+7*3=47 ;

(c) WPL=7*1+6*2+2*3+3*3=34 。

其中(c)的 WPL值最小,可以证明是Huffman树。

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

 

 

2  Huffman树的构造

① 根据n个权值{w1, w2, ⋯,wn},构造成n棵二叉树的集合F={T1, T2, ⋯,Tn},其中每棵二叉树只有一个权值为wi的根结点,没有左、右子树;

②  在F中选取两棵根结点权值最小的树作为左、右子树构造一棵新的二叉树,且新的二叉树根结点权值为其左、右子树根结点的权值之和;

③  在F中删除这两棵树,同时将新得到的树加入F中;

④ 重复②、③,直到F只含一棵树为止。

构造Huffman树时,为了规范,规定F={T1,T2, ⋯,Tn}中权值小的二叉树作为新构造的二叉树的左子树,权值大的二叉树作为新构造的二叉树的右子树;在取值相等时,深度小的二叉树作为新构造的二叉树的左子树,深度大的二叉树作为新构造的二叉树的右子树。  

 

下图是权值集合W={8, 3, 4, 6, 5, 5}构造Huffman树的过程。所构造的Huffman树的WPL是: WPL=6*2+3*3+4*3+8*2+5*3+5*3 =79

javascript实现数据结构: 树和二叉树的应用--最优二叉树(赫夫曼树),回溯法与树的遍历--求集合幂集及八皇后问题

 

 

赫夫曼编码及其算法

1  Huffman编码

在电报收发等数据通讯中,常需要将传送的文字转换成由二进制字符0、1组成的字符串来传输。为了使收发的速度提高,就要求电文编码要尽可能地短。此外,要设计长短不等的编码,还必须保证任意字符的编码都不是另一个字符编码的前缀,这种编码称为前缀编码。

Huffman树可以用来构造编码长度不等且译码不产生二义性的编码。

设电文中的字符集C={c1,c2, ⋯,ci, ⋯,cn},各个字符出现的次数或频度集W={w1,w2, ⋯,wi, ⋯,wn}。

 

Huffman编码方法

以字符集C作为叶子结点,次数或频度集W作为结点的权值来构造 Huffman树。规定Huffman树中左分支代表“0”,右分支代表“1” 。

从根结点到每个叶子结点所经历的路径分支上的“0”或“1”所组成的字符串,为该结点所对应的编码,称之为Huffman编码。

由于每个字符都是叶子结点,不可能出现在根结点到其它字符结点的路径上,所以一个字符的Huffman编码不可能是另一个字符的Huffman编码的前缀。

若字符集C={a, b, c, d, e, f}所对应的权值集合为W={8, 3, 4, 6, 5, 5},如上图所示,则字符a,b, c,d, e,f所对应的Huffman编码分别是:10,010,011,00 ,110,111。

 

2  Huffman编码算法实现

(1)  数据结构设计

Huffman树中没有度为1的结点棵有n个叶子结点的Huffman树共有2n-1个结点,则可存储在大小为2n-1的一维数组中。

原因:

◆ 求编码需从叶子结点出发走一条从叶子到根的路径;

◆ 译码需从根结点出发走一条到叶子结点的路径。

1 // 赫夫曼树和赫夫曼编码的存储结构
2 function HuffmanNode(weight, parent, leftChild, rightChild) {
3     this.weight = weight || 0;
4     this.parent = parent || 0;
5     this.leftChild = leftChild || 0;
6     this.rightChild = rightChild || 0;
7 }

Weight:权值域;

Parent:双亲结点下标

leftChild, rightChild:分别标识左、右子树的下标

 

(2) Huffman树的生成

 1 // 创建一棵叶子结点数为n的Huffman树
 2 function buildHuffmanTree(weights, n) {
 3     n = n || weights.length;
 4     var m = 2 * n - 1;
 5     var huffmanTree = [];
 6 
 7     // 初始化
 8     for (var i = 0; i < n; i++)
 9         huffmanTree[i] = new HuffmanNode(weights[i], 0, 0, 0);
10     for (; i < m; i++)
11         huffmanTree[i] = new HuffmanNode(0, 0, 0, 0);
12 
13     for (i = n; i < m; i++) {
14         // 在HT[1..i-1]选择parent为0且weight最小的两个结点,返回其序号为[s1, s2]
15         var ret = select(huffmanTree, i);
16         var s1 = ret[0];
17         var s2 = ret[1];
18         huffmanTree[s1].parent = i;
19         huffmanTree[s2].parent = i;
20         huffmanTree[i].leftChild = s1;
21         huffmanTree[i].rightChild = s2;
22         huffmanTree[i].weight = huffmanTree[s1].weight + huffmanTree[s2].weight;
23     }
24 
25     return huffmanTree;
26 }
27 
28 function select(huffmanTree, len) {
29     var ret = [];
30     for (var i = 0; i < len; i++) {
31         var node = huffmanTree[i];
32         if (node.parent !== 0) continue;
33 
34         if (ret.length < 2) {
35             ret.push(i);
36         } else {
37             var index = huffmanTree[ret[0]].weight > huffmanTree[ret[1]].weight
38                 ? 0 : 1;
39 
40             if (node.weight < huffmanTree[ret[index]].weight)
41                 ret[index] = i;
42         }
43     }
44 
45     if (ret[0] > ret[1]) {
46         var temp = ret[0];
47         ret[0] = ret[1];
48         ret[1] = temp;
49     }
50 
51     return ret;
52 }

 

说明:生成Huffman树后,树的根结点的下标是2n-1 ,即m-1 。

 

(3) Huffman编码算法

根据出现频度(权值)Weight,对叶子结点的Huffman编码有两种方式:

① 从叶子结点到根逆向处理,求得每个叶子结点对应字符的Huffman编码。

② 从根结点开始遍历整棵二叉树,求得每个叶子结点对应字符的Huffman编码。

 

由Huffman树的生成知,n个叶子结点的树共有2n-1个结点,叶子结点存储在数组HT中的下标值为1∽n。

①  编码是叶子结点的编码,只需对数组HT[1…n]的n个权值进行编码;

②  每个字符的编码不同,但编码的最大长度是n。

 

算法实现:

 1 function huffManCoding(weights) {
 2     var n = weights.length;
 3     if (n < 1) return;
 4 
 5     var huffmanTree = buildHuffmanTree(weights, n);
 6 
 7     // 从叶子到根逆向求每个字符的赫夫曼编码
 8     var hc = calcHuffmanCode(huffmanTree, n);
 9 
10     return [huffmanTree, hc];
11 }
12 
13 function calcHuffmanCode(huffmanTree, n) {
14     // 从叶子到根逆向求每个字符的赫夫曼编码
15     var hc = [];
16     var cd = [];
17     for (var i = 0; i < n; i++) {
18         var start = n - 1;
19         for (var c = i, f = huffmanTree[i].parent; f != 0; c = f, f = huffmanTree[f].parent) {
20             if (huffmanTree[f].leftChild == c) cd[--start] = '0';
21             else cd[--start] = '1';
22         }
23 
24         hc[i] = strCopy(cd, start);
25     }
26 
27     return hc;
28 }
29 
30 function strCopy(str, start) {
31     var s = '';
32     for (; str[start]; start++) {
33         s += str[start];
34     }
35     return s;
36 }

 

 

全部代码实现:

 1 // 赫夫曼树和赫夫曼编码的存储结构
 2 function HuffmanNode(weight, parent, leftChild, rightChild) {
 3     this.weight = weight || 0;
 4     this.parent = parent || 0;
 5     this.leftChild = leftChild || 0;
 6     this.rightChild = rightChild || 0;
 7 }
 8 function huffManCoding(weights) {
 9     var n = weights.length;
10     if (n < 1) return;
11 
12     var huffmanTree = buildHuffmanTree(weights, n);
13 
14     // 从叶子到根逆向求每个字符的赫夫曼编码
15     var hc = calcHuffmanCode(huffmanTree, n);
16 
17     return [huffmanTree, hc];
18 }
19 
20 function calcHuffmanCode(huffmanTree, n) {
21     // 从叶子到根逆向求每个字符的赫夫曼编码
22     var hc = [];
23     var cd = [];
24     for (var i = 0; i < n; i++) {
25         var start = n - 1;
26         for (var c = i, f = huffmanTree[i].parent; f != 0; c = f, f = huffmanTree[f].parent) {
27             if (huffmanTree[f].leftChild == c) cd[--start] = '0';
28             else cd[--start] = '1';
29         }
30 
31         hc[i] = strCopy(cd, start);
32     }
33 
34     return hc;
35 }
36 
37 // 创建一棵叶子结点数为n的Huffman树
38 function buildHuffmanTree(weights, n) {
39     n = n || weights.length;
40     var m = 2 * n - 1;
41     var huffmanTree = [];
42 
43     // 初始化
44     for (var i = 0; i < n; i++)
45         huffmanTree[i] = new HuffmanNode(weights[i], 0, 0, 0);
46     for (; i < m; i++)
47         huffmanTree[i] = new HuffmanNode(0, 0, 0, 0);
48 
49     for (i = n; i < m; i++) {
50         // 在HT[1..i-1]选择parent为0且weight最小的两个结点,返回其序号为[s1, s2]
51         var ret = select(huffmanTree, i);
52         var s1 = ret[0];
53         var s2 = ret[1];
54         huffmanTree[s1].parent = i;
55         huffmanTree[s2].parent = i;
56         huffmanTree[i].leftChild = s1;
57         huffmanTree[i].rightChild = s2;
58         huffmanTree[i].weight = huffmanTree[s1].weight + huffmanTree[s2].weight;
59     }
60 
61     return huffmanTree;
62 }
63 
64 function strCopy(str, start) {
65     var s = '';
66     for (; str[start]; start++) {
67         s += str[start];
68     }
69     return s;
70 }
71 
72 function select(huffmanTree, len) {
73     var ret = [];
74     for (var i = 0; i < len; i++) {
75         var node = huffmanTree[i];
76         if (node.parent !== 0) continue;
77 
78         if (ret.length < 2) {
79             ret.push(i);
80         } else {
81             var index = huffmanTree[ret[0]].weight > huffmanTree[ret[1]].weight
82                 ? 0 : 1;
83 
84             if (node.weight < huffmanTree[ret[index]].weight)
85                 ret[index] = i;
86         }
87     }
88 
89     if (ret[0] > ret[1]) {
90         var temp = ret[0];
91         ret[0] = ret[1];
92         ret[1] = temp;
93     }
94 
95     return ret;
96 }
97 
98 console.log('-------huffman coding :------');
99 console.log(huffManCoding([5, 29, 7, 8, 14, 23, 3, 11]));
View Code

相关文章: