从头开始构建顺序的霍夫曼树

时间:2019-11-23 16:26:25

标签: c algorithm data-structures binary-tree huffman-code

给出一些文本文件,我需要阅读每个字母数字字符并使用Huffman's algorithm.

对其进行编码。

解决字符读取,存储概率和创建节点以及使用指针创建霍夫曼的特里的问题。

但是,我需要使用二叉树的顺序表示形式创建和初始化霍夫曼树,而没有任何指针。

这可以通过使用指针创建规则树然后将其读入数组来完成,但是我的目标是直接用节点填充数组。

我考虑过创建较小的树并将它们合并在一起,但是选择了一种矩阵表示形式,从二进制堆中收集概率最小的元素,然后将它们存储到矩阵的行中,其中矩阵的行将表示级别该节点应以相反的顺序位于二叉树中。

E.g. Given characters and their probabilities as char[int] pairs.

a[1], b[1], c[2], d[1], e[3], f[11], g[2]

I aim to create matrix that looks like 
____________________________________
    a   |    b   |    d   |    g   |
____________________________________
   ab   |    c   |   dg   |    e   |
____________________________________
   abc  |   deg  |        |        |
____________________________________ 
 abcdeg |    f   |        |        |  
____________________________________
abcdefg |        |        |        |
____________________________________

Where levels of a, b, c, d, e & f would be rows of a matrix.

当前,我停留在当元素的“父”移动时如何递归递增元素级别的问题(如果我组合了来自不同级别['ab“和'c']的两个节点,则很容易将它们的级别相等c与ab并解决问题,但例如在第二行中同时包含'c'和'd'的情况下,以及如何创建完整的二叉树(如果已离开子树,则需要有正确的一棵树)终端节点的级别。

事先,我了解到该问题不是很具体,并且希望知道是否还有其他方法可以解决此问题,而不仅仅是解决上述问题。

1 个答案:

答案 0 :(得分:0)

这是人为作业的问题吗?我问是因为不使用链接的树表示需要O(2 ^ h)空间来存储高度为h的树。这是因为他们假设树是完整的,从而允许索引计算替换指针。由于霍夫曼树对于大小为m的字母可以具有高度h = m-1,因此最坏情况的数组的大小可能很大。大部分都不会使用。

但是,如果您放弃链接必须是指针并且允许其成为数组索引的想法,那么您就可以了。很久以前-在动态内存分配器普及之前-这是标准的。这个问题对这种方法特别有用,因为您总是提前知道树中的节点数:少于字母表大小的两倍。在C语言中,您可能会这样做

typedef struct {
  char ch;
  int f;
  int left, right; // Indices of children. If both -1, this is leaf for char ch.
} NODE;

#define ALPHABET_SIZE 7
NODE nodes[2 * ALPHABET_SIZE - 1] = {
  { 'a', 1, , -1, -1}, 
  { 'b', 1, -1, -1 }, 
  { 'c', 2, -1, -1 }, 
  { 'd', 1, -1, -1 }, 
  { 'e', 3, -1, -1 },
  { 'f', 11, -1, -1 }, 
  { 'g', 2, -1, -1 },
  // Rest of array for internal nodes
};
int n_nodes = ALPHABET_SIZE;

int add_internal_node(int f, int left, int right) {
  // Allocate a new node in the array and fill in its values.
  int i = n_nodes++;
  nodes[i] = (NODE) { .f = f, .left = left, .right = right };
  return i;
}

现在,您将使用如下所示的标准树构建算法:

int build_huffman_tree(void) {
  // Add the indices of the leaf nodes to the priority queue.
  for (int i = 0; i < ALPHABET_SIZE; ++i)
    add_to_frequency_priority_queue(i);
  while (priority_queue_size() > 1) {
    int a = remove_min_frequency(); // Removes index of lowest freq node from the queue.
    int b = remove_min_frequency();
    int p = add_internal_node(nodes[a].f + nodes[b].f, a, b);
    add_to_frequency_priority_queue(p);
  }
  // Last node is huffman tree root.
  return remove_min_frequency();
}

解码算法将使用如下所示的根索引:

char decode(BIT bits[], int huffman_tree_root_index) {
  int i = 0, p = huffman_tree_root_index;
  while (node[p].left != -1 || node[p].right != -1) // while not a leaf
    p = bits[i++] ? nodes[p].right : nodes[p].left;
  return nodes[p].ch;
}

当然,这不会返回消耗了多少位,真正的解码器需要这样做。真正的解码器也不会将其位放入数组中。最后,对于 encoding ,您除了子元素外还需要父索引。解决这些问题应该很有趣。祝你好运。