文字描述

结点的路径长度

  从树中一个结点到另一个结点之间的分支构成这两个结点之间的路径,路径上的分支数目称作路径长度。

树的路径长度

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

结点的带权路径长度

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

树的带权路径长度

  所有叶子结点的带权路径长度之和

最优二叉树或哈夫曼树

  假设有n个权值{w1,w2, … ,wn},试构造一颗有n个叶子结点的二叉树,每个叶子结点带权wi,则其中带权路径长度WPL最小的二叉树称作最优二叉树或哈夫曼树。

哈夫曼算法

  目的:构造一颗哈夫曼树

  实现:

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

  (2) 在F中选取两颗根结点的权值最小的树作为左右子树构造一颗新的二叉树,且置新的二叉树的根结点的权值为其左右子树上根结点的权值之和。

  (3) 在F中删除这两颗树,同时将新得到的二叉树加入F中

  (4) 重复(2)和(3),直到F只含一颗树为止。这颗树便是哈夫曼树。

哈夫曼编码

  假设A、B、C、D的编码分别为00,01,10和11,则字符串’ABACCDA‘的电文便是’00010010101100’,对方接受时,可按二位一分进行译码。但有时希望电文总长尽可能的短, 如果让电文中出现次数较多的字符采用尽可能短的编码,则传送电文的总长鞭可减少。但是设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,以避免同样子电文串有多种译法,这种编码叫前缀编码。

  又如何得到使电文总长最短的二进制前缀编码呢?假设字符串中有n种字符,每种字符在电文中出现的次数作为权,设计一颗哈夫曼树。左子树为0,右子树为1,走一条从根到叶子结点的路径进行编码。

哈夫曼译码

译码的过程是分解电文字符串,从根出发,按字符‘0‘或’1‘确定找左孩子或右孩子,直至叶子结点,便求得该子串相应的字符。

示意图

算法分析

代码实现

 /*
./a.out 5 29 7 8 14 23 3 11 1001111100110001110
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #define DEBUG
//结点的最大个数
#define MAX_SIZE 50 //哈夫曼树和哈夫曼结点的存储结构
typedef struct{
unsigned int weight;
unsigned int parent, lchild, rchild;
}HNode, *HuffmanTree; //哈夫曼编码表的存储结构
typedef char **HuffmanCode; /*
* 在HT[1, ..., l]中选择parent为0且weight最小的两个结点,其序号分别为min1,min2
*/
int Select(HuffmanTree HT, int l, int *min1, int *min2){
int i = ;
*min1 = *min2 = -; //找parent为0且weight最小的min1
for(i=; i<=l; i++){
if(!HT[i].parent){
if(*min1<){
*min1 = i;
}else if(HT[*min1].weight > HT[i].weight){
*min1 = i;
}else{
//other
}
}else{
//other
}
} //找parent为0且weight最小的min2
for(i = ; i<=l; i++){
if(!HT[i].parent && i!=*min1){
if(*min2<){
*min2 = i;
}else if(HT[*min2].weight > HT[i].weight){
*min2 = i;
}else{
//other
}
}else{
//other
}
} //确保min1<min2
if(*min1 > *min2){
i = *min1;
*min1 = *min2;
*min2 = i;
} if((*min1<) && (*min2<)){
return -;
}else{
return ;
}
} /*
* 创建一颗哈夫曼树
*
* w存放n个字符的权值(权值都为非负数),构造哈夫曼树HT, L表示哈夫曼树的长度(正常情况下为2*n-1)
*/
void HuffmanCreate(int w[], int n, HuffmanTree *HT, int *L){
if(n<=){
return;
}
int m = *n-, i = , s1 = , s2 = ;
//0号单元不用
(*HT) = (HuffmanTree)malloc((m+)*sizeof(HNode));
HuffmanTree p = *HT;
*L = m; //给哈夫曼树的叶子结点赋值
for(p=(*HT+), i = ; i<n; i++){
p[i].weight = w[i];
p[i].parent = p[i].lchild = p[i].rchild = ;
} //给哈夫曼树的非叶子结点赋值
for(i=n+; i<=m; ++i){
//在HT[1,...,i-1]中选择parent为0且weight最小的两个结点,其序号分别为s1和s2
if( Select(*HT, i-, &s1, &s2) < ){
break;
}
//用s1和s2作为左右子树构造一颗新的二叉树
(*HT)[s1].parent = (*HT)[s2].parent = i;
(*HT)[i].lchild = s1;
(*HT)[i].rchild = s2;
//置新的二叉树的根结点的权值为其左右子树上根结点的权值之和
(*HT)[i].weight = (*HT)[s1].weight + (*HT)[s2].weight;
}
return;
} /*
* 哈夫曼编码
*
* 从叶子到根逆向求每个字符的哈夫曼编码
*/
void HuffmanEncoding_1(HuffmanTree HT, HuffmanCode *HC, int n){
//分配n个字符编码的头指针向量
*HC = (HuffmanCode)malloc((n+)*sizeof(char *));
//分配求编码的工作空间
char *cd = (char*)malloc(n+sizeof(char));
//编码结束符
cd[n-] = '\0';
int i = , start = , c = , f = ;
//逐个字符求哈夫曼编码
for(i=; i<=n; i++){
//编码结束位置
start = n-;
//从叶子到根逆向求编码
for(c=i, f=HT[i].parent; f!=; c = f, f=HT[f].parent){
if(HT[f].lchild == c){
cd[--start] = '';
}else{
cd[--start] = '';
}
}
//为第i个字符编码分配空间
(*HC)[i-] = (char*)malloc((n-start)*sizeof(char));
//从cd复制编码到HC
strncpy((*HC)[i-], cd+start, n-start);
}
//释放工作空间
free(cd);
return ;
} /*
* 哈夫曼编码
*
* 从根出发,遍历整个哈夫曼树,求得各个叶子结点所表示的字符的哈夫曼编码
*/
void HuffmanEncoding_2(HuffmanTree HT, HuffmanCode *HC, int n){
*HC = (HuffmanCode)malloc((n+)*sizeof(char *));
int p = *n-;
int cdlen = ;
int i = ;
char *cd = (char*)malloc(n+sizeof(char));
//利用weight,当作遍历哈夫曼树时的结点状态标志
for(i=; i<=(*n-); i++){
HT[i].weight = ;
}
while(p){
if(HT[p].weight == ){
//向左
HT[p].weight = ;
if(HT[p].lchild != ){
p = HT[p].lchild;
cd[cdlen++] = '';
}else if(HT[p].rchild == ){
//登记叶子结点的字符的编码
(*HC)[p-] = (char *)malloc((cdlen+)*sizeof(char));
memset((*HC)[p-], , (cdlen+)*sizeof(char));
cd[cdlen] = '\0';
//复制编码
strncpy((*HC)[p-], cd, cdlen);
}
}else if(HT[p].weight == ){
//向右
HT[p].weight = ;
if(HT[p].rchild != ){
p = HT[p].rchild;
cd[cdlen++] = '';
}
}else{
//退到父结点,编码长度减1
HT[p].weight = ;
p = HT[p].parent;
--cdlen;
}
}
} /*
*哈夫曼译码
*
*从根出发走向叶子结点,找到叶子结点后输出该结点的下标值,然后返回根结点重复,直至读完。
*/
void HuffmanDecoding(HuffmanTree HT, int n, char str[], size_t len)
{
//
int p = *n-;
int i = ;
while(i < len){
printf("%c",str[i]);
if(str[i] == ''){
//走向左孩子
p = HT[p].lchild;
}else if(str[i] == ''){
//走向右孩子
p = HT[p].rchild;
}else{
printf("unexcepted char %c!!!\n", str[i]);
return;
}
//看是否走到叶子结点
if(p<=n){
//输出结点的下标值,并返回根结点
printf("\t=\t%d\n", p);
p = *n-;
}
i+=;
}
return ;
} void HuffmanPrint(HuffmanTree HT, int L){
if(L<=)
return ;
int i = ;
for(i=; i<=L; i++){
printf("%d:\t{w=%d\tp=%d\tl=%d\tr=%d}\n", i, HT[i].weight, HT[i].parent, HT[i].lchild, HT[i].rchild);
}
} int main(int argc, char *argv[])
{
if(argc < ){
return -;
} int i = , L = ;
int w[MAX_SIZE] = {};
HuffmanTree HT = NULL; for(i=; i<argc; i++){
w[i-] = atoi(argv[i]);
}
#ifdef DEBUG
printf("创建并打印哈夫曼树:\n");
#endif
HuffmanCreate(w, i-, &HT, &L);
HuffmanPrint(HT, L); #ifdef DEBUG
printf("从叶子结点到根逆向求哈夫曼编码:\n");
#endif
HuffmanCode HC;
HuffmanEncoding_1(HT, &HC, i-); #ifdef DEBUG
int j = ;
for(j=; j<i-; j++){
printf("%d:\t{w:%d\tcode=%s}\n", j+, w[j], HC[j]);
free(HC[j]);
}
free(HC);
#endif #ifdef DEBUG
printf("从根出发求哈夫曼编码:\n");
#endif
HuffmanEncoding_2(HT, &HC, i-); #ifdef DEBUG
for(j=; j<i-; j++){
printf("%d:\t{w:%d\tcode=%s}\n", j+, w[j], HC[j]);
free(HC[j]);
}
free(HC);
#endif printf("按照上述编码指示,输入一串0/1字符串以进行译码:");
char str[] = {};
scanf("%s", &str);
HuffmanDecoding(HT, i-, str, strlen(str));
return ;
}

最优二叉树

 运行

树和二叉树->最优二叉树的更多相关文章

  1. 哈夫曼树【最优二叉树】【Huffman】

    [转载]只为让价值共享,如有侵权敬请见谅! 一.哈夫曼树的概念和定义 什么是哈夫曼树? 让我们先举一个例子. 判定树:         在很多问题的处理过程中,需要进行大量的条件判断,这些判断结构的设 ...

  2. Data Structure 之 最优二叉树

    给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近. ...

  3. 数据结构之Huffman树与最优二叉树

    最近在翻炒一些关于树的知识,发现一个比较有意思的二叉树,huffman树,对应到离散数学中的一种名为最优二叉树的路径结构,而Huffman的主要作用,最终可以归结到一种名为huffman编码的编码方式 ...

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

    赫夫曼树及其应用 赫夫曼(Huffman)树又称最优树,是一类带权路径长度最短的树,有着广泛的应用. 最优二叉树(Huffman树) 1 基本概念 ① 结点路径:从树中一个结点到另一个结点的之间的分支 ...

  5. 霍夫曼(最优二叉树)和Java达到

    一.定义 一些定义: 节点之间的路径长度:在从节点树中的一个节点也经历分公司,这构成的两个节点之间的路径分支的数目后这就是所谓的路径长度 的路径长度:从树的根节点到树中每一结点的路径长度之和. 在结点 ...

  6. 【Java】 大话数据结构(9) 树(二叉树、线索二叉树)

    本文根据<大话数据结构>一书,对Java版的二叉树.线索二叉树进行了一定程度的实现. 另: 二叉排序树(二叉搜索树) 平衡二叉树(AVL树) 二叉树的性质 性质1:二叉树第i层上的结点数目 ...

  7. 【js数据结构】可逐次添加叶子的二叉树(非最优二叉树)

    最近小菜鸟西瓜莹看到了一道面试题: 给定二叉树,按层打印.例如1的子节点是2.3, 2的子节点是3.4, 5的子节点是6,7. 需要建立如图二叉树: 但是西瓜莹找到的相关代码都是用js构建最优二叉树, ...

  8. 树&二叉树&&满二叉树&&完全二叉树&&完满二叉树

    目录 树 二叉树 完美二叉树(又名满二叉树)(Perfect Binary Tree) 完全二叉树(Complete Binary Tree) 完满二叉树(Full Binary Tree) 树 名称 ...

  9. Python与数据结构[3] -> 树/Tree[0] -> 二叉树及遍历二叉树的 Python 实现

    二叉树 / Binary Tree 二叉树是树结构的一种,但二叉树的每一个节点都最多只能有两个子节点. Binary Tree: 00 |_____ | | 00 00 |__ |__ | | | | ...

随机推荐

  1. Hadoop 2.x 安装常见问题FAQ(一) NodeManager 无法启动问题解决

    一.问题描述 在搭建 Hadoop hadoop-2.4.1 集群的最后一步启动集群,在命令窗口并没有报任何错误,但是Slave 节点的 NodeManager进程始终启动不起来.随后查看了后台启动日 ...

  2. layui table 前台数字格式保留两位小数,不足补0(mysql 数据库)

    layui table 对于后台json数据,有数字的,默认不会原样显示,而是只取数值,即100.00显示为100.如果想原样显示,需转为字符串. 项目采用mysql数据库,字段类型为decimal( ...

  3. Java多线程系列——过期的suspend()挂起、resume()继续执行线程

    简述 这两个操作就好比播放器的暂停和恢复. 但这两个 API 是过期的,也就是不建议使用的. 不推荐使用 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会 ...

  4. vue:过滤器

    一.过滤器 过滤器是vue中的一个特性,作用是用于对文本进行格式化的作用. 注意:在vue 1.0中有内置的过滤器,在2.0中去掉了内置过滤器,只有自定义过滤器 二.使用位置 过滤器只能应用在两个地方 ...

  5. 解决Android8.0之后开启service时报错IllegalStateException: Not allowed to start service Intent ...

    项目测试时发现的,在双击返回键关闭应用后(并未杀死后台)重新打开APP,其他手机都OK,但是8.0的手机会出现较频繁的crash.检查代码,问题锁定在重新开启应用时的startService()上. ...

  6. token令牌和jwt

    用户登录,后端生成token返回给前端 前端拿到token,以后每次登录使用header里的token进行权限验证 后端接收到前端传来的token,如果是通过数据库或redis或session进行比对 ...

  7. Entity Framework 5中遇到的 mysql tinyint(1) 转换为 bool 的问题 (我用的是VS2013中的EF5版本)

    数据有一个字段,用的是 tinyint 长度是1  默认值为0 , 当用vs2013中的 EF5来生成 实体模型之后,看到这个列被标识为 bool 类型   Mysql官方参考文档关于布尔类型的说明: ...

  8. 【win10】显示窗口标题栏颜色

    win10默认窗口标题栏是白色的,看起来不习惯. 修改方法如下. 1.按win+X,[设置]->[个性化]->[颜色],勾选如图所示[标题栏]即可.

  9. 静态,关键字:static 接口,IUSB , API 接口关键字:interface

    //静态 //普通成员 //普通成员是属于对象的 //静态成员 //静态成员属于类的 //关键字:static //self关键字:在类里面代表该类 //在静态方法里面不能调用普通成员 //在普通方法 ...

  10. 洛谷试炼场-简单数学问题-P1088 火星人

    洛谷试炼场-简单数学问题 A--P1088 火星人 Description 人类终于登上了火星的土地并且见到了神秘的火星人.人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法 ...