数据布局:哈夫曼树及其哈夫曼编码

金歌  金牌会员 | 2024-6-24 04:00:34 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 583|帖子 583|积分 1749

目次
        1.哈夫曼树是什么?
        2.哈夫曼编码是什么?
        3.哈夫曼编码的应用
        4.包含头文件
        5.结点设计
        6.接口函数定义
        7.接口函数实现
        8.哈夫曼编码测试案列

哈夫曼树是什么?

        哈夫曼树(Huffman Tree)是一种特殊的二叉树,由David A. Huffman在1952年发明的,用于数据压缩领域。哈夫曼树是一种最优的二叉树,由于它具有最小的加权路径长度。这里的“最优”是指在给定的一组权重(通常是字符出现频率)下,哈夫曼树的加权路径长度(即树中全部叶节点的权重乘以其到根节点的间隔)是最小的,以下是哈夫曼树的特点:

        1.完全二叉树:除了末了一层外,每一层都是满的
        2.加权路径长度最小:全部叶节点的权重乘以其到根节点的间隔之和是最小的
        3.每个节点都有权重:叶节点代表单个字符,非叶节点代表字符的聚集

哈夫曼编码是什么?

        哈夫曼编码是一种利用哈夫曼树进行编码的方法。它将每个字符映射为一个唯一的二进制串,这些二进制串的长度差异,且是根据字符出现频率来确定的。频率越高的字符,其编码越短;频率越低的字符,其编码越长。这种编码方式可以有用地淘汰数据的存储空间或传输时间。实现哈夫曼编码的步骤如下:
        1.统计字符频率:首先统计数据集中每个字符出现的频率
        2.构建哈夫曼树
                1.将每个字符及其频率作为叶子节点放入优先队列(通常是最小堆)
                2.从队列中取出两个权重最小的节点,创建一个新的内部节点,其权重为这两个节点权重之和
                3.将新节点重新参加队列。重复上述步骤,直到队列中只剩下一个节点,这个节点就是哈夫曼树的根节点
        3.生成编码:从根节点开始,向左子树走标记为0,向右子树走标记为1,直到到达叶节点,此时叶节点对应的字符的路径标记就是其哈夫曼编码

哈夫曼编码的应用

        哈夫曼编码是一种非常实用的编码技能,它通过利用数据的内在特性来优化存储和传输服从:
        1.数据压缩:用于无损数据压缩,特别是在文本压缩中非常有用。
        2.文件压缩:如ZIP文件格式就利用了哈夫曼编码。
        3.通信协议:在某些通信协议中,用于淘汰传输数据的巨细。

包含头文件

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<string.h>
复制代码

结点设计

  1. #define Initsize 100
  2. typedef int Elemtype;
  3. int Node[Initsize][2];                                //定义二维数组Node存储输入的字符和字符所含的权值
  4. int NodeValue[Initsize];                        //定义一维数组NodeValue存储经排序过的字符的权值
  5. int Hand = 0;                                                //定义整形变量Hand作为数组NodeValue的头指针
  6. int CodeHead = 0;                                        //定义int类型变量CodeHead作为指针数组Code的头指针
  7. typedef struct HTree {
  8.         Elemtype value;                                        //存储结点权值
  9.         Elemtype Lvalue, Rvalue;                //存储孩子标识
  10.         struct HTree* lchild;                        //存储左孩子树
  11.         struct HTree* rchild;                        //存储右孩子树
  12. }HTree,*HfmTree;
  13. HfmTree Head;                                                //定义全局变量Head作为哈夫曼树的根节点指针
  14. HfmTree Code[Initsize];                                //定义HTree类型的指针数组Code,存储结点的地址
复制代码

接口函数定义

  1. void InitHTree(HfmTree& A);                //用于初始化哈夫曼树
  2. void InsertNode(int A);                        //用于输入字符和其权值
  3. void SortNodeV(int A);                        //用于对输入的权值进行排序
  4. void InitLHfm(HfmTree& A);                //用于哈夫曼树的左子树进行初始化并赋值
  5. void InitRHfm(HfmTree& A);                //用于哈夫曼树的右子树进行初始化并赋值
  6. void InsertHTree(HfmTree& A,int B); //用于创建哈夫曼树
  7. void PostOrder(HfmTree A);                //用于对哈夫曼树进行后序遍历
  8. void InputBTree(HfmTree A);                //用于对哈夫曼树的结点权值输出
  9. void SeekHTreeL(HfmTree A, int B);        //用于单独寻找哈夫曼树的左子树的字符及权值
  10. void SeekHTreeR(HfmTree A, int B);        //用于寻找哈夫曼树的右子树的字符及权值
  11. void InputHfmCode(HfmTree A,int B);        //用于输出哈夫曼编码
  12. void InitRootHfm(HfmTree& A,HfmTree &B,HfmTree &C); //用于对哈夫曼树的根结点进行初始化并赋值
复制代码

接口函数实现

  1. void InputHfmCode(HfmTree A,int B) { //用于输出哈夫曼编码
  2.         int i,j;
  3.         while (A != NULL) {                                 //对哈夫曼树的左子树进行进栈操作
  4.                 Code[CodeHead] = A;
  5.                 A = A->lchild;
  6.                 CodeHead++;
  7.         }
  8.         printf("\n");
  9.         SeekHTreeL(A, B);                //使用函数SeekHtreeL对其哈夫曼树进行寻找左子树的字符及权值
  10.         for (i = 1; i < CodeHead; i++) {
  11.                 printf("%d", Code[i]->Lvalue);       
  12.         //对栈里结点所含的Lvalue进行输出(从根结点开始输出其含有的LvaLue)
  13.         }
  14.         CodeHead--;                                        //出栈操作
  15.         while (CodeHead != 0) {                //判断栈是否为空
  16.                 printf("\n");
  17.                 SeekHTreeR(A, B);                //使用函数SeekHTreeR对其哈夫曼树进行寻找右子树的字符及权值
  18.                 for (i = 0; i <= CodeHead - 1; i++) {       
  19.         //对栈里结点所含的Lvalue进行输出(从根结点开始输出其含有的LvaLue)
  20.                         if (i == CodeHead - 1) {                                //若为栈尾结点则输出其右子树
  21.                                 printf("%d", Code[i]->Rvalue);       
  22.                                 break;
  23.                         }
  24.                         printf("%d", Code[i]->Lvalue);
  25.                 }
  26.                 CodeHead--;                                //出栈操作
  27.         }
  28. }
  29. void SeekHTreeR(HfmTree A, int B) {        //用于寻找哈夫曼树的右子树的字符及权值
  30.         int i,j;
  31.         for (i = CodeHead - 1; i >= 0; i++){ //遍历栈
  32.                 if (i == CodeHead-1) {                         //判断是否为栈的倒数第二个结点(跟定义的结点的有关)
  33.                         for (j = 0; j < B; j++)        {         //遍历存储字符及权值的数组,寻找对应的字符和权值
  34.                                 if (Node[j][1] == Code[i]->rchild->value) {
  35.                                         Node[j][1] = -1;       
  36.                     //未防止字符不一样,但权值相同的出现,造成输出哈夫曼编码错误
  37.                                         printf("%c的哈夫曼编码为:", Node[j][0]);
  38.                                         break;                                       
  39.                                 }
  40.                         }
  41.                 }
  42.                 break;                       
  43.         }
  44. }
  45. void SeekHTreeL(HfmTree A, int B){        //用于单独寻找哈夫曼树的左子树的字符及权值
  46.         int i,j;
  47.         for (i = 0; i < CodeHead; i++) {        //遍历栈
  48.                 if (i == CodeHead - 1) {                //判断是否为栈的倒数第二个结点(跟定义的结点的有关)
  49.                         for (j = 0; j < B; j++) {        //遍历存储字符及权值的数组,寻找对应的字符和权值
  50.                                 if (Node[j][1] == Code[i]->value) {       
  51.                                         Node[j][1] = -1;       
  52.                     //未防止字符不一样,但权值相同的出现,造成输出哈夫曼编码错误
  53.                                         printf("%c的哈夫曼编码为:", Node[j][0]);
  54.                                         break;
  55.                                 }
  56.                         }
  57.                 }
  58.         }
  59. }
  60. void InputBTree(HfmTree A) {        //用于对哈夫曼树的结点权值输出               
  61.         printf("%d   ", A->value);
  62. }
  63. void PostOrder(HfmTree A) {                //用于对哈夫曼树进行后序遍历               
  64.         if (A != NULL) {
  65.                 PostOrder(A->lchild);       
  66.                 PostOrder(A->rchild);       
  67.                 InputBTree(A);                       
  68.         }
  69. }
  70. void InsertHTree(HfmTree& A,int B) {//用于创建哈夫曼树
  71.         if(Hand<B-1){                                //判断是否已将所有字符及权值进行构建对应的哈夫曼树的结点
  72.                 if (A == NULL) {               
  73.                         HfmTree Q = (HTree*)malloc(sizeof(HTree));               
  74.                         HfmTree W = (HTree*)malloc(sizeof(HTree));               
  75.                         InitLHfm(Q);                                //使用函数InitLHfm对其左子树初始化
  76.                         Hand++;
  77.                         InitRHfm(W);                                //使用函数InitRHfm对其右子树初始化
  78.                         InitRootHfm(A, Q, W);                //使用函数InitRootHfm对其根结点初始化
  79.                         Head = A;                                        //更新哈夫曼树的头指针的指向
  80.                         InsertHTree(A, B);                       
  81.                 }
  82.                 else {
  83.                         HfmTree Q = (HTree*)malloc(sizeof(HTree));               
  84.                         HfmTree W = (HTree*)malloc(sizeof(HTree));               
  85.                         Hand++;
  86.                         InitRHfm(Q);                                //使用函数InitRHfm对其右子树初始化
  87.                         InitRootHfm(W, A, Q);                //使用函数InitRootHfm对其根结点初始化
  88.                         Head = W;
  89.                         InsertHTree(W, B);
  90.                 }
  91.         }
  92. }
  93. void InitRootHfm(HfmTree& A, HfmTree& B, HfmTree& C) {
  94.     //用于对哈夫曼树的根结点进行初始化并赋值
  95.         A = (HTree*)malloc(sizeof(HTree));
  96.         A->value = B->value + C->value;                        //根结点的权值为两个子结点的权值之和
  97.         A->Lvalue = 1;                                                        //添加左子树标识
  98.         A->Rvalue = 0;
  99.         A->lchild = B;                                                        //添加根结点指向的左子树
  100.         A->rchild = C;                                                        //添加根结点指向的右子树
  101.         printf("新建的根结点的权值数据为%d\n", A->value);
  102. }
  103. void InitRHfm(HfmTree& A) {                //用于哈夫曼树的右子树进行初始化并赋值
  104.         A->value = NodeValue[Hand];        //对结点所含的权值进行更新
  105.         A->Lvalue = 0;
  106.         A->Rvalue = 1;                                //添加右子树标识
  107.         A->lchild = NULL;                                               
  108.         A->rchild = NULL;
  109.         printf("新建的右孩子的权值数据为%d\n", A->value);
  110. }
  111. void InitLHfm(HfmTree& A) {                        //用于哈夫曼树的左子树进行初始化并赋值
  112.         A->value = NodeValue[Hand];                //对结点所含的权值进行更新
  113.         A->Lvalue = 1;                                        //添加左子树标识
  114.         A->Rvalue = 0;
  115.         A->lchild = NULL;
  116.         A->rchild = NULL;
  117.         printf("新建的左孩子的权值数据为%d\n", A->value);
  118. }
  119. void SortNodeV(int A) {                                //用于对输入的权值进行排序
  120.         int i, j, Q;
  121.         for (i = 0; i < A - 1; i++) {                                //冒泡排序
  122.                 for (j = 0; j < A - 1 - i; j++)
  123.                         if (NodeValue[j] > NodeValue[j + 1]) {
  124.                                 Q = NodeValue[j];
  125.                                 NodeValue[j] = NodeValue[j + 1];
  126.                                 NodeValue[j + 1] = Q;
  127.                         }
  128.         }
  129. }
  130. void InsertNode(int A) {                        //用于输入字符和其权值
  131.         int i, j;
  132.         char Q;
  133.         for (i = 0; i < A; i++) {
  134.                 j = 0;
  135.                 printf("请输入结点的字符");
  136.                 getchar();                        //清除缓冲区,防止赋值脏数据
  137.                 Q=getchar();
  138.                 Node[i][j] = (int) Q;
  139.                 j++;
  140.                 printf("请输入结点的权值");
  141.                 scanf_s("%d", &Node[i][j]);
  142.                 NodeValue[i] = Node[i][j];
  143.         }
  144. }
  145. void InitHTree(HfmTree& A) {                //用于初始化哈夫曼树
  146.         A = NULL;
  147.         printf("初始化哈夫曼树成功\n");
  148. }
复制代码

哈夫曼编码测试案列

  1. void main() {
  2.         int NodeSize,i;
  3.         HfmTree X;
  4.         InitHTree(X);
  5.         printf("请问需要输入多少个字符");
  6.         scanf_s("%d", &NodeSize);
  7.         InsertNode(NodeSize);
  8.         SortNodeV(NodeSize);
  9.         InsertHTree(X, NodeSize);
  10.         printf("创建哈夫曼树成功\n");
  11.         printf("后序遍历的哈夫曼树为:");
  12.         PostOrder(Head);
  13.         printf("\n");
  14.         InputHfmCode(Head,NodeSize);
  15. }
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

金歌

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表