29
6.8 哈 哈 哈 哈 哈 哈 哈 哈 哈 哈 哈哈哈 ( 哈哈哈哈 ) 哈哈哈 哈哈哈哈哈哈哈 哈哈哈哈 哈哈哈哈哈

第六章 树和二叉树2

Embed Size (px)

Citation preview

Page 1: 第六章 树和二叉树2

6.8 哈 夫 曼 树 与 哈 夫 曼 编 码• 最优树 ( 哈夫曼树 ) 的定义• 如何构造最优树• 前缀编码• 哈夫曼编码

Page 2: 第六章 树和二叉树2

一、最优树的定义

树的路径长度定义为: 树中每个结点的路径长度之和。

结点的路径长度定义为: 从根结点到该结点的路径上 分支的数目。

结点的带权路径长度定义为: 从根结点到该结点的路径长度与 结点上权的乘积。

Page 3: 第六章 树和二叉树2

树的带权路径长度定义为: 树中所有叶子结点的带权路径长度之和 WPL(T) = wklk ( 对所有叶子结点 ) 。

在所有含 n 个叶子结点、并带相同权值的 m 叉树中,必存在一棵其带权路径长度取最小值的树,称为“最优树”。

例如:

Page 4: 第六章 树和二叉树2

WPL(T)=

72+52

+22+42 =36

WPL(T)=

71+52

+23+43

=35

WPL(T)=

73+53

+42+21 =46

Page 5: 第六章 树和二叉树2

根据给定的 n 个权值 {w1, w2,

…, wn} ,构造 n 棵二叉树的集合 F = {T1, T2, … , Tn} ,其中每棵二叉树 Ti 中均只含一个带权值 为 wi 的根结点,其左、右子树为空树;

二、如何构造最优树

(1)( 哈夫曼算法 ) 以二叉树为例:

Page 6: 第六章 树和二叉树2

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

(2)

Page 7: 第六章 树和二叉树2

从 F 中删去这两棵树,同时加入 刚生成的新树;

重复 (2) 和 (3) 两步,直至 F 中只含一棵树为止。这棵树便是哈夫曼树

(3)

(4)

Page 8: 第六章 树和二叉树2

9

例如 : 已知权值 W={ 5, 6, 2, 9, 7 }

5 6 2 7

5 2

76 9 7

6 7

139

5 2

7

Page 9: 第六章 树和二叉树2

6 7

139

5 2

7

9

5 2

7

16

6 7

13

29

Page 10: 第六章 树和二叉树2

注意:•    ① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子 ② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。 ③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。

Page 11: 第六章 树和二叉树2

前缀编码        在电文传输中,需要将电文中出现的每个字符进行二进制编码。在设计编码时需要遵守两个原则:

( 1)发送方传输的二进制编码,到接收方解码后必须具有唯一性,即解码结果与发送方发送的电文完全一样;

( 2)发送的二进制编码尽可能地短。下面我们介绍两种编码的方式。

Page 12: 第六章 树和二叉树2

1. 等长编码       这种编码方式的特点 : 每个字符的编码长度相同。 设字符集只含有 4个字符 A, B, C, D,用两位二进制表示的编码分别为 00, 01 , 10 ,11 。若现在电文为: ABACCDA ,则应发送二进制序列: 00010010101100 ,总长度为 14 位。当接收方接收到这段电文后,将按两位一段进行译码。这种编码的特点 :译码简单且具有唯一性,但编码长度并不是最短的。

Page 13: 第六章 树和二叉树2

2. 不等长编码 在传送电文时,为了使其二进制位数尽可能地少,可以将每个字符的编码设计为不等长的,使用频度较高的字符分配一个相对比较短的编码,使用频度较低的字符分配一个比较长的编码。例如,可以为 A, B, C, D四个字符分别分配0, 00 , 1, 01 ,并可将上述电文用二进制序列:000011010 发送,其长度只有 9个二进制位,但随之带来了一个问题,接收方接到这段电文后无法进行译码,因为无法断定前面 4个 0是 4个 A, 1个 B、2个 A,还是 2个 B,即译码不唯一,因此这种编码方法不可使用。

Page 14: 第六章 树和二叉树2

利用哈夫曼树可以构造一种不等长的二进制编码,并且构造所得的哈夫曼编码是一种最优前缀编码,即使所传电文的总长度最短。称为哈夫曼编码

哈夫曼编码

Page 15: 第六章 树和二叉树2

( 1)利用字符集中每个字符的使用频率作为权值构造一个哈夫曼树;( 2)从根结点开始,为到每个叶子结点路径上的左分支赋予 0,右分支赋予 1,并从根到叶子方向形成该叶子

结点的编码。

哈夫曼编码的构造方法 :

Page 16: 第六章 树和二叉树2

例如:

假设有一个电文字符集中有 8个字符,每个字符

的使用频率分别为 {0.05,0.29,0.07,0.08,0.14,0.23,

0.03,0.11},现以此为例设计哈夫曼编码。 为方便计算,将所有字符的频度乘以 100 ,使

其转换成整型数值集合,得到 {5,29,7,8,14,23,3,11} ; 哈夫曼编码设计如下图:

Page 17: 第六章 树和二叉树2

115 29 7 8 14 23 3

1000 1

15

58

29

0

0

0

1

1

18

42

19

0

0

0

1

1

1

00

010

0110 0111 1110 1111

110

10

Page 18: 第六章 树和二叉树2

哈夫曼树的存储结构 用一个大小为 2n-1 的向量来存储哈夫曼树中的结点,其存储结构为:  typedef struct { //结点类型       int weight;  //权值,不妨设权值均大于零       int lchild, rchild, parent; 

//左右孩子及双亲指针     }HTNode, *HuffmanTree;

Typedef char **Huffmancode;

哈夫曼编码的存储结构

Page 19: 第六章 树和二叉树2

Weight parent lchild rchild

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

5 29 7 8 14 23 3 11

0 1 2 3 4 5 6 7

W 529

87

0 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 00 0 0

1423311

9

9

9

1 78

1010

3 415

111112

819

14

14

1213

13

1515

5294258

1002

10116

1412

130

HC

0cd 0 1 2 3 4 5 6 7

1

2

3

4

5

6

7

8

00 11

↑start

0 01 11

1 111

11

11 1

11

10

00

000

00

1

求哈夫曼编码过程的实例 : HT数组

Page 20: 第六章 树和二叉树2

求每个字符的 Huffman 编码是从叶子到根逆向处理 , 即从叶子出发 , 沿着双亲线索 , 回溯到根节点 , 求得各个叶子节点所表示的字符的编码 .

0

HT(‘5’).parent = ‘8’

HT(‘8’).lchild = ‘5’

HT(‘8’).parent = ‘19’

HT(‘19’).rchild = ‘8’

1HT(‘19’).parent = ‘42’

HT(‘42’).rchild = ‘19’

HT(‘42’).parent = ‘100’

HT(‘100’).ichild = ‘42’

1

0

HT(‘100’).parent = 0

‘5’: 0110

Page 21: 第六章 树和二叉树2

      if(n<=1) return;      m=2*n-1;      HT=(HuffmanTree)malloc((m+1)*sizeof(HTNode));

求哈夫曼编码的算法 6.12:

for(p=HT+1,i=1;i<=n;++i,++p,++w) *p={*w,0,0,0}     for(;i<=m;++i,++p) *p={0,0,0,0} 

for(i=n+1 ; i<=m ; i++){      SelectMin(HT , i-1 , s1 , s2) ;        HT[s1].parent=i; HT[s2].parent=i ; HTIi].1child=s1 ; HT[i].rchild=s2 ; HT[i].weight=T[s1].weight+T[s2].weight ; } // end for 创建哈夫曼树

void HuffmanCoding(HuffmanTree &HT,HuffmanCode &HC, int *w,int n){

Page 22: 第六章 树和二叉树2

//从叶子到根逆向求每个字符的赫夫曼编码

   HC=(Huffmancode)malloc((n+1)*sizeof(char *));cd=(char *)malloc(n*sizeof(char *));cd[n-1]=“\0” ; //cd 临时存储每个叶子节点的哈夫曼编码

for(i=1;i<=n;i++){

... // 对每个叶子节点逆向求其 Haffman 编码

}

free(cd); }//Huffmancoding

Page 23: 第六章 树和二叉树2

//从叶子到根逆向求每个字符的赫夫曼编码 for(i=1;i<=n;i++){

start=n-1 ; for( c=i,f=HT[i].parent; f!=0; c=f,f=HT[f].parent) //i 为当前的树叶 , f 为 i 的双亲 . 每次循环沿着双亲信息逆向搜寻 , 直至根节点 if(HT[f].lchild==c cd[--start]=“0”;           else cd[--start]=“1”; HC[i]=(char * )malloc((n-start)*sizeof(char));         strcpy(Hc[i] , &cd[start]) ;}

Page 24: 第六章 树和二叉树2

求每个字符的 Huffman 编码是从叶子到根逆向处理 , 也可以从根出发 , 遍历整棵树 , 求得各个叶子节点所表示的字符的编码 .

0

0

HT(‘100’).lchild =’42’

Cd[0] = “0”

HT(‘42’).lchild =’23’

Cd[1] = “0”

HT(‘23’).lchild =0

HT(‘23’).rchild =0

‘23’: 00

Page 25: 第六章 树和二叉树2

// 无栈非递归遍历 Haffman 树 , 求 Haffman 编码...

HC=(HuffmanCode)malloc(n+1)*sizeof(char *));

p = m; cdlen = 0; // 指向根节点for (i = 1; i<=m; ++i)

HT[i].weight = 0; // 遍历时用作节点的状态标志while(p) {

... // 从根开始遍历整棵树 , 求取每个节点的编码}

Page 26: 第六章 树和二叉树2

// 无栈非递归遍历 Haffman 树 , 求 Haffman 编码 if (HT[p].weight == 0) { // 向左 HT[p].weight = 1;

if (HT[p].lchild != 0 )

{ p = HT[p].lchild; cd[cdlen++] = “0”;}

else if (HT[p].rchild ==0 ) { // 表明该节点为叶子 ,记录编码

HC[p] = (char *)malloc((cdlen+1)*sizeof(char)); cd[cdlen] = “\0”; strcpy(HC[p],cd); } //end else if

}

Page 27: 第六章 树和二叉树2

// 无栈非递归遍历 Haffman 树 , 求 Haffman 编码 else if (HT[p].weight ==1 ) { // 向右 HT[p].weight = 2;

if(HT[p].rchild != 0){ p = HT[p].rchild; cd[cdlen++]=“1”;}

else {

HT[p].weight = 0;

p = HT[p].parent;

--cdlen; // 退到父亲节点 , 编码长度减 1

} //end else

Page 28: 第六章 树和二叉树2

// HT[p].weight = 2 时 ,

HT[p].weight = 0;

p = HT[p].parent;

--cdlen; // 退到父亲节点 , 编码长度减 1

0

0

HT(‘23’).lchild =0

HT(‘23’).rchild =0

cd: 00p = HT[’23’].parent = ’42’

cdlen = 2-1 = 2

cd: 0

再开始遍历’ 42’ 的右边

...

Page 29: 第六章 树和二叉树2

上机实习 2-1