如何在后序遍历中构造BST

Sex*_*ast 13 algorithm recursion binary-tree binary-search-tree

我知道有一些方法可以从预先遍序遍历(作为数组)构造树.考虑到有序和预订遍历,更常见的问题是构造它.在这种情况下,虽然顺序遍历是多余的,但它确实使事情变得更容易.任何人都可以告诉我如何进行后期遍历?迭代和递归解决方案都是必需的.

我尝试使用堆栈迭代地执行它,但根本无法正确地获得逻辑,因此得到了一个可怕的凌乱的树.同样去递归.

Dan*_*her 26

如果你有一个来自BST的后序遍历的数组,你知道根是数组的最后一个元素.根的左子节点占据数组的第一部分,由小于根的条目组成.然后是正确的孩子,由比根大的元素组成.(两个孩子都可能是空的).

________________________________
|             |              |R|
--------------------------------
 left child     right child   root
Run Code Online (Sandbox Code Playgroud)

因此,主要问题是找到左边孩子结束和右边开始的点.

这两个孩子也是从他们的后序遍历中获得的,因此以递归方式以相同的方式构建它们.

BST fromPostOrder(value[] nodes) {
    // No nodes, no tree
    if (nodes == null) return null;
    return recursiveFromPostOrder(nodes, 0,  nodes.length - 1);
}

// Construct a BST from a segment of the nodes array
// That segment is assumed to be the post-order traversal of some subtree
private BST recursiveFromPostOrder(value[] nodes, 
                                   int leftIndex, int rightIndex) {
    // Empty segment -> empty tree
    if (rightIndex < leftIndex) return null;
    // single node -> single element tree
    if (rightIndex == leftIndex) return new BST(nodes[leftIndex]);

    // It's a post-order traversal, so the root of the tree 
    // is in the last position
    value rootval = nodes[rightIndex];

    // Construct the root node, the left and right subtrees are then 
    // constructed in recursive calls, after finding their extent
    BST root = new BST(rootval);

    // It's supposed to be the post-order traversal of a BST, so
    // * left child comes first
    // * all values in the left child are smaller than the root value
    // * all values in the right child are larger than the root value
    // Hence we find the last index in the range [leftIndex .. rightIndex-1]
    // that holds a value smaller than rootval
    int leftLast = findLastSmaller(nodes, leftIndex, rightIndex-1, rootval);

    // The left child occupies the segment [leftIndex .. leftLast]
    // (may be empty) and that segment is the post-order traversal of it
    root.left = recursiveFromPostOrder(nodes, leftIndex, leftLast);

    // The right child occupies the segment [leftLast+1 .. rightIndex-1]
    // (may be empty) and that segment is the post-order traversal of it
    root.right = recursiveFromPostOrder(nodes, leftLast + 1, rightIndex-1);

    // Both children constructed and linked to the root, done.
    return root;
}

// find the last index of a value smaller than cut in a segment of the array
// using binary search
// supposes that the segment contains the concatenation of the post-order
// traversals of the left and right subtrees of a node with value cut,
// in particular, that the first (possibly empty) part of the segment contains
// only values < cut, and the second (possibly empty) part only values > cut
private int findLastSmaller(value[] nodes, int first, int last, value cut) {

    // If the segment is empty, or the first value is larger than cut,
    // by the assumptions, there is no value smaller than cut in the segment,
    // return the position one before the start of the segment
    if (last < first || nodes[first] > cut) return first - 1;

    int low = first, high = last, mid;

    // binary search for the last index of a value < cut
    // invariants: nodes[low] < cut 
    //             (since cut is the root value and a BST has no dupes)
    // and nodes[high] > cut, or (nodes[high] < cut < nodes[high+1]), or
    // nodes[high] < cut and high == last, the latter two cases mean that
    // high is the last index in the segment holding a value < cut
    while (low < high && nodes[high] > cut) {

        // check the middle of the segment
        // In the case high == low+1 and nodes[low] < cut < nodes[high]
        // we'd make no progress if we chose mid = (low+high)/2, since that
        // would then be mid = low, so we round the index up instead of down
        mid = low + (high-low+1)/2;

        // The choice of mid guarantees low < mid <= high, so whichever
        // case applies, we will either set low to a strictly greater index
        // or high to a strictly smaller one, hence we won't become stuck.
        if (nodes[mid] > cut) {
            // The last index of a value < cut is in the first half
            // of the range under consideration, so reduce the upper
            // limit of that. Since we excluded mid as a possible
            // last index, the upper limit becomes mid-1
            high = mid-1;
        } else {
            // nodes[mid] < cut, so the last index with a value < cut is
            // in the range [mid .. high]
            low = mid;
        }
    }
    // now either low == high or nodes[high] < cut and high is the result
    // in either case by the loop invariants
    return high;
}
Run Code Online (Sandbox Code Playgroud)

  • @MohitJain`O(n*log n)`.对于每个节点,我们需要一个复杂度为"O(log n)"的二进制搜索[更尖锐的边界是"O(log t)",其中`t`是子树的大小,其根是我们当前的节点处理,但不会改变复杂性,但它只产生一个常数因子]. (2认同)

ham*_*mar 11

你真的不需要inorder遍历.只给出了后序遍历,有一种简单的方法来重建树:

  1. 获取输入数组中的最后一个元素.这是根.
  2. 循环遍历剩余的输入数组,寻找元素从小于根变为更大的点.在那一点拆分输入数组.这也可以使用二进制搜索算法来完成.
  3. 从这两个子阵列递归地重建子树.

这可以通过递归或迭代使用堆栈轻松完成,并且您可以使用两个索引来指示当前子数组的开始和结束,而不是实际拆分数组.


IVl*_*lad 6

后序遍历如下:

visit left
visit right
print current.
Run Code Online (Sandbox Code Playgroud)

并且像这样:

visit left
print current
visit right
Run Code Online (Sandbox Code Playgroud)

我们来举个例子:

        7
     /     \
    3      10
   / \     / \
  2   5   9   12
             /
            11
Run Code Online (Sandbox Code Playgroud)

顺序是: 2 3 5 7 9 10 11 12

后序是: 2 5 3 9 11 12 10 7

以相反的顺序迭代后序数组并继续将inorder数组拆分为该值的位置.递归地执行此操作,这将是您的树.例如:

current = 7, split inorder at 7: 2 3 5 | 9 10 11 12
Run Code Online (Sandbox Code Playgroud)

看起来熟悉?左边的子树是左子树,右边的是右子树,就BST结构而言是伪随机顺序.但是,您现在知道您的根是什么.现在对两半做同样的事情.在后序遍历中查找左半部分中元素的第一个匹配项(从末尾开始).这将是3.分裂3左右:

current = 3, split inorder at 3: 2 | 5 ...
Run Code Online (Sandbox Code Playgroud)

所以你知道你的树到目前为止看起来像这样:

   7
 /
3
Run Code Online (Sandbox Code Playgroud)

这是基于这样的事实:后序遍历中的值将始终在其子项出现后出现,并且inorder遍历中的值将出现在其子值之间.