数据结构最优二叉树

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

//建立叶结点个数为n权值为weight的哈夫曼树haffTree
{
int j, m1, m2, x1, x2;
//哈夫曼树haffTree初始化。n个叶结点的哈夫曼树共有2n-1个结点
for(int i = 0; i < 2 * n - 1 ; i++)
{
if(i < n) haffTree[i].weight = weight[i];
最优二叉树--哈夫曼树压缩技术的应用
1、 数据压缩技术概述、方法 随着社会的发展,人们对信息处理的要求越来越高,要求存储和通
讯的信息量也越来越大。于是信息学中出现了数据压缩这门技术,它的 基本特征就是把某些表达式中的字符串(如ASCII)转化成包含相 同信息但长度尽量短的一个新串,存储或传送经过压缩的数据可以减少 存储或通讯费用。换句话说,数据压缩的目的是减少数据的冗余度提高 数据密度的有效性。数据压缩有着广泛的应用范围,首先文件存储和分 布式系统是个大应用领域;其次数据压缩可用于数据的安全和保密;再 者,通过处理压缩数据还可以开发快速算法等等
a) 由给定的n个权值{W1,W2,…,Wn}构造n棵只有一个叶结点 的二叉树,从而得到一个二叉树的集合F={T1,T2,…, Tn};
b) 在F中选取根结点的权值最小和次小的两棵二叉树作为左、右 子树构造一棵新的二叉树,这棵新的二叉树根结点的权值为 其左、右子树根结点权值之和;
c) 在集合F中删除作为左、右子树的两棵二叉树,并将新建立的 二叉树加入到集合F中;
(4) 哈夫曼编码的算法 思路:给定字符集的哈夫曼树生成后,求哈夫曼编码的具体实现过程 是:依次以叶子C[i](0≤i≤n-1)为出发点,向上回溯至根为止。上溯 时走左分支则生成代码0,走右分支则生成代码1。
注意: ①由于生成的编码与要求的编码反序,将生成的代码先从后往前依次存 放在一个临时向量中,并设一个指针start指示编码在该向量中的起始 位置(start初始时指示向量的结束位置)。 ②当某字符编码完成时,从临时向量的start处将编码复制到该字符相 应的位串bits中即可。 ③因为字符集大小为n,故变长编码的长度不会超过n,加上一个结束 符'\0',bits的大小应为n+1。
0.03
0.03
10 110 11100 11101 11110 11111
图(三)构造哈夫曼树
表(三)指令的哈夫曼编码
对于该二叉树,我们可以规定向左的分支标记为1,向右的分支标记为
0。这样从根结点开始,沿线到达各频度指令对应的结点,所以经过的
分枝代码序列就构成了相应频度指令的哈夫曼编码,如表(三)所示。
//由n个结点的哈夫曼树haffTree构造哈夫曼编码haffCode {
m1 = m2 = MaxValue; x1 = x2 = 0; for(j = 0; j < n+i;j++) { if(haffTree[j].weight < m1 && haffTree[j].flag == 0)
{ m2 = m1; x2 = x1; m1 = haffTree[j].weight; x1 = j;
else
haffTree[i].weight = 0;
haffTree[i].parent = 0;
haffTree[i].flag = 0;
haffTree[i].leftChild = -1;
haffTree[i].rightChild = -1; } //构造哈夫曼树haffTree的n-1个非叶结点 for(i = 0;i < n-1;i++) {
中虽然大部分编码的长度大于定长编码的长度3,却使得程序的总位数
变小了。可以算出该哈夫曼的平均码长为
li×wi =1×0.40+2×0.30+3×0.15+5×0.05+5×0.40+5×0.30+5×0.30=2.2
7

i=1
根据上述例子我们可以总结出,利用哈夫曼树获得哈夫曼编码,即最优 前缀编码的具体操做:
数据压缩的研究始于二十世纪四十年代,而且发展迅速。数据压缩 实际上是一种编码,即对数据表达式的一种压缩式编码。编码是源文 (源字母表α的字)到码字(码字母表β的字)的一个映射。编码可分 为块-块,块-变量,变量-块,及变量-变量几类,其中块-块表示 源文和码字是因定长度的,而变量-变量编码是把可变长的源文映射成 可变长的码字。总的来说,数据压缩方法可分为三类:静态、动态及混 合方法。所谓静态方法就是在信文传送之前源文集和码子集之间的映射 关系就已确定,以后给定信文无论什么时刻出现在信文集中总被表示为 相同的码字,静态编码需要二次操作:第一次计算概率并确定映射,第 二次传送码字;动态方法是在信文集和码子集之间建立一种随着时间的 变化而变化的映射关系,动态方法也称为自适应编码,即它们不断地修 改源信文集与码字的对就关系以便更好地适应于信文集特征随时间的变 化,所有的自适应方法都是一次操作方法,即只需对源文集扫描一次; 当一个算法既不完全静态又不完全动态时,就称为混合方法。而我们要 提到的基于哈夫曼编码数据压缩技术,就时静态方法实现的一种方式。
可以验证,该编码是前缀编码,若一段程序有1000条指令,其中A大约
有400条,B大约有300条,C大约有50条,D大约有40,E大约有30条,F
大约有30条。对于定长编码,该段程序的总位数大约为3×1000=3000.
采用哈夫曼编码后,该段程序的总位数大约为
1×400+2×300+3×150+5×(50+40+30+30)=2200.可见,哈夫曼编码
(5) 哈夫曼编码的算法的实现 ①哈夫曼编码的数据结构设计 weight
flag
parent
leftChild
rightChild
为了在构造哈夫曼树时能方便的实现从双亲结点到左右孩子结点的操 作,在进行哈夫曼编码时能方便的实现从孩子结点到双亲结点的操作。 设计哈夫曼树的结点存储结构为双亲孩子存储结构。采用仿真指针实
能成为其它任意编码的前缀。我们把这个条件的编码叫做前缀编码。
根据以上描述,利用哈夫曼算 法,我们可以设计出最优的前缀编
指令 A
编码 0
码。首先,我们以每条指令的使用频 B
率为权值构造哈夫曼树,如图(三)所

C
0.60
D
0.30
1.00
E
0.30
0.15
F
0.40
0.15
G
0.09
0.06
0.04
0.05
ห้องสมุดไป่ตู้ n

k=1
3 4 2
5
图(一) 在给定一组具有确定权值的叶结点,可以构造出不同的带权二叉 树。例如,给出4个叶结点,设其权值分别为1,3,5,7,我们可以构 造出形状不同的多个二叉树。这些形状不同的二叉树的带权路径长度将 各不相同。图(二)给出了其中5个不同形状的二叉树。 这五棵树的带权路径长度分别为: (a)WPL=1×2+3×2+5×2+7×2=32 (b)WPL=1×3+3×3+5×2+7×1=29 (c)WPL=1×2+3×3+5×3+7×1=33 (d)WPL=7×3+5×3+3×2+1×1=43 (e)WPL=7×1+5×2+3×3+1×3=29
00
D
01
E
000
F
001
这样虽然可以使得程序的总位数达到 G
010
最小,但机器却无法解码。例如对编码串0010110该怎么识别呢?第一
个0可以识别A,也可以识别与第二个0组成的串00一起被识别为C,还可
以将前三位识别为F,这样一来,这个编码串就有多种译法。因此,若
要设计变长编码,则这种编码必须满足这样一个条件:任意一个编码不
① 用字符ci作为叶子,wi 做为叶子ci的权,构造一棵哈夫曼树,并 将树中左分支和右分支分别标记为0和1; ② 将从根到叶子的路径上的标号依次相连,作为该叶子所表示字符的 编码。该编码即为最优前缀码(也称哈夫曼编码)。
(3) 哈夫曼树求得编码为最优前缀码的原因 ① 每个叶子字符ci的码长恰为从根到该叶子的路径长度li,平均码 长(或文件总长)又是二叉树的带权路径长度WPL。而哈夫曼树是WPL最小 的二叉树,因此编码的平均码长(或文件总长)亦最小。 ② 树中没有一片叶子是另一叶子的祖先,每片叶子对应的编码就不可 能是其它叶子编码的前缀。即上述编码是二进制的前缀码。
2、 最优二叉树原理分析
(1) 最优二叉树的基本概念
最优二叉树,也称哈夫曼(Haffman)树,是指对于一组带有确定权 值的叶结点,构造的具有最小带权路径长度的二叉树。那么什么是二叉 树的带权路径长度呢?
如果设二叉树具有n个带权值的叶结点,那么从根结点到各个叶结 点的路径长度与相应结点权值的乘积之和叫做二叉树的带权路径长度, 记为: WPL=Wk·Lk,其中Wk为第k个叶结点的权值,Lk 为第k个叶结点的 路径长度。如图(一)所示的二叉树,它的带权路径长度值WPL=2×2+ 4×2+5×2+3×2=28。
//左孩子下标
int rightChild;
//右孩子下标
};
struct Code
//存放哈夫曼编码的数据
元素结构
{
int bit[MaxN];
//数组
int start;
//编码的起始下标
int weight;
//字符的权值
};
哈夫曼树构造算法如下:
void Haffman(int weight[], int n, HaffNode haffTree[])
现,每个结点的数据结构设计为:
start Bit[0] Bit[1]

Bit[MaxBit-1]
②哈夫曼编码算法C语言实现 :
struct HaffNode
//哈夫曼树的结点结构
{
int weight;
//权值
int flag;
//标记
int parent;
//双亲结点下标
int leftChild;
7 3 1
5
7 2
3 5 1 7
5
1 3 1
3
7 5
7 1 3 5
(a)
(b)
(c)
(d)
(e)
图(二 ) 具有相同叶子结点和不同带权路径长度的二叉树
由此可见,由相同权值的一组叶子结点所构成的二叉树有不同的形 态和不同的带权路径长度,那么如何找到带权路径长度最小的二叉树 (即哈夫曼树)呢?根据哈夫曼树的定义,一棵二叉树要使其WPL值最 小,必须使权值越大的叶结点越靠近根结点,而权值越小的叶结点越远 离根结点。哈夫曼(Haffman)依据这一特点于1952年提出了一种方 法,这种方法的基本思想是:
最优前缀码。
首先我们来看一个例子:
设有一台模型机,共有7种不同的 指令,其使用频率如表(一)所示。
指令
由于计算机内部只能识别0,1代码,
所以采用定长操作码,则需3位
A
使用频率(
wi)
0.40
(23=8)。显然,有一条编码没有作 B
0.30
用,这是一种浪费。一段程序中若有n C 条指令,那么程序的总位数为3×n。
} else if(haffTree[j].weight < m2 && haffTree[j].flag == 0)
{ m2 = haffTree[j].weight; x2 = j;
} } //将找出的两棵权值最小的子树合并为一棵子树
haffTree[x1].parent = n+i; haffTree[x2].parent = n+i; haffTree[x1].flag = 1; haffTree[x2].flag = 1; haffTree[n+i].weight = haffTree[x1].weight+haffTree[x2].weight; haffTree[n+i].leftChild = x1; haffTree[n+i].rightChild = x2; } } 哈夫曼编码算法如下: void HaffmanCode(HaffNode haffTree[], int n, Code haffCode[])
D
0.15 0.05
E
0.04
F
0.03
为了充分利用编码信息和减少程序 的总位数,我们可以采用变长编码。 如果对每一条指令指定一条编码,使 得这些编码互不相同且最短,是否可 以满足要求呢?即是否可以如表(二) 所示这样编码呢?
表(二) 指令的变长编码
G
0.03
表(一)指令的使用频率
指令
编码
A
0
B
1
C
d) 重复(b)(c)两步,当F中只剩下一棵二叉树时,这棵二叉 树便是所要建立的哈夫曼树。
由于这种算法是哈夫曼最早提出的,所以将最优二叉树称为哈夫曼 树。
(2) 根据最优二叉树构造哈夫曼编码
哈夫曼树被广泛应用在各种技术中,其中最典型的就是在编码技术上
的应用。利用哈夫曼树很容易求出给定字符集及其概率(或频度)分布的
相关文档
最新文档