数据结构之哈夫曼树

一.简介

给定n个权值作为n个叶子结点,构造一棵二叉树,若该树的带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree)。哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近。
所谓树的带权路径长度,就是树中所有的叶结点的权值乘上其到根结点的路径长度(若根结点为0层,叶结点到根结点的路径长度为叶结点的层数)。树的路径长度是从树根到每一结点的路径长度之和,记为WPL=(W1L1+W2L2+W3L3+…+WnLn),N个权值Wi(i=1,2,…n)构成一棵有N个叶结点的二叉树,相应的叶结点的路径长度为Li(i=1,2,…n)。可以证明霍夫曼树的WPL是最小的。

二.基本术语

1.路径和路径长度

在一棵树中,从一个结点往下可以达到的孩子或孙子结点之间的通路,称为路径。通路中分支的数目称为路径长度。若规定根结点的层数为1,则从根结点到第L层结点的路径长度为L-1。

2.结点的权及带权路径长度

若将树中结点赋给一个有着某种含义的数值,则这个数值称为该结点的权。结点的带权路径长度为:从根结点到该结点之间的路径长度与该结点的权的乘积。

3.树的带权路径长度

树的带权路径长度规定为所有叶子结点的带权路径长度之和,记为WPL。

三.构造

假设有n个权值,则构造出的哈夫曼树有n个叶子结点。 n个权值分别设为 w1、w2、…、wn,则哈夫曼树的构造规则为:
(1) 将w1、w2、…,wn看成是有n 棵树的森林(每棵树仅有一个结点);
(2) 在森林中选出两个根结点的权值最小的树合并,作为一棵新树的左、右子树,且新树的根结点权值为其左、右子树根结点权值之和;
(3)从森林中删除选取的两棵树,并将新树加入森林;
(4)重复(2)、(3)步,直到森林中只剩一棵树为止,该树即为所求得的哈夫曼树。
图解过程

四.编码

利用哈夫曼树求得的二进制编码称为哈夫曼编码。树中从根到每个叶子节点都有一条路径,对路径上的各分支约定指向左子树的分支表示”0”码,指向右子树的分支表示“1”码,取每条路径上的“0”或“1”的序列作为各个叶子节点对应的字符编码,即是哈夫曼编码。

就拿上图例子来说:

A,B,C,D对应的哈夫曼编码分别为:111,10,110,0

用图说明如下:

五.例题及代码实现


C++实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef int ELEMTYPE;

   // 哈夫曼树结点结构体
   typedef struct HuffmanTree
   {
      ELEMTYPE weight;
      ELEMTYPE id;        // id用来主要用以区分权值相同的结点,这里代表了下标
      struct HuffmanTree* lchild;
      struct HuffmanTree* rchild;
  }HuffmanNode;

  // 构建哈夫曼树
  HuffmanNode* createHuffmanTree(int* a, int n)
  {
      int i, j;
      HuffmanNode **temp, *hufmTree;
      temp = malloc(n*sizeof(HuffmanNode));
      for (i = 0; i<n; ++i)     // 将数组a中的权值赋给结点中的weight
      {
          temp[i] = (HuffmanNode*)malloc(sizeof(HuffmanNode));
          temp[i]->weight = a[i];
          temp[i]->id = i;
          temp[i]->lchild = temp[i]->rchild = NULL;
      }

      for (i = 0; i<n - 1; ++i)       // 构建哈夫曼树需要n-1合并
      {
          int small1 = -1, small2;      // small1、small2分别作为最小和次小权值的下标
          for (j = 0; j<n; ++j)         // 先将最小的两个下标赋给small1、small2(注意:对应权值未必最小)
          {
              if (temp[j] != NULL && small1 == -1)
              {
                  small1 = j;
                  continue;
              }
              else if (temp[j] != NULL)
              {
                  small2 = j;
                  break;
              }
          }

          for (j = small2; j<n; ++j)    // 比较权值,挪动small1和small2使之分别成为最小和次小权值的下标
          {
              if (temp[j] != NULL)
              {
                  if (temp[j]->weight < temp[small1]->weight)
                  {
                      small2 = small1;
                      small1 = j;
                  }
                  else if (temp[j]->weight < temp[small2]->weight)
                  {
                      small2 = j;
                  }
              }
          }
         hufmTree = (HuffmanNode*)malloc(sizeof(HuffmanNode));
         hufmTree->weight = temp[small1]->weight + temp[small2]->weight;
         hufmTree->lchild = temp[small1];
         hufmTree->rchild = temp[small2];

         temp[small1] = hufmTree;
         temp[small2] = NULL;
     }
     free(temp);
     return hufmTree;
 }

 // 以广义表的形式打印哈夫曼树
 void PrintHuffmanTree(HuffmanNode* hufmTree)
 {
     if (hufmTree)
     {
         printf("%d", hufmTree->weight);
         if (hufmTree->lchild != NULL || hufmTree->rchild != NULL)
         {
             printf("(");
             PrintHuffmanTree(hufmTree->lchild);
             printf(",");
             PrintHuffmanTree(hufmTree->rchild);
             printf(")");
         }
     }
 }

 // 递归进行哈夫曼编码
 void HuffmanCode(HuffmanNode* hufmTree, int depth)      // depth是哈夫曼树的深度
 {
     static int code[100];
     if (hufmTree)
     {
         if (hufmTree->lchild == NULL && hufmTree->rchild == NULL)
         {
             printf("id为%d权值为%d的叶子结点的哈夫曼编码为 ", hufmTree->id, hufmTree->weight);
             int i;
             for (i = 0; i<depth; ++i)
             {
                 printf("%d", code[i]);
             }
             printf("\n");
         }
         else
         {
             code[depth] = 0;
             HuffmanCode(hufmTree->lchild, depth + 1);
             code[depth] = 1;
             HuffmanCode(hufmTree->rchild, depth + 1);
         }
     }
 }

 // 哈夫曼解码
 void HuffmanDecode(char ch[], HuffmanNode* hufmTree, char string[])     // ch是要解码的01串,string是结点对应的字符
 {
     int i;
     int num[500];
     HuffmanNode* tempTree = NULL;
     for (i = 0; i<strlen(ch); ++i)
     {
         if (ch[i] == '0')
             num[i] = 0;
         else
             num[i] = 1;
     }
     if (hufmTree)
     {
         i = 0;      // 计数已解码01串的长度
         while (i<strlen(ch))
         {
             tempTree = hufmTree;
             while (tempTree->lchild != NULL && tempTree->rchild != NULL)
             {
                 if (num[i] == 0)
                 {
                     tempTree = tempTree->lchild;
                 }
                 else
                 {
                     tempTree = tempTree->rchild;
                 }
                 ++i;
             }
             printf("%c", string[tempTree->id]);     // 输出解码后对应结点的字符
         }
     }
 }

 int main()
 {
     int i, n;
     printf("请输入叶子结点的个数:\n");
     while (1)
     {
         scanf("%d", &n);
         if (n>1)
             break;
         else
             printf("输入错误,请重新输入n值!");
     }

     int* arr;
     arr = (int*)malloc(n*sizeof(ELEMTYPE));
     printf("请输入%d个叶子结点的权值:\n", n);
     for (i = 0; i<n; ++i)
     {
         scanf("%d", &arr[i]);
     }

     char ch[500], string[500];
     printf("请连续输入这%d个叶子结点各自所代表的字符:\n", n);
     fflush(stdin);      // 强行清除缓存中的数据,也就是上面输入权值结束时的回车符
     gets(string);

     HuffmanNode* hufmTree = NULL;
     hufmTree = createHuffmanTree(arr, n);

     printf("此哈夫曼树的广义表形式为:\n");
     PrintHuffmanTree(hufmTree);
     printf("\n各叶子结点的哈夫曼编码为:\n");
     HuffmanCode(hufmTree, 0);

     printf("要解码吗?请输入编码:\n");
     gets(ch);
     printf("解码结果为:\n");
     HuffmanDecode(ch, hufmTree, string);
     printf("\n");

     free(arr);
     free(hufmTree);

     return 0;
 }

结果
这里结果只用改一下就好

f}alg55fd5f50f0ddd0d00adafdd5505d50a5{
flag{ddf5dfd0f05550500a5af55dd0d5d0ad}

  Reprint please specify: clam 数据结构之哈夫曼树

 Previous
php学习笔记(二) php学习笔记(二)
一.函数函数(function)是一段完成特定功能的已命名代码块。函数可以遵照参数完成特定的任务,并且可能返回一个值。 1.自定义函数函数的定义function 函数名称(参数1 ,参数2=默认值 ,…){ 程序内容叙述(也叫函数体);
2019-04-17 yxld
Next 
php学习笔记(一) php学习笔记(一)
一.初识php1.php是什么php(全称:Hypertext Preprocessor,即“PHP:超文本预处理器”)是一种开源的服务端脚本语言。php独特的语法混合了C、Java、以及PHP自创的语法。它可以更快的执行动态网页。php是
2019-04-07 yxld
  TOC