• 【数据结构】霍夫曼树


    1.概念

    霍夫曼树(Huffman Tree),又称最优二叉树,是一种带权路径长度最短的二叉树。在霍夫曼树中,叶子节点的权值通常代表字符出现的频率,非叶子节点的权值是其子节点权值的和。霍夫曼树广泛应用于数据压缩,尤其是霍夫曼编码,它是一种基于字符出现频率的变长前缀编码。

    霍夫曼树的构建过程如下:

    1. 权值集合:首先,将给定的字符和它们对应的权值(频率)放入一个集合中。

    2. 选择最小的权值:每次从集合中选出两个具有最小权值的节点,将它们合并成一个新节点,新节点的权值是这两个子节点权值的和。

    3. 删除并添加:将选出的两个最小权值节点从集合中删除,并将新创建的节点添加到集合中。

    4. 重复步骤2和3:重复步骤2和3,直到集合中只剩下一个节点,这个节点就是霍夫曼树的根节点。

    5. 构建完成:此时,霍夫曼树构建完成,每个原始节点都成为了叶子节点,而新创建的节点都是非叶子节点。

    霍夫曼编码的过程如下:

    1. 为每个叶子节点分配码字:从根节点开始,向左的路径分配0,向右的路径分配1。这样,每个叶子节点都会得到一个唯一的二进制编码。

    2. 构建编码表:将每个字符和它对应的霍夫曼编码放入一个编码表中。

    3. 编码:使用编码表对原始数据进行编码,替换每个字符为其对应的霍夫曼编码。

    4. 解码:解码时,从霍夫曼树的根节点开始,根据编码的0和1向左或向右移动。每次到达一个叶子节点,就输出对应的字符,然后从根节点开始继续解码过程。

    霍夫曼编码是一种前缀编码,即任何一个字符的编码都不是另一个字符编码的前缀,这样可以保证编码的唯一可解性。由于频率高的字符分配的编码较短,频率低的字符分配的编码较长,因此霍夫曼编码能够实现数据的压缩。

    2.如何自己实现霍夫曼编码?

    实现霍夫曼编码需要定义数据结构来表示霍夫曼树节点,以及实现构建霍夫曼树和编码的过程。以下是一个简单的 C 语言实现。

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <string.h>
    4. // 定义霍夫曼树节点结构
    5. typedef struct {
    6. char ch; // 字符(仅用于叶子节点)
    7. int freq; // 字符频率
    8. char code[100]; // 霍夫曼编码
    9. struct HuffmanNode *left, *right; // 左右子树指针
    10. } HuffmanNode;
    11. // 函数声明
    12. HuffmanNode* createNode(int freq, char ch);
    13. void printCodes(HuffmanNode* root, char* arr, int top);
    14. void buildHuffmanTree(char ch[], int freq[], int size);
    15. void destroyTree(HuffmanNode* root);
    16. int main() {
    17. int n, i;
    18. printf("请输入字符的数量:");
    19. scanf("%d", &n);
    20. char ch[n];
    21. int freq[n];
    22. printf("请输入字符及其频率(例如:a 5):\n");
    23. for(i = 0; i < n; i++) {
    24. scanf(" %c %d", &ch[i], &freq[i]);
    25. }
    26. buildHuffmanTree(ch, freq, n);
    27. return 0;
    28. }
    29. // 创建一个新的霍夫曼树节点
    30. HuffmanNode* createNode(int freq, char ch) {
    31. HuffmanNode* newNode = (HuffmanNode*)malloc(sizeof(HuffmanNode));
    32. newNode->ch = ch;
    33. newNode->freq = freq;
    34. newNode->left = newNode->right = NULL;
    35. strcpy(newNode->code, "");
    36. return newNode;
    37. }
    38. // 打印霍夫曼编码
    39. void printCodes(HuffmanNode* root, char* arr, int top) {
    40. if (root->left) {
    41. arr[top] = '0';
    42. printCodes(root->left, arr, top + 1);
    43. }
    44. if (root->right) {
    45. arr[top] = '1';
    46. printCodes(root->right, arr, top + 1);
    47. }
    48. if (!root->left && !root->right) {
    49. printf("%c: %s\n", root->ch, root->code);
    50. strcpy(root->code, arr);
    51. }
    52. }
    53. // 构建霍夫曼树
    54. void buildHuffmanTree(char ch[], int freq[], int size) {
    55. HuffmanNode *left, *right, *top;
    56. // 创建一个节点数组
    57. HuffmanNode* nodes[size];
    58. for(int i = 0; i < size; i++) {
    59. nodes[i] = createNode(freq[i], ch[i]);
    60. }
    61. // 构建霍夫曼树
    62. for(int i = 0; i < size - 1; i++) {
    63. // 找到两个最小频率的节点
    64. HuffmanNode* min1 = NULL, *min2 = NULL;
    65. for(int j = 0; j < size; j++) {
    66. if(nodes[j] && (!min1 || nodes[j]->freq < min1->freq)) {
    67. min2 = min1;
    68. min1 = nodes[j];
    69. } else if(nodes[j] && (!min2 || nodes[j]->freq < min2->freq)) {
    70. min2 = nodes[j];
    71. }
    72. }
    73. // 创建一个新的内部节点
    74. top = createNode(min1->freq + min2->freq, '\0');
    75. top->left = min1;
    76. top->right = min2;
    77. // 从节点数组中移除已合并的节点
    78. nodes[size + i] = top;
    79. nodes[min1 - nodes] = NULL;
    80. nodes[min2 - nodes] = NULL;
    81. }
    82. // 打印霍夫曼编码
    83. char arr[100];
    84. printCodes(nodes[size + size - 2], arr, 0);
    85. // 释放内存
    86. for(int i = 0; i < size; i++) {
    87. destroyTree(nodes[i]);
    88. }
    89. }
    90. // 销毁霍夫曼树
    91. void destroyTree(HuffmanNode* root) {
    92. if(root) {
    93. destroyTree(root->left);
    94. destroyTree(root->right);
    95. free(root);
    96. }
    97. }

    这段代码定义了霍夫曼树节点的结构,并提供了一个简单的命令行界面来输入字符及其频率。然后,它构建了霍夫曼树并打印了每个字符的霍夫曼编码。最后,它释放了分配给霍夫曼树的内存。

    请注意,这个实现是为了演示目的而简化的。在实际应用中,霍夫曼编码的构建和解析通常更复杂,需要处理更多的边缘情况和优化。

    以上就是霍夫曼树的基本使用,本次代码分享到此结束,后续还会更新数据结构与算法的知识。最后的最后,还请大家点点赞,点点关注,谢谢大家!

  • 相关阅读:
    C++学习日记:关于我决定开始学习C++的那些事
    基于AERMOD模型在大气环境影响评价中的实践技术
    RegAD-Registration based Few-Shot Anomaly Detection论文学习
    Unity3D教程:布娃娃系统
    使用amp训练出现Detected call of lr_scheduler.step() before optimizer.step().
    .Net Framework中的AppDomain.AssemblyResolve事件的常见用法、问题,以及解决办法
    某些设置由你的组织来管理
    面试常问的异常体系
    07 Linux补充|秋招刷题|9月6日
    微前端四:qiankun在开发中遇到的问题
  • 原文地址:https://blog.csdn.net/m0_74055118/article/details/138047867