当前位置:   article > 正文

[C、C++]数据结构:哈夫曼树 & 字母表的哈夫曼编码_哈夫曼数加权构建,打印字符并编码

哈夫曼数加权构建,打印字符并编码

欢迎关注本人b站账号:Dr_四倍体果蝇

UID:12595237

一、哈夫曼树の重要概念

结点的权、结点的带权路径长度,树的带权路径长度。

哈夫曼树(最优二叉树):含有某n个叶子结点的,带权路径长度最低的二叉树。

哈夫曼树不唯一。

二、哈夫曼树の构造

1.每次选择2个权值最低的结点(树)作为兄弟,形成新的树,根节点为两结点权值之和

2.新树加入树的集合

3.重复前两步直到剩下一棵树

一共结合n-1次。

哈夫曼树的结点数为n-1。

三、哈夫曼编码

往往结点的权重用其data的出现频率代替,为了让传递的二进制信息尽可能小。

例:根据ABCD的频率得到编码方案

左侧是固定长度编码,右侧是可变长度编码。

前缀编码:不存在某个元素的编码是另一个元素的前缀的情况。

非前缀编码:存在~

哈夫曼编码得到的一定是前缀编码。每一个元素作为叶子结点,把频度作为权值。一般左指针看成0,右指针看成1。

四、哈夫曼编码实现26字母表编码

已经将所有整理的思路放在了注释里。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #define ARRAY_SIZE(a) sizeof(a)/sizeof(a[0]) // 数组的大小:数组的总字节数/数组的第一个元素的字节数
  4. #define MAX_TREE_HT 100 // 哈夫曼树的编码数组的最大长度
  5. // MinHeapNode 结构体定义
  6. struct MinHeapNode {
  7. char data;
  8. int freq;
  9. struct MinHeapNode* left, * right;
  10. };
  11. // MinHeapNode是最小堆的节点,包含字符数据和频率,以及左右子节点的指针
  12. // 左右子节点指针为空的结点就是叶子结点,左右子节点指针不为空的结点就是内部结点
  13. // 最小堆结点就是哈夫曼树的结点,哈夫曼树的叶子结点就是字符,哈夫曼树的非叶子结点就是内部结点
  14. // MinHeap 结构体定义
  15. struct MinHeap {
  16. int size;
  17. int capacity;
  18. struct MinHeapNode** array;
  19. };
  20. // MinHeap是最小堆,包含堆的大小,容量,以及节点指针数组
  21. // 最小堆就是哈夫曼树的结点数组,哈夫曼树的叶子结点就是字符,哈夫曼树的非叶子结点就是内部结点
  22. // 用二级指针是因为MinHeapNode之间互相以left,right指针连接,而最小栈MinHeap的array指针只负责指向堆的结点,
  23. // 在最小栈弹出元素时,不影响结点之间的相互连接
  24. // 创建并初始化一个新的 MinHeapNode
  25. struct MinHeapNode* newNode(char data, int freq) {
  26. struct MinHeapNode* node = (struct MinHeapNode*)malloc(sizeof(struct MinHeapNode));
  27. node->data = data;
  28. node->freq = freq;
  29. node->left = node->right = NULL; // 叶子结点的左右子节点指针为空,用作判断是否是叶子结点的标准
  30. return node;
  31. }
  32. // 创建并初始化一个新的 MinHeap
  33. struct MinHeap* createMinHeap(int capacity) {
  34. struct MinHeap* minHeap = (struct MinHeap*)malloc(sizeof(struct MinHeap)); // 给最小堆分配内存
  35. minHeap->size = 0;
  36. minHeap->capacity = capacity; // 堆的容量就是哈夫曼树的结点个数
  37. minHeap->array = (struct MinHeapNode**)malloc(capacity * sizeof(struct MinHeapNode*)); // 给堆的结点指针数组分配内存
  38. return minHeap;
  39. }
  40. // 交换两个 MinHeapNode 指针
  41. void swapMinHeapNode(struct MinHeapNode** a, struct MinHeapNode** b) {
  42. struct MinHeapNode* t = *a;
  43. *a = *b;
  44. *b = t;
  45. }
  46. // 交换指针会交换指针指向的内容,也就是交换指向的最小堆结点(因为指针就是地址,交换指针就是交换地址,交换地址就是交换指针指向的内容)
  47. // 因为最小堆结点指针数组的元素是指向最小堆结点的指针,所以交换指针就是交换指针指向的最小堆结点
  48. // 最小堆调整
  49. void minHeapify(struct MinHeap* minHeap, int idx) {
  50. int smallest = idx; // 初始化最小值为根结点
  51. int left = 2 * idx + 1; // 左子节点
  52. int right = 2 * idx + 2; // 右子节点
  53. if (left < minHeap->size && minHeap->array[left]->freq < minHeap->array[smallest]->freq)
  54. smallest = left; // 如果左子节点的频率小于根结点的频率,那么最小值就是左子节点
  55. if (right < minHeap->size && minHeap->array[right]->freq < minHeap->array[smallest]->freq)
  56. smallest = right; // 如果右子节点的频率小于最小值,那么最小值就是右子节点
  57. if (smallest != idx) {
  58. swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]); // 如果最小值不是根结点,那么交换最小值和根结点
  59. minHeapify(minHeap, smallest); // 递归调整,直到最小值是根结点
  60. }
  61. }
  62. // 这个函数的作用是调整最小堆,使得最小堆的根结点是最小值,也就是哈夫曼树的根结点是最小频率的结点,其他结点的顺序不变
  63. // 检查 MinHeap 的大小是否为1
  64. int checkSizeOne(struct MinHeap* minHeap) {
  65. return (minHeap->size == 1);
  66. }
  67. // 这个函数的作用是检查最小堆的大小是否为1,如果是1,那么最小堆就是哈夫曼树
  68. // 这个结点就是哈夫曼树的根结点,也就是最小频率的结点
  69. // 从最小堆中提取最小值节点
  70. struct MinHeapNode* extractMin(struct MinHeap* minHeap) {
  71. struct MinHeapNode* temp = minHeap->array[0]; // 最小值就是根结点,minHeap->array[0]是根结点
  72. minHeap->array[0] = minHeap->array[minHeap->size - 1]; // 把最后一个结点放到根结点
  73. --minHeap->size; // 最小堆的大小减1
  74. minHeapify(minHeap, 0); // 调整最小堆
  75. return temp; // 返回最小值节点
  76. }
  77. // 这个函数的作用是从最小堆中提取最小值节点,也就是从哈夫曼树中提取最小值节点,也就是从哈夫曼树中提取最小频率的节点
  78. // 插入新节点到最小堆
  79. void insertMinHeap(struct MinHeap* minHeap, struct MinHeapNode* minHeapNode) {
  80. ++minHeap->size; // 最小堆的大小加1
  81. int i = minHeap->size - 1; // 最后一个结点的下标是size-1
  82. while (i && minHeapNode->freq < minHeap->array[(i - 1) / 2]->freq) {
  83. minHeap->array[i] = minHeap->array[(i - 1) / 2]; // 把父节点放到新节点的位置
  84. i = (i - 1) / 2; // 新节点的位置变成父节点的位置
  85. } // 如果新节点的频率小于父节点的频率,那么就把父节点放到新节点的位置,然后新节点的位置变成父节点的位置,直到新节点的频率大于父节点的频率
  86. minHeap->array[i] = minHeapNode; // 把新节点放到新节点的位置
  87. }
  88. // 构建最小堆
  89. void buildMinHeap(struct MinHeap* minHeap) {
  90. int n = minHeap->size - 1; // 最后一个结点的下标是size-1
  91. int i;
  92. for (i = (n - 1) / 2; i >= 0; --i) // 从最后一个结点的父节点开始,直到根结点
  93. minHeapify(minHeap, i); // 调整最小堆
  94. }
  95. // 这个函数和minHeapify()函数的区别是,这个函数是从最后一个结点的父节点开始,调用minHeapify(),直到根结点,调整最小堆
  96. // 判断是否是叶子结点
  97. int isLeaf(struct MinHeapNode* root) {
  98. return !(root->left) && !(root->right); // 如果左右子节点指针都为空,那么就是叶子结点
  99. }
  100. // 打印哈夫曼树的编码
  101. void printCodes(struct MinHeapNode* root, int arr[], int top) {
  102. if (root->left) { // 如果左子节点指针不为空
  103. arr[top] = 0; // 编码是0
  104. printCodes(root->left, arr, top + 1); // 递归打印左子树的编码,每次递归把编码数组的下标加1,也就是编码的位数加1
  105. }
  106. if (root->right) { // 如果右子节点指针不为空
  107. arr[top] = 1; // 编码是1
  108. printCodes(root->right, arr, top + 1); // 递归打印右子树的编码,每次递归把编码数组的下标加1,也就是编码的位数加1
  109. }
  110. if (isLeaf(root)) { // 如果是叶子结点
  111. printf("%c: ", root->data);
  112. int i;
  113. for (i = 0; i < top; ++i) // 对于编码数组的每一位,打印
  114. printf("%d", arr[i]);
  115. printf("\n");
  116. }
  117. }
  118. // 构建哈夫曼树
  119. struct MinHeapNode* buildHuffmanTree(char data[], int freq[], int size) {
  120. struct MinHeap* minHeap = createMinHeap(size); // 创建最小堆
  121. int i;
  122. for (i = 0; i < size; ++i)
  123. minHeap->array[i] = newNode(data[i], freq[i]); // 把哈夫曼树的结点放到最小堆的结点指针数组中
  124. minHeap->size = size; // 最小堆的大小是哈夫曼树的结点个数
  125. buildMinHeap(minHeap); // 构建最小堆
  126. // 最小堆的大小不是1,就继续把最小堆的最小值节点提取出来,然后把最小值节点的左右子节点放到最小堆中,直到最小堆的大小是1
  127. while (!checkSizeOne(minHeap)) { // 如果最小堆的大小不是1,那么就继续
  128. struct MinHeapNode* left = extractMin(minHeap); // 从最小堆中提取最小值节点,也就是从哈夫曼树中提取最小值节点
  129. struct MinHeapNode* right = extractMin(minHeap); // 从最小堆中提取最小值节点,也就是从哈夫曼树中提取最小值节点
  130. struct MinHeapNode* top = newNode('$', left->freq + right->freq); // 新建一个哈夫曼树的结点,字符是$,频率是左右子节点的频率之和
  131. top->left = left; // 左子节点是最小值节点
  132. top->right = right; // 右子节点是最小值节点
  133. insertMinHeap(minHeap, top); // 把新节点插入到最小堆
  134. }
  135. return extractMin(minHeap); // 从最小堆中提取最小值节点,也就是从哈夫曼树中提取最小值节点,也就是哈夫曼树的根结点
  136. }
  137. //在调用`buildHuffmanTree`函数时,虽然最后最小堆中只剩下一个节点,但这个节点实际上是哈夫曼树的根节点,
  138. //它包含了整个哈夫曼树的所有信息。在构建哈夫曼树的过程中,我们不断地从最小堆中取出两个频率最小的节点,
  139. //然后创建一个新的内部节点,这个内部节点的左右子节点就是这两个取出的节点。
  140. //这样,新的内部节点就包含了它的左右子节点的所有信息。当最小堆中只剩下一个节点时,
  141. //这个节点就是包含了所有字符和频率信息的哈夫曼树的根节点。
  142. //因此,当我们调用`printCodes(root, arr_huff, top); `函数时,虽然传入的是哈夫曼树的根节点,
  143. // 但通过遍历这个根节点,我们可以访问到哈夫曼树中的所有节点,从而打印出所有字符的哈夫曼编码。
  144. // 具体来说,`printCodes`函数会递归地访问每一个节点的左右子节点,直到访问到叶子节点(即字符),
  145. // 然后打印出从根节点到叶子节点的路径,这个路径就是字符的哈夫曼编码。
  146. // 所以虽然看起来只有一个节点,但实际上这个节点包含了整棵哈夫曼树的所有信息。
  147. int main() {
  148. char arr[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  149. 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
  150. int freq[] = { 819, 147, 383, 391, 1225, 226, 171, 457, 710, 14,
  151. 41, 377, 334, 706, 726, 289, 9, 685, 636, 941,
  152. 258, 109, 159, 21, 158, 8 };
  153. int size = ARRAY_SIZE(arr); // 哈夫曼树的结点个数
  154. struct MinHeapNode* root = buildHuffmanTree(arr, freq, size); // 构建哈夫曼树
  155. int arr_huff[MAX_TREE_HT], top = 0; // arr_huff是编码数组,top是编码数组的下标,MAX_TREE_HT是编码数组的最大长度
  156. printCodes(root, arr_huff, top); // 打印哈夫曼树的编码
  157. return 0;
  158. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/595976
推荐阅读
相关标签
  

闽ICP备14008679号