【发布时间】:2020-06-15 22:30:57
【问题描述】:
我得到一个严格二叉树的后序遍历,并被要求找到它的前序遍历。通常,我会先构建树,然后找到前序遍历。但是,我想知道是否有任何方法可以在不实际构建树的情况下找到前序遍历。
【问题讨论】:
-
您能否举一个您正在处理的数据的示例?
标签: c binary-tree preorder postorder
我得到一个严格二叉树的后序遍历,并被要求找到它的前序遍历。通常,我会先构建树,然后找到前序遍历。但是,我想知道是否有任何方法可以在不实际构建树的情况下找到前序遍历。
【问题讨论】:
标签: c binary-tree preorder postorder
[编辑:我首先在假设给定的后序来自具有严格排序的二叉搜索树的假设下回答了这个问题。 OP 现在指出了我的错误并提供了一个例子。两种算法的基本原理是一样的,但是:找到左右子树的边界在哪里。]
让我们考虑以下完整二叉树的后序遍历,其中每个节点要么是叶子,要么是具有两个叶子的分支:
1, 2, B, 3, 4, D, 5, C, A
我们知道数字是树叶,字母是树枝。我们也知道节点 A 是根,因为它在后序遍历中排在最后。为了重构前序遍历,我们必须存储根,然后递归地考虑左子树,然后是右子树。
但是哪些节点属于左子树,哪些属于右子树?在具有 L 个叶子的完整或 strictly binary tree 中,有 N = 2·L - 1 个节点。所以在存储了根之后,我们从右边遍历剩余的子数组,并跟踪节点的数量N和叶子的数量L。当条件 N = 2·L − 1 为真时,我们停止。我们看到的一切都属于右子树,其余的都属于左子树。
所以:
int is_leaf(int c)
{
return !isalpha(c); // non-alphas are leaves
}
void reconst(int **pre, const int *post, int lo, int hi)
{
printf("%d :: %d\n", lo, hi);
if (lo < hi) {
int k = --hi; // will be boundary between l/r
int root = post[k];
int leaves = 0;
int nodes = 0;
while (k > lo && nodes != 2 * leaves - 1) {
if (is_leaf(post[k - 1])) leaves++;
nodes++;
k--;
}
*(*pre)++ = root;
reconst(pre, post, lo, k);
reconst(pre, post, k, hi);
}
}
这样称呼它:
int post[] = {'1', '2', 'B', '3', '4', 'D', '5', 'C', 'A'};
int n = 9;
int pre[9];
int *p = pre;
int i;
reconst(&p, post, 0, n);
for (i = 0; i < n; i++) {
printf("%c ", pre[i]);
}
puts("pre");
上面的代码依赖于几件事: (a) prearray 必须与 post 数组一样大,以保存重构的预购。 (b) 输入必须格式正确。该算法依赖于找到正确的完整子树。 (计数器被防止超出下限,但仅此而已。)
[试图从没有重复值的二叉搜索树的后序中找到前序的原始帖子,即严格排序。很好的答案,但因为误解了要求,而不是 OP 想要的。对此感到抱歉。]
假设你得到一个像这样的后序遍历:
3, 1, 7, 9, 8, 5
您知道顶部节点是 (5),所有较小的节点 (3, 1) 都在左分支,所有较大的节点 (7, 8, 9) 都在右分支。顶部节点在前序遍历中首先进入。这样做,然后递归表示左分支的子数组,然后是右分支。
这是一个执行此操作的函数:
void reconst(int **pre, const int *post, int lo, int hi)
{
if (lo < hi) {
int k = --hi; // k will be the boundary between l/r
int parent = post[k]; // remove parent from this subarray
// find boundary between left and right branches
while (k > lo && post[k - 1] > parent) k--;
*(*pre)++ = parent; // write parent to pre-order array
reconst(pre, post, lo, k); // do the left subarray
reconst(pre, post, k, hi); // do the right subarray
}
}
pre 数组通过指向指针的指针填充:顶级指针跟踪pre 数组中的位置,二级指针访问底层数组。 (你可以传递一个数组和一个索引,如果这太巴洛克,你可以提前。)
这样调用函数:
int post[] = {3, 1, 7, 9, 8, 5};
int n = 6;
int pre[6];
int *p = pre;
int i;
reconst(&p, post, 0, n);
for (i = 0; i < n; i++) {
printf("%d ", pre[i]);
}
puts("pre");
当然,保存前序数据的数组必须和后序数组一样大。从 post-oder 数据重建树的代码看起来非常相似,所以我不知道这是否真的合格。
【讨论】:
如果有能力重复读取原始的后序表示,则可以使用 O(N²) 时间但恒定空间(少量计数器)将后序遍历转换为前序遍历。按顺序检查源的每个字符。如果它是一个节点,则丢弃它。如果它是叶子,则将“嵌套”计数器设置为零并检查后续字符,为每个叶子增加嵌套计数器并为每个节点减少它。当到达源文本的末尾或值变为负数时停止。输出那里的节点,然后返回找到的叶节点,以与刚才所做的相反的方式递增和递减计数器。
计数器达到零的每个位置都将立即跟随一个节点(如果没有,这意味着计数器在到达当前位置之前已经变为负数)。输出那个节点。一旦计数器到达一开始的叶节点,输出它并丢弃它,因为它和它之前的任何东西都不再需要了。
这种方法不会像其他方法那样快,但它需要最少的内存,这可能有利于某些用途。
【讨论】:
如果您假设任何二叉树,而不是二叉搜索树,那么我相信不可能获得唯一的预排序序列。在我看来,原因是您无法知道后序序列中的第二个、第三个等节点是左子树的根还是右子树的根。如果树是二叉搜索树,这很清楚,因为顺序会告诉您节点是在左子树中还是在右子树中。
如果您只是想要一个与给出后序序列的树相对应的前序序列,那么您可以从左侧假设一棵不平衡的树。因此,简单地反转后序序列是从左侧不平衡的树的前序序列。
例如,如果您的后序序列是"abcd",那么"dcba" 是树的有效前序序列:
d
/
c
/
b
/
a
也许不是你想要的,但我认为这回答了你的说法。
【讨论】: