最优二叉树

1.树的路径长度      

树的路径长度是从树根到树中每一结点的路径长度之和。在结点数目相同的二叉树中,完全二叉树的路径长度最短。

2.树的带权路径长度(Weighted Path Length of Tree,简记为WPL)   

结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数。  

 结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积。  

 树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和,通常记为:

其中:            n表示叶子结点的数目     wi和li分别表示叶结点ki的权值和根到结点ki之间的路径长度。

树的带权路径长度亦称为树的代价。

3.最优二叉树或哈夫曼树

 在权为wl,w2,…,wn的n个叶子所构成的所有二叉树中,带权路径长度最小(即代价最小)的二叉树称为最优二叉树哈夫曼树

【例】给定4个叶子结点a,b,c和d,分别带权7,5,2和4。构造如下图所示的三棵二叉树(还有许多棵),它们的带权路径长度分别为:         (a)WPL=7*2+5*2+2*2+4*2=36         (b)WPL=7*3+5*3+2*1+4*2=46         (c)WPL=7*1+5*2+2*3+4*3=35  

其中(c)树的WPL最小,可以验证,它就是哈夫曼树。

注意:     ① 叶子上的权值均相同时,完全二叉树一定是最优二叉树,否则完全二叉树不一定是最优二叉树。

② 最优二叉树中,权越大的叶子离根越近。

③ 最优二叉树的形态不唯一,WPL最小

构造最优二叉树

1.哈夫曼算法     

哈夫曼首先给出了对于给定的叶子数目及其权值构造最优二叉树的方法,故称其为哈夫曼算法。其基本思想是:  

 (1)根据给定的n个权值wl,w2,…,wn构成n棵二叉树的森林F={T1,T2,…,Tn},其中每棵二叉树Ti中都只有一个权值为wi的根结点,其左右子树均空。  

 (2)在森林F中选出两棵根结点权值最小的树(当这样的树不止两棵树时,可以从中任选两棵),将这两棵树合并成一棵新树,为了保证新树仍是二叉树,需要增加一个新结点作为新树的根,并将所选的两棵树的根分别作为新根的左右孩子(谁左,谁右无关紧要),将这两个孩子的权值之和作为新树根的权值。  

 (3)对新的森林F重复(2),直到森林F中只剩下一棵树为止。这棵树便是哈夫曼树。    用哈夫曼算法构造哈夫曼树的过程见【动画演示】。

注意:     ① 初始森林中的n棵二叉树,每棵树有一个孤立的结点,它们既是根,又是叶子

② n个叶子的哈夫曼树要经过n-1次合并,产生n-1个新结点。最终求得的哈夫曼树中共有2n-1个结点。     ③ 哈夫曼树是严格的二叉树,没有度数为1的分支结点。

2.哈夫曼树的存储结构及哈夫曼算法的实现

(1) 哈夫曼树的存储结构     用一个大小为2n-1的向量来存储哈夫曼树中的结点,其存储结构为:

#define n 100 //叶子数目

#define m 2*n-1//树中结点总数

typedef struct

{ //结点类型       float weight; //权值,不妨设权值均大于零

int lchild,rchild,parent; //左右孩子及双亲指针

}HTNode;

typedef HTNode HuffmanTree[m];

//HuffmanTree是向量类型

注意:      因为C语言数组的下界为0,故用-1表示空指针。树中某结点的lchild、rchild和parent不等于-1时,它们分别是该结点的左、右孩子和双亲结点在向量中的下标。

 这里设置parent域有两个作用:其一是使查找某结点的双亲变得简单;其二是可通过判定parent的值是否为-1来区分根与非根结点。

(2)哈夫曼算法的简要描述

 在上述存储结构上实现的哈夫曼算法可大致描述为(设T的类型为HuffmanTree):  

(1)初始化      将T[0..m-1]中2n-1个结点里的三个指针均置为空(即置为-1),权值置为0。

 (2)输人      读人n个叶子的权值存于向量的前n个分量(即T[0..n-1])中。它们是初始森林中n个孤立的根结点上的权值。

 (3)合并      对森林中的树共进行n-1次合并,所产生的新结点依次放人向量T的第i个分量中(n≤i≤m-1)。

每次合并分两步:      ①在当前森林T[0..i-1]的所有结点中,选取权最小和次小的两个根结点[p1]和T[p2]作为合并对象,这里0≤p1,p2≤i-1。      

② 将根为T[p1]和T[p2]的两棵树作为左右子树合并为一棵新的树,新树的根是新结点T[i]。具体操作:   将T[p1]和T[p2]的parent置为i,   将T[i]的lchild和rchild分别置为p1和p2   新结点T[i]的权值置为T[p1]和T[p2]的权值之和。

注意:      合并后T[pl]和T[p2]在当前森林中已不再是根,因为它们的双亲指针均已指向了T[i],所以下一次合并时不会被选中为合并对象。
哈夫曼算法模拟演示过程【参见动画模拟】

(3)哈夫曼算法的求精   v

oid CreateHuffmanTree(HuffmanTree T)

{//构造哈夫曼树,T[m-1]为其根结点

int i,p1,p2;

InitHuffmanTree(T); //将T初始化

InputWeight(T); //输入叶子权值至T[0..n-1]的weight域

for(i=n;i<m;i++){//共进行n-1次合并,新结点依次存于T[i]中

SelectMin(T,i-1,&p1,&p2);           //在T[0..i-1]中选择两个权最小的根结点,其序号分别为p1和p2

T[p1].parent=T[p2].parent=i;           TIi].1child=p1; //最小权的根结点是新结点的左孩子           T[j].rchild=p2; //次小权的根结点是新结点的右孩子

T[i].weight=T[p1].weight+T[p2].weight;

} // end for

}
上述算法中调用的三个函数【参见练习】。

【例】以7个权值:7,5,1,4,8,10,20为例,执行CreateHuffmanTree求最优二叉树的过程【参见动画模拟】

哈夫曼编码:

1. 编码和解码      

数据压缩过程称为编码。即将文件中的每个字符均转换为一个惟一的二进制位串。      数据解压过程称为解码。即将二进制位串转换为对应的字符。

根据最优二叉树构造哈夫曼编码 

 利用哈夫曼树很容易求出给定字符集及其概率(或频度)分布的最优前缀码。哈夫曼编码正是一种应用广泛且非常有效的数据压缩技术。该技术一般可将数据文件压缩掉20%至90%,其压缩效率取决于被压缩文件的特征。

1. 具体做法

(1)用字符ci作为叶子,pi或fi做为叶子ci的权,构造一棵哈夫曼树,并将树中左分支和右分支分别标记为0和1;

(2)将从根到叶子的路径上的标号依次相连,作为该叶子所表示字符的编码。该编码即为最优前缀码(也称哈夫曼编码)。

2. 哈夫曼编码为最优前缀码

 由哈夫曼树求得编码为最优前缀码的原因:   

① 每个叶子字符ci的码长恰为从根到该叶子的路径长度li,平均码长(或文件总长)又是二叉树的带权路径长度WPL。而哈夫曼树是WPL最小的二叉树,因此编码的平均码长(或文件总长)亦最小。  

 ② 树中没有一片叶子是另一叶子的祖先,每片叶子对应的编码就不可能是其它叶子编码的前缀。即上述编码是二进制的前缀码。

3. 求哈夫曼编码的算法

(1)思想方法  

给定字符集的哈夫曼树生成后,求哈夫曼编码的具体实现过程是:依次以叶子T[i](0≤i≤n-1)为出发点,向上回溯至根为止。上溯时走左分支则生成代码0,走右分支则生成代码1。

注意:  

 ① 由于生成的编码与要求的编码反序,将生成的代码先从后往前依次存放在一个临时向量中,并设一个指针start指示编码在该向量中的起始位置(start初始时指示向量的结束位置)。   

② 当某字符编码完成时,从临时向量的start处将编码复制到该字符相应的位串bits中即可。  

 ③ 因为字符集大小为n,故变长编码的长度不会超过n,加上一个结束符'\0',bits的大小应为n+1。

(2)字符集编码的存储结构及其算法描述

typedef struct

{

char ch; //存储字符

char bits[n+1]; //存放编码位串

}CodeNode;

typedef CodeNode HuffmanCode[n];

void CharSetHuffmanEncoding(HuffmanTree T,HuffmanCode H)

{//根据哈夫曼树T求哈夫曼编码表H

int c,p,i;//c和p分别指示T中孩子和双亲的位置

char cd[n+1]; //临时存放编码

int start; //指示编码在cd中的起始位置

cd[n]='\0'; //编码结束符

for(i=0,i<n,i++)

{ //依次求叶子T[i]的编码

H[i].ch=getchar();//读入叶子T[i]对应的字符

start=n; //编码起始位置的初值

c=i; //从叶子T[i]开始上溯

while((p=T[c].parent)>=0)

{//直至上溯到T[c]是树根为止

//若T[c]是T[p]的左孩子,则生成代码0;否则生成代码1

cd[--start]=(T[p).1child==C)?'0':'1';

c=p; //继续上溯

}

strcpy(H[i].bits,&cd[start]); //复制编码位串

}//endfor

}//CharSetHuffmanEncoding

文件的编码和解码 

 有了字符集的哈夫曼编码表之后,对数据文件的编码过程是:依次读人文件中的字符c,在哈夫曼编码表H中找到此字符,若H[i].ch=c,则将字符c转换为H[i].bits中存放的编码串。

 对压缩后的数据文件进行解码则必须借助于哈夫曼树T,其过程是:依次读人文件的二进制码,从哈夫曼树的根结点(即T[m-1])出发,若当前读人0,则走向左孩子,否则走向右孩子。

一旦到达某一叶子T[i]时便译出相应的字符H[i].ch。然后重新从根出发继续译码,直至文件结束。      

文件的编码和解码算法【参见练习】。

转载于:https://www.cnblogs.com/java2016/p/7669833.html

最有二叉树 哈夫曼树相关推荐

  1. 最优二叉树——哈夫曼树

      一:什么是最优二叉树? 从我个人理解来说,最优二叉树就是从已给出的目标带权结点(单独的结点) 经过一种方式的组合形成一棵树.使树的权值最小. 最优二叉树是带权路径长度最短的二叉树.根据结点的个数, ...

  2. 6.6.1最优二叉树(赫夫曼树)

    首先我们来看一个伪代码.这个是代表成绩的等级. 然后我们知道,每一次高考,学生的成绩分布应该接近某个比例,现在我们假如分别规律如下: 为此可以作出下面的这个树. 我们发现,概率分布主要是在70-79, ...

  3. 理论基础 —— 二叉树 —— 哈夫曼树与哈夫曼编码

    [哈夫曼树] 1.相关概念 1)叶结点的权值:对叶结点赋予的一个有意义的数值量 2)二叉树的带权路径长度(WPL):设二叉树具有 n 个带权叶结点,从根结点到各叶结点的路径长度与相应叶节点权值的乘积之 ...

  4. 最优二叉树(赫夫曼树)

    赫夫曼树的介绍(写的不好地方大佬请指教) 最优二叉树又称哈夫曼树,是带权路径最短的二叉树.根据节点的个数,权值的不同,最优二叉树的形状也不同. 图 6-34 是 3 棵最优二叉树的例子,它们共同的特点 ...

  5. 最优二叉树(哈夫曼树)

    哈夫曼树 哈夫曼树的定义 哈夫曼树的构造 哈夫曼树的特点 哈夫曼编码 哈夫曼树的定义 例如: 哈夫曼树就是要构造一棵WPL最小的二叉树. 哈夫曼树的构造 哈夫曼树构造时,每次把权值最小的两颗二叉树合并 ...

  6. 算法学习之最优二叉树(赫夫曼树)

    概念 给定n个权值作为n个叶子节点,构造一颗二叉树,若该数的代全路径长度(wpl)达到最小,称这样的的二叉树为最优二叉树,也成霍夫曼树 霍夫曼树是带权路径长度最短的树,权值较大的节点离根较近 路径和路 ...

  7. 最优二叉树-哈夫曼树

    关于最优二叉树, 一开始看书 , 做题 还是蒙的 后来多做几题大致就懂得了 , 百度百科给出的官方定义: 给定N个权值作为N个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最 ...

  8. 【id:180】【20分】D. DS二叉树--赫夫曼树解码(不含代码框架)

    题目描述 已知赫夫曼编码算法和程序,在此基础上进行赫夫曼解码 在赫夫曼树的类定义中增加了一个公有方法: int  Decode(const string codestr, char txtstr[]) ...

  9. 【id:179】【20分】C. DS二叉树--赫夫曼树的构建与编码(不含代码框架)

    题目描述 给定n个权值,根据这些权值构造huffman树,并进行huffman编码 参考课本P147算法6.12 HuffmanCoding代码,注意数组访问是从位置1开始 要求:赫夫曼的构建中,默认 ...

最新文章

  1. TYAN_S8230做硬Raid
  2. Android监听ScrollView滑动到顶端和底部
  3. 云炬随笔20211012(2)
  4. java 转xml 变成两根下划线_XStream实现xml和java对象之间的互相转换(包括对属性,别名,下划线_的处理),同理JSON也可以...
  5. 加权最小二乘法的原理讲解
  6. c++ 使用socket实现C/S端文件的下载传输
  7. 查找二叉树(信息学奥赛一本通-T1367)
  8. 线性表顺序存储的基本操作方法(C语言)
  9. [经验]自定义ASP.NET服务器控件属性的状态不能保存的问题
  10. VMware ESXi 6.7注入第三方RAID驱动
  11. 窗体最小化时隐藏窗体_delphi基础
  12. python生成图表
  13. 开源OA项目:办公用品如何管理?
  14. C++多线程detach函数使用
  15. 为你的简书和 GitHub 设定个性域名 1
  16. 不懂技术怎么让服务器更安全,几个插件让你的服务器更安全(防攻击防注入)
  17. linux_SIGCHLD信号-子进程回收
  18. 乱花渐欲迷人眼:浅谈关于分布式存储的五大 “谎言”
  19. 如何解决 fs.renameSync() 跨区移动文件的问题
  20. 为你揭秘,希格斯玻色子如何赋予粒子质量

热门文章

  1. mmap mprotect详解
  2. c++11-template template Parameter
  3. 总有个短信发来一行乱码_个别收到的短信乱码,有什么办法还原么。。。
  4. 判断端口是否能用_【图文】 Windows自带入侵检测工具—Netstat命令查询 是否中木马...
  5. 兼容Tomcat和Weblogic的Spring 数据源JNDI配置
  6. Hibernate本地SQL查询SQLQuery
  7. PKUWC 2018 滚粗记
  8. Spring错误小结
  9. 从零开始学android开发-IDE空间不够报错
  10. 解决Button设置disabled后无法执行后台代码问题