Huffman Tree


哈夫曼树和哈夫曼编码

基本概念

哈夫曼树(Huffman Tree)

哈夫曼树又称赫夫曼树、最优树,是最带权路径长度(WPL)最短的树。

路径:在树中,从一个结点到另一个结点之间的分支被称为路径。

路径长度:路径的分支数目。

结点的权:赋予结点代表某种意义的值,在哈夫曼树中权值可以理解为出现的频率。

树的带权路径长度(WPL):所有叶子结点的带权路径长度之和。

最右侧为哈夫曼树

哈夫曼编码(Huffman Coding)

哈夫曼编码是一种可变字长的编码方式,依据字符出现的频率来构造编码。为了能正确解码,可变字长编码必须满足前缀编码的性质,即任意一个字符的编码都不能是另一个字符编码的前缀,使用字符作为二叉树的叶子结点来设计,其得到的必定为前缀编码,为了得到最短前缀编码,使用哈夫曼树来进行设计。

Huffman coding


算法思想

如何构建哈夫曼树?

1.从所有结点选出权值最小的两个结点,用这两个结点构建成一颗二叉树,该二叉树的结点权值为左右孩子权值之和。

2.将构建好的二叉树加入待构建的行列,注意:上一步选出的结点不再参与构建。

3.重复步骤1和步骤2,直到待构建行列剩余一个结点。

使用数组作为存储结构,在哈夫曼树中,没有度为1的结点,树的结点数量为n0+n2,根据n0=n2+1,可得出总结点数量为n * 2 - 1,n为叶子结点数量。

从待构建序列中选出两个权值最小的结点,必须保证:不能选有双亲的结点,两个结点不能重复。

求哈夫曼编码可自顶向下也可自底向上,本文采用自底向上。


代码实现

/*************************************************
 * Author           : TangRan                    
 * Blog             : tongyin.site               
 * Email            : tarngran@outlook.com       
 * Last modified    : 2022-11-12 18:09
 * Filename         : HuffmanTree.c
 * Description      :                            
 *************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>

typedef char ElemType;
typedef char** HFcode;
typedef struct {
  ElemType ch;
  int weight;
  int parent, lchild, rchild;
}HTnode;

typedef struct {
  HTnode *data;
  int length;
}HT;

void select(HT *T, int *s1, int *s2) {
   int min = INT_MAX;
   for (int i = 0 ; i < T -> length ; ++i) {
     if (T -> data[i].parent == 0 && T -> data[i].weight < min) {
      *s1 = i;
      min = T -> data[i].weight;
     }
   }
   min = INT_MAX;
   for (int i = 0 ; i < T -> length ; ++i) {
     if (T -> data[i].parent == 0 && i != *s1) {
      if (T -> data[i].weight < min) {
        *s2 = i;
        min = T -> data[i].weight;
      }
     }
   }
}

void createHT(HT *T) {
  int s1, s2, len = T -> length * 2 - 1;
  for (int i = T -> length ; i < len ; ++i) {
   select(T, &s1, &s2);
   T -> data[i].parent = 0;
   T -> data[i].ch = '^';
   T -> data[s1].parent = T -> data[s2].parent = i;
   T -> data[i].lchild = s1;
   T -> data[i].rchild = s2;
   T -> data[i].weight = T -> data[s1].weight + T -> data[s2].weight;
   T -> length++;
  }
}

HT* initHTtree(char ch[], int w[], int length) {
  HT *T = (HT*)malloc(sizeof(HT));
  T -> data = (HTnode*)malloc(sizeof(HTnode) * (2 * length - 1));
  T -> length = length;
  for(int i = 0 ; i < length ; ++i) {
   HTnode tmp = {ch[i], w[i], 0, -1, -1};
   T -> data[i] = tmp;
  }
  return T;
}

void preOrder(const HT *T, int idx) {
  if (idx == -1) return;
  if (T -> data[idx].ch == '^')
   printf("%d ", T -> data[idx].weight);
  else
   printf("%c(%d) ",T -> data[idx].ch, T -> data[idx].weight);
  preOrder(T, T -> data[idx].lchild);
  preOrder(T, T -> data[idx].rchild);
}

//自底向上构建哈夫曼编码
HFcode huffmanCode(HT *T, int n) {
  if (!T || T -> length == 0) return NULL;
  HFcode HC = (HFcode)(malloc(n * sizeof(char*)));
  char *tmp = (char*)malloc(sizeof(char) * n);
  tmp[n - 1] = '\0';
  for (int i = 0 ; i < n ; i++) {
   int idx = n - 1;
   for (int cur = i, p = T -> data[cur].parent ; cur != T -> length - 1 ;cur = p, p = T -> data[p].parent) {
      if (T -> data[p].lchild == cur) tmp[--idx] = '0';
        else tmp[--idx] = '1';
   }
   HC[i] = (char*)malloc((n - idx) * sizeof(char));
   strcpy(HC[i], &tmp[idx]);
  }
  free(tmp);
  return HC;
}

int main() {
  char ch[] = {'a', 'b', 'c', 'd', 'e', 'f'};
  int w[] = {3,7,11,8,9,12};
  int chLen = sizeof(ch) / sizeof(ch[0]), wLen = sizeof(w) / sizeof(w[0]);
  HT *T = initHTtree(ch, w, wLen);
  createHT(T);
  preOrder(T, T -> length - 1);
  printf("\n");
  HFcode code = huffmanCode(T, chLen);
  for(int i = 0 ; i < chLen ; i++) {
   printf("%c = %s\n", ch[i], code[i]);
  }
  return 0;
}

测试结果


文章作者: tang ran
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 tang ran !
  目录