【问题标题】:Iterative postorder traversal breaks at root node of tree树的根节点处的迭代后序遍历中断
【发布时间】:2015-12-22 10:18:45
【问题描述】:

我实现了一种算法,用于迭代地打印二叉树的后序遍历。整个算法都可以工作,只是它在到达树根时进入无限循环。

有人能指出我正确的方向吗?我已经被这个问题困扰了 2 天了。

void postorder_nonrec_2(treenode *root)
{
    stack_t *s;
    stack_init(&s, 100);
    treenode *temp = root;

    while(1)
    {
        while(root)
        {
            push(s, root);
            root = root -> left;
        }

        if(stack_isEmpty(s))
            break;

        if(!top(s) -> right)
        {
            root = pop(s);
            printf("%d ", root -> data);

            if(root == top(s) -> left)
            {
                root = top(s) -> right;
            }
            else if(root == top(s) -> right)
            {
                printf("%d ", pop(s) -> data);

                root = NULL;
            }
        }
        else
        {
            root = top(s) -> right;
        }

    }
}

【问题讨论】:

  • 如果您唯一的终止子句是“is_empty”,则显示“is_empty”的作用,否则我们无法判断......否则构造是无限循环的。此外,我们需要验证树是否正确形成。格式错误的树会导致正确的算法永远循环。
  • stack_isEmpty(stack *s) 如果堆栈为空,则返回 true。
  • 这似乎很明显,但是,如果堆栈是问题呢?从这里我们看不到。据我所知,除非正确触发,否则循环将永远持续下去。换句话说,你有没有使用调试器来查看程序卡在哪里循环?不是在调用stack_isEmpty吗?如果不是,它可能在上层循环中寻找一个最左边的节点,这暗示树本身可能是问题,但是为什么不推送泛滥内存(创建了巨大的堆栈)。如果正在调用 stack_isEmpty,那么为什么它永远不会返回 true?这些是我看到的唯一可能性
  • stack_isEmpty() 已经过测试和调试。它没有问题。我所需要的只是一种方法来确定当前元素是根节点,并且已经遍历了整个树。然后我可以从堆栈中弹出根节点,打印它并自动停止循环。
  • 这是函数:int stack_isEmpty(stack_t *stack) { if(stack -> top == -1) return 1; return 0; }

标签: c algorithm data-structures binary-tree postorder


【解决方案1】:

也许对于您使用的测试用例,只有根存在无限循环,但我认为无限循环也可能发生在树的其他地方,具体取决于具体的树。

我认为问题在于,当右侧存在孩子时,您没有正确地继续弹出堆栈。

考虑一个简单的例子,我们有一个根节点 1,有一个左孩子 0 和一个右孩子 2,并假设 2 有一个名为 3 的右孩子。

在第一个循环中,我们将 1 和 0 压入堆栈。那么 0 没有左孩子,所以 root 变为 null。堆栈不是空的,所以我们继续。 0在栈顶,没有右孩子,所以我们进入if语句的第一个分支。然后我们打印出 0,因为 0 是 1 的左孩子——1 现在是栈顶——根变成了 2。

此时我们回到顶部。 2 是根并被推入堆栈。 2 没有左孩子,所以根为空。堆栈不为空。 2 在堆栈的顶部。它有一个右孩子,所以我们进入 if 语句的第二个分支。这使得 3 成为根。

我们回到外循环的顶部。 3 是根并被推入堆栈。 3 没有左孩子,所以根为空。堆栈不为空。 3 没有右孩子,所以我们进入 if 语句的第一个分支。我们打印出 3。然后因为 3 是 2 的右孩子——现在 2 在栈顶——我们将 2 从栈中弹出,打印出 2,然后根变为 null。

我们回到循环的顶部。根已经为空,所以没有任何东西被压入堆栈。堆栈不为空。 1 位于堆栈的顶部。此时正确的做法是从堆栈中弹出 1,因为我们已经处理了它的右孩子;然而,1 在栈顶并且确实有一个右孩子,所以我们进入 if 语句的第二个分支,2 成为根。情况和前两段完全一样,1 是栈上唯一的元素,2 是根,所以我们得到一个无限循环回到那里。

如果我们更改示例,使 3 也有一个名为 4 的右孩子,那么,如果我没看错,我们将永远不会打印出 2,而是会循环打印出 4 和 3。

要纠正此问题,只要您正在处理的元素是堆栈顶部的右子元素,就应该继续弹出堆栈。我还没有编译或测试过这个,但我认为写这样的东西会起作用

    if (!top(s) -> right)
    {
        root = pop(s);
        printf("%d ", root -> data);

        while (!stack_isEmpty(s) && root == top(s) -> right)
        {
            root = pop(s);
            printf("%d ", root -> data);
        }
        if (!stack_isEmpty(s) && root == top(s) -> left)
        {
            // checking root == top(s) -> left is probably redundant,
            // since the code is structured so that root is either
            // a child of top(s) or null if the stack is not empty
            root = top(s) -> right;
        }
        else
        {
            root = NULL;
            // could actually break out of outer loop here, but
            // to be more consistent with code in the question
        }
    }

【讨论】:

  • 巨大的努力和精确的解决方案。我让你的代码工作,它确实很有吸引力。然后我按照您的建议切断了所有多余的部分以保持代码简短,并且它有效!谢谢!
【解决方案2】:

发布此答案以提供@Evan VanderZee 建议的解决方案的完整代码

void postorder_nonrec_2(treenode *root)
{
    stack_t *s;
    stack_init(&s, 100);


    while(1)
    {
        while(root)
        {
            push(s, root);
            root = root -> left;
        }

        if(stack_isEmpty(s))
            break;

        if (!top(s) -> right)
        {
            root = pop(s);
            printf("%d ", root -> data);

            while (!stack_isEmpty(s) && root == top(s) -> right)
            {
                root = pop(s);
                printf("%d ", root -> data);
            }

            root = NULL;
        }
        else
        {
            root = top(s) -> right;
        }
    }
}

【讨论】:

    猜你喜欢
    • 2021-10-30
    • 2012-03-21
    • 1970-01-01
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-11
    相关资源
    最近更新 更多