1、基本概念

a、路径和路径长度

若在一棵树中存在着一个结点序列 k1,k2,……,kj, 使得 ki是ki+1 的双亲(1<=i<j),则称此结点序列是从 k1 到 kj 的路径。

从 k1 到 kj 所经过的分支数称为这两点之间的路径长度,它等于路径上的结点数减1.

b、结点的权和带权路径长度

在许多应用中,常常将树中的结点赋予一个有着某种意义的实数,我们称此实数为该结点的权,(如下面一个树中的蓝色数字表示结点的权)

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

c、树的带权路径长度

树的带权路径长度定义为树中所有叶子结点的带权路径长度之和,公式为:

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

如下图中树的带权路径长度 WPL = 9 x 2 + 12 x 2 + 15 x 2 + 6 x 3 + 3 x 4 + 5 x 4  =  122

d、哈夫曼树

哈夫曼树又称最优二叉树。它是 n 个带权叶子结点构成的所有二叉树中,带权路径长度 WPL 最小的二叉树。

如下图为一哈夫曼树示意图。

2、构造哈夫曼树

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:

(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);

(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;

(3)从森林中删除选取的两棵树,并将新树加入森林;

(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。

如:对 下图中的六个带权叶子结点来构造一棵哈夫曼树,步骤如下:


注意:为了使得到的哈夫曼树的结构尽量唯一,通常规定生成的哈夫曼树中每个结点的左子树根结点的权小于等于右子树根结点的权。

具体算法如下:

[cpp] view plaincopy
  1. //2、根据数组 a 中 n 个权值建立一棵哈夫曼树,返回树根指针
  2. struct BTreeNode* CreateHuffman(ElemType a[], int n)
  3. {
  4. int i, j;
  5. struct BTreeNode **b, *q;
  6. b = malloc(n*sizeof(struct BTreeNode));
  7. for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点
  8. {
  9. b[i] = malloc(sizeof(struct BTreeNode));
  10. b[i]->data = a[i];
  11. b[i]->left = b[i]->right = NULL;
  12. }
  13. for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树
  14. {
  15. //k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
  16. int k1 = -1, k2;
  17. for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵
  18. {
  19. if (b[j] != NULL && k1 == -1)
  20. {
  21. k1 = j;
  22. continue;
  23. }
  24. if (b[j] != NULL)
  25. {
  26. k2 = j;
  27. break;
  28. }
  29. }
  30. for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小
  31. {
  32. if (b[j] != NULL)
  33. {
  34. if (b[j]->data < b[k1]->data)
  35. {
  36. k2 = k1;
  37. k1 = j;
  38. }
  39. else if (b[j]->data < b[k2]->data)
  40. k2 = j;
  41. }
  42. }
  43. //由最小权值树和次最小权值树建立一棵新树,q指向树根结点
  44. q = malloc(sizeof(struct BTreeNode));
  45. q->data = b[k1]->data + b[k2]->data;
  46. q->left = b[k1];
  47. q->right = b[k2];
  48. b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置
  49. b[k2] = NULL;//k2位置为空
  50. }
  51. free(b); //删除动态建立的数组b
  52. return q; //返回整个哈夫曼树的树根指针
  53. }

3、哈夫曼编码

在电报通信中,电文是以二进制的0、1序列传送的,每个字符对应一个二进制编码,为了缩短电文的总长度,采用不等长编码方式,构造哈夫曼树,

将每个字符的出现频率作为字符结点的权值赋予叶子结点,每个分支结点的左右分支分别用0和1编码,从树根结点到每个叶子结点的路径上

所经分支的0、1编码序列等于该叶子结点的二进制编码。如上文所示的哈夫曼编码如下:

a 的编码为:00

b 的编码为:01

c 的编码为:100

d 的编码为:1010

e 的编码为:1011

f 的编码为:11

4、哈夫曼树的操作运算

以上文的哈夫曼树作为具体实例,用详细的程序展示哈夫曼树的操作运算

[cpp] view plaincopy
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. typedef int ElemType;
  4. struct BTreeNode
  5. {
  6. ElemType data;
  7. struct BTreeNode* left;
  8. struct BTreeNode* right;
  9. };
  10. //1、输出二叉树,可在前序遍历的基础上修改。采用广义表格式,元素类型为int
  11. void PrintBTree_int(struct BTreeNode* BT)
  12. {
  13. if (BT != NULL)
  14. {
  15. printf("%d", BT->data); //输出根结点的值
  16. if (BT->left != NULL || BT->right != NULL)
  17. {
  18. printf("(");
  19. PrintBTree_int(BT->left); //输出左子树
  20. if (BT->right != NULL)
  21. printf(",");
  22. PrintBTree_int(BT->right); //输出右子树
  23. printf(")");
  24. }
  25. }
  26. }
  27. //2、根据数组 a 中 n 个权值建立一棵哈夫曼树,返回树根指针
  28. struct BTreeNode* CreateHuffman(ElemType a[], int n)
  29. {
  30. int i, j;
  31. struct BTreeNode **b, *q;
  32. b = malloc(n*sizeof(struct BTreeNode));
  33. for (i = 0; i < n; i++) //初始化b指针数组,使每个指针元素指向a数组中对应的元素结点
  34. {
  35. b[i] = malloc(sizeof(struct BTreeNode));
  36. b[i]->data = a[i];
  37. b[i]->left = b[i]->right = NULL;
  38. }
  39. for (i = 1; i < n; i++)//进行 n-1 次循环建立哈夫曼树
  40. {
  41. //k1表示森林中具有最小权值的树根结点的下标,k2为次最小的下标
  42. int k1 = -1, k2;
  43. for (j = 0; j < n; j++)//让k1初始指向森林中第一棵树,k2指向第二棵
  44. {
  45. if (b[j] != NULL && k1 == -1)
  46. {
  47. k1 = j;
  48. continue;
  49. }
  50. if (b[j] != NULL)
  51. {
  52. k2 = j;
  53. break;
  54. }
  55. }
  56. for (j = k2; j < n; j++)//从当前森林中求出最小权值树和次最小
  57. {
  58. if (b[j] != NULL)
  59. {
  60. if (b[j]->data < b[k1]->data)
  61. {
  62. k2 = k1;
  63. k1 = j;
  64. }
  65. else if (b[j]->data < b[k2]->data)
  66. k2 = j;
  67. }
  68. }
  69. //由最小权值树和次最小权值树建立一棵新树,q指向树根结点
  70. q = malloc(sizeof(struct BTreeNode));
  71. q->data = b[k1]->data + b[k2]->data;
  72. q->left = b[k1];
  73. q->right = b[k2];
  74. b[k1] = q;//将指向新树的指针赋给b指针数组中k1位置
  75. b[k2] = NULL;//k2位置为空
  76. }
  77. free(b); //删除动态建立的数组b
  78. return q; //返回整个哈夫曼树的树根指针
  79. }
  80. //3、求哈夫曼树的带权路径长度
  81. ElemType WeightPathLength(struct BTreeNode* FBT, int len)//len初始为0
  82. {
  83. if (FBT == NULL) //空树返回0
  84. return 0;
  85. else
  86. {
  87. if (FBT->left == NULL && FBT->right == NULL)//访问到叶子结点
  88. return FBT->data * len;
  89. else //访问到非叶子结点,进行递归调用,返回左右子树的带权路径长度之和,len递增
  90. return WeightPathLength(FBT->left,len+1)+WeightPathLength(FBT->right,len+1);
  91. }
  92. }
  93. //4、哈夫曼编码(可以根据哈夫曼树带权路径长度的算法基础上进行修改)
  94. void HuffManCoding(struct BTreeNode* FBT, int len)//len初始值为0
  95. {
  96. static int a[10];//定义静态数组a,保存每个叶子的编码,数组长度至少是树深度减一
  97. if (FBT != NULL)//访问到叶子结点时输出其保存在数组a中的0和1序列编码
  98. {
  99. if (FBT->left == NULL && FBT->right == NULL)
  100. {
  101. int i;
  102. printf("结点权值为%d的编码:", FBT->data);
  103. for (i = 0; i < len; i++)
  104. printf("%d", a[i]);
  105. printf("\n");
  106. }
  107. else//访问到非叶子结点时分别向左右子树递归调用,并把分支上的0、1编码保存到数组a
  108. {   //的对应元素中,向下深入一层时len值增1
  109. a[len] = 0;
  110. HuffManCoding(FBT->left, len + 1);
  111. a[len] = 1;
  112. HuffManCoding(FBT->right, len + 1);
  113. }
  114. }
  115. }
  116. //主函数
  117. void main()
  118. {
  119. int n, i;
  120. ElemType* a;
  121. struct BTreeNode* fbt;
  122. printf("从键盘输入待构造的哈夫曼树中带权叶子结点数n:");
  123. while(1)
  124. {
  125. scanf("%d", &n);
  126. if (n > 1)
  127. break;
  128. else
  129. printf("重输n值:");
  130. }
  131. a = malloc(n*sizeof(ElemType));
  132. printf("从键盘输入%d个整数作为权值:", n);
  133. for (i = 0; i < n; i++)
  134. scanf(" %d", &a[i]);
  135. fbt = CreateHuffman(a, n);
  136. printf("广义表形式的哈夫曼树:");
  137. PrintBTree_int(fbt);
  138. printf("\n");
  139. printf("哈夫曼树的带权路径长度:");
  140. printf("%d\n", WeightPathLength(fbt, 0));
  141. printf("树中每个叶子结点的哈夫曼编码:\n");
  142. HuffManCoding(fbt, 0);
  143. }

运行结果:

Huffman树概念及理解相关推荐

  1. BlockChain:区块链技术基础概念综合理解——个人总结

    BlockChain:区块链技术基础概念综合理解--个人总结 目录 区块链的进阶与意义 1.区块链技术的进阶 2.区块链技术三大意义-传递价值.建立可信用环境.提高效率和降低成本 1.传递价值 2.建 ...

  2. 深夜爆肝:万字长文3种语言实现Huffman树(强烈建议三连)

    文章目录 一.C语言能干大事 1. C语言下Huffman树的计算过程分析 2. C语言下Huffman树的编程 二.C#语言也不赖 1. C#下Huffman类的设计 2. C#中界面设计 3. 建 ...

  3. 【用学校抄作业带你走进可持久化线段树(主席树)】可持久化线段树概念+全套模板+例题入门:[福利]可持久化线段树)

    我似乎很少写这种算法博客 可持久化线段树概念 概念介绍(类比帮助理解) 简单分析一下时间和空间复杂度(内容池) 模板 结构体变量 建树模板 单点修改模板 单点查询模板 区间修改模板(pushup) 区 ...

  4. Huffman编码(Huffman树)

    [0]README 0.1) 本文总结于 数据结构与算法分析, 源代码均为原创, 旨在 理解 "Huffman编码(Huffman树)" 的idea 并用源代码加以实现: 0.2) ...

  5. huffman树_笃学不倦|c语言构造哈夫曼树哈夫曼编码

    艾薇巴蒂!许久不见甚是想念,想必这"涨姿势"的时刻大家已经期待许久了!今天我们要共同学习的是c语言构造哈夫曼树-哈夫曼编码 构造哈夫曼树 首先,我们需要了解哈夫曼树是什么: 相关知 ...

  6. Huffman树(哈夫曼树)

    哈夫曼树又称最优二叉树,是一种带权路径长度最短的二叉树.所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数).树的带权路径 ...

  7. B树和B+树还没理解透?真的别去面试了,过不了。

    这篇文章主要介绍了还不理解B树和B+树,那就看看这篇文章吧! 这里我也最新整理了40套2020年Java面试题合集1000+页PDF文档!有需要的小伙伴可以点这里.这里,暗号:CSDN.如果你最近在面 ...

  8. Java学习——数据结构——Huffman树

    学习尚硅谷韩顺平老师的Java数据结构笔记,详情请移步网站 1.介绍 (1) 给定 n 个权值作为 n 个叶子结点,构造一棵二叉树,若该树的带权路径长度(wpl)达到最小,称这样的二叉树为最优二叉树, ...

  9. 赫夫曼(Huffman)树/编码

    一 . 基本概念: 赫夫曼树:给定带权的N个叶子构成的所有二叉树中,树的带权路径长度最小的二叉树(最优二叉树) 带权路径长度:所有树叶到树根之间的路径长度与该节点上权的乘积 权:赋予节点的有意义的参数 ...

  10. 自己动手写word2vec (三):构建Huffman树

    系列所有帖子 自己动手写word2vec (一):主要概念和流程 自己动手写word2vec (二):统计词频 自己动手写word2vec (三):构建Huffman树 自己动手写word2vec ( ...

最新文章

  1. [轉]Webdings字体图案
  2. Bootstrap-datepicker设置开始时间结束时间范围
  3. linux文件统计命令,linux文件统计命令和目录统计命令
  4. IP地址与MAC地址的区别
  5. ibatis中的xml配置文件
  6. CSS布局教程:用DIV CSS实现国内经典式三行两列布局-CSS布局实例
  7. SSIS 包部署错误 0xC0010014
  8. oracle不空顺序输出,Oracle应用笔记
  9. VB6 如何连接MYSQL数据库
  10. 【ElasticSearch】 ElasticSearch 写入流程
  11. Ajax Post请求实例
  12. java世博会_世博会申请由xcode修改
  13. 数据库系统——基本概念
  14. JavaScript数据类型 - Undefined类型
  15. Windows和Ubuntu双系统美化 针对UEFI启动方式
  16. 新手提问!求解答QAQADODB.Recordset 错误 #x27;800a0bb9#x27; 参数类型不正确,或不在可以接受的范围之内,或与其他参数冲突。
  17. 为什么别人进步你退步,你应该如何提升自己
  18. 判断设备是否是 iphone5
  19. 工作簿(Workbook)基本操作应用示例
  20. mysql的UNIX_TIMESTAMP用法

热门文章

  1. rabbitmq - (消息队列) 的基本原理介绍
  2. QQ连连看 逆向分析外挂制作报告【脱壳ASPPack】【模拟点击】【内联HOOK】
  3. Visual Studio中创建混合移动应用程序解决方案Xamarin Portable Razor
  4. 2014 年 1 月 21 日国内互联网根域名服务器 (DNS) 故障是什么原因?
  5. 微信小程序——实现时钟样式
  6. 重新认识HTML中的p标签
  7. linux测试at命令,linux at命令:(定时执行脚本)
  8. python获取当前服务器路径_利用Python实现对Web服务器的目录探测
  9. MapReduce官方案例wordcount
  10. (day 52 - DFS) 剑指 Offer 68 - II. 二叉树的最近公共祖先