【问题标题】:C: Finding Huffman-coded path of specific leaf in treeC:查找树中特定叶子的霍夫曼编码路径
【发布时间】:2016-09-26 22:59:54
【问题描述】:

我正在尝试编写一个递归函数,该函数在霍夫曼树中定位一个特定的叶子,然后用零和一打印其最短路径(零是向左遍历,一个是向右遍历)。我理解我需要做的事情的逻辑,但我在实际实施它时没有成功。我相信我在这里有一个很好的骨架,但是我缺少的部分是一些更复杂的逻辑来告诉我什么时候应该实际运行 printf 以及什么时候不应该运行(因为这目前只打印每个零和一)。另外,我知道除此之外的其余逻辑都可以正常工作,因为如果您进行正常遍历而不必绘制最短路径,则会找到我正在搜索的每个元素。

我已经尝试在网上查看很多资源,但我找不到解决方案,或者至少我无法正确识别解决方案。我可能已经重写了 50 次或更多次。让我知道你的想法!

void traverse(struct tree *curr, struct tree *cmp)
{
  if (curr == NULL)
  {
    return;
  }

  if (getLeft(curr) == NULL && getRight(curr) == NULL)
  {
    if (curr == cmp)
    {
      return;
    }
  }

  if (getLeft(curr) != NULL)
  {
    printf("0");

    traverse(getLeft(curr), cmp);
  }

  if (getRight(curr) != NULL)
  {
    printf("1");

    traverse(getRight(curr), cmp);
  }
}

对于上下文:cmp 是我们要查找的节点,getLeft()getRight() 分别返回节点的左右子节点,curr 作为 Huffman 树本身的根开始。另外,这个 printf 事情起作用的原因是因为我遍历所有已知的叶子,打印关于叶子的其他信息,然后调用这个遍历方法,然后是一个换行符。

【问题讨论】:

    标签: c recursion tree traversal huffman-code


    【解决方案1】:

    一个可行的解决方案是给每个节点一个parent 指针。这样,一旦找到叶子,就可以从该叶子开始递归遍历树,并在递归调用返回时打印适当的位。

    在这个函数中,首先检查节点是否有父节点(即我们是否在根节点),如果是,则递归调用该节点的父节点的函数;如果没有,请返回。

    在我们递归调用函数的情况下,递归调用后,检查当前节点是否是其父节点的右子节点。如果是,则打印 1;如果没有,打印一个 0。

    在此实现中无需担心反转字符串。

    另一种可能的解决方案是构建字符串并将其传递给递归调用。对于此解决方案,您需要知道树的高度,或者至少您的树可以编码的符号数量,以便您传入一个至少具有该大小的 char 数组,加上一个用于空终止。

    在伪代码中,这看起来像:

    func traverse (cur, cmp, str)
        if cur == null, return
        if cur == cmp
            print str
        if cur.left != null
            traverse(cur.left, cmp, str + "0")
        if cur.right != null
            traverse(cur.right, cmp, str + "1")
    

    这样,您正在构建字符串,并且仅在找到有问题的叶子后才打印它。请注意,我将 cur == cmp 检查移到了 if 语句之外,因为对于 Huffman 代码树中的内部节点,它永远不应该为真。但是,这种方法在查找单个字符的代码方面效率极低,因为它在整个树上执行 DFS。

    【讨论】:

    • 您的第一种方法有效地在堆栈上反向构建字符串信息,并在从递归调用返回时将其反转。除了递归,您可以只使用一个将字符串保存在内存中的循环,这比在堆栈上为每个字符进行递归调用占用的空间要少得多。如果您知道树的最大深度,则可以将字符反向存储,这样您实际上不必在最后进行任何反向操作。
    • 您的第二种方法是如何构建一个表格,将结果保存在每一片叶子上,而不是只在其中一个叶子上打印。
    • @MarkAdler 这个递归公式和循环公式观察到相同的时间复杂度(排除函数调用开销),它在树的最大深度(树编码的字符数)中是线性的。递归的空间复杂度在最大深度上也是线性的,因此对于任何现代机器来说几乎可以忽略不计。
    • @MarkAdler 第二,你是对的,我可以补充一点,该方法是你所说的实现。我还可以提到,如果你将它们存储在哈希表中,table 方法可以让你的常量时间编码摊销。
    • 我是那些关心 O(n) 过程的 n 前面的常数的怪人之一。跨度>
    【解决方案2】:

    有几种解决方案。

    首先,您可以在执行过程中遍历整个树并构建一个代码表。然后使用表,而不是树。这样您就不会浪费时间在整个树中搜索每个代码。当您遍历树时,您会构建一个由 0 和 1 组成的字符串,当您到达叶子时,您会将构建的字符串和叶子中的符号保存在表中。然后扔掉树。这是推荐的方法。

    其次,您的链接可以是双向的。因为你有一个指向叶子的指针,你可以简单地从叶子开始,然后回到根,反向构造 0 和 1 的字符串。

    第三,您可以通过让 traverse 函数返回 true 或 false 来坚持对每一片叶子进行痛苦的树搜​​索。如果它到达所需的叶子,或者如果遍历调用之一返回 true,它将返回 true。然后根据返回 true 的遍历调用,您将打印或保存零或一。这将反向打印路径。如果您将它们以相反的顺序保存在字符串中而不是打印,那么您可以在第一次遍历调用返回时打印字符串。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多