13

プレオーダートラバーサルから(配列として)ツリーを構築する方法があることを私は知っています。より一般的な質問は、順序どおりのトラバーサルと事前順序のトラバーサルを前提として、それを構築することです。この場合、インオーダートラバーサルは冗長ですが、間違いなく簡単になります。誰かが注文後のトラバーサルのためにそれを行う方法を教えてもらえますか?反復ソリューションと再帰ソリューションの両方が必要です。

スタックを使って繰り返しやってみましたが、ロジックがまったくうまくいかなかったので、ひどい散らかった木になりました。同じことが再帰にも当てはまりました。

4

5 に答える 5

28

BSTのポストオーダートラバーサルからの配列がある場合、ルートが配列の最後の要素であることがわかります。ルートの左側の子は配列の最初の部分を占め、ルートよりも小さいエントリで構成されます。次に、ルートよりも大きい要素で構成される右の子に従います。(両方の子供が空の場合があります)。

________________________________
|             |              |R|
--------------------------------
 left child     right child   root

したがって、主な問題は、左の子が終了し、右の子が開始するポイントを見つけることです。

両方の子も注文後の走査から取得されるため、子の構築は同じ方法で再帰的に行われます。

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;
}
于 2012-10-31T22:08:43.983 に答える
12

順序通りのトラバーサルは本当に必要ありません。ポストオーダートラバーサルのみを指定してツリーを再構築する簡単な方法があります。

  1. 入力配列の最後の要素を取得します。これがルートです。
  2. 残りの入力配列をループして、要素がルートよりも小さいものから大きいものに変化するポイントを探します。その時点で入力配列を分割します。これは、二分探索アルゴリズムを使用して実行することもできます。
  3. これらの2つのサブ配列からサブツリーを再帰的に再構築します。

これは、スタックを使用して再帰的または反復的に簡単に実行でき、2つのインデックスを使用して、実際に配列を分割するのではなく、現在のサブ配列の開始と終了を示すことができます。

于 2012-10-31T22:22:20.117 に答える
6

注文後のトラバーサルは次のようになります。

visit left
visit right
print current.

そしてこのように順番に:

visit left
print current
visit right

例を見てみましょう:

        7
     /     \
    3      10
   / \     / \
  2   5   9   12
             /
            11

順序は次のとおりです。2 3 5 7 9 10 11 12

後注文は次のとおりです。2 5 3 9 11 12 10 7

ポストオーダー配列を逆の順序で繰り返し、その値がある場所の周りでインオーダー配列を分割し続けます。これを再帰的に実行すると、それがあなたのツリーになります。例えば:

current = 7, split inorder at 7: 2 3 5 | 9 10 11 12

見覚えがあります?左側にあるのは左側のサブツリーで、右側にあるのは右側のサブツリーです。BST構造に関する限り、疑似ランダムな順序になっています。しかし、あなたは今あなたのルートが何であるかを知っています。次に、2つの半分について同じことを行います。ポストオーダートラバーサルの左半分から要素の最初の出現(最後から)を見つけます。それは3になります。3を分割します。

current = 3, split inorder at 3: 2 | 5 ...

これまでのところ、ツリーは次のようになっています。

   7
 /
3

これは、ポストオーダートラバーサルの値は常にその子が表示された後に表示され、インオーダートラバーサルの値はその子の値の間に表示されるという事実に基づいています。

于 2012-10-31T22:05:13.360 に答える
1

何もループしないでください。最後の要素はあなたのルートです。次に、配列を逆方向に取得し、BSTの挿入規則に従います。

eg:-   
given just the postorder -- 2 5 3 9 11 12 10 7



        7
         \
          10

        ----
        7
         \
          10
           \
            12
         -----
        7
         \
          10
           \
            12
           /
          11
         -------
        7
         \
          10
         /  \
        9    12
           /
          11
         --------
        7
      /  \
     3    10
    / \  /  \
   2   5 9  12
           /
          11
于 2017-10-12T16:17:42.687 に答える
1

いずれの回答も、動作するコードを示したり、時間計算量の分析を提供したりすることはなく、ハンマーの素晴らしい答えではありません。手振りが気になるので、もう少し正式な商売に取り掛かりましょう。

PythonでのHammerのソリューション:

def from_postorder(nodes: Sequence[int]) -> BinaryTree[int]:
    def build_subtree(subtree_nodes: Sequence[int]) -> BinaryTree[int]:
        if not subtree_nodes:
            return None

        n = len(subtree_nodes)
        # Locates the insertion point for x to maintain sorted order.
        # This is the first element greater than root.
        x = bisect.bisect_left(subtree_nodes, subtree_nodes[-1], hi=n - 1)

        root = BinaryTree(subtree_nodes[-1])
        root.left = build_subtree(subtree_nodes[:x])
        # slice returns empty list if end is <= start
        root.right = build_subtree(subtree_nodes[x:n - 1])

        return root

    return build_subtree(nodes)

各ステップで、二分探索にはlog(n)時間がかかり、問題を1つの要素(ルート)で減らします。したがって、全体的な時間計算量はnlog(n)です。

代替ソリューション:

2つのデータ構造を作成します。1つはBSTのインオーダートラバーサルで、もう1つはポストオーダートラバーサルシーケンスのインデックスへの各ノードのマッピングです。

サブツリーを形成するノードの特定の範囲では、ルートはポストオーダートラバーサルの最後に表示されるものです。ルートを効率的に見つけるために、前に作成したマッピングを使用して各ノードをそのインデックスにマッピングしてから、最大値を見つけます。

ルートを見つけたら、インオーダートラバーサルシーケンスでバイナリ検索を実行します。指定された範囲の下限からルートの左側にある要素はその左側のサブツリーを形成し、ルートの右側から範囲の右側にある要素はその右側のサブツリーを形成します。左右のサブツリーで繰り返します。

言い換えると、ポストオーダートラバーサルシーケンスを使用してルートを検索し、インオーダートラバーサルシーケンスを使用して左右のサブツリーを検索します。

時間計算量:各ステップで、ルートを見つけるのにO(n)時間がかかります。二分探索にはlog(n)時間がかかります。また、問題を2つのほぼ等しいサブ問題に分割します(完全なBSTの最悪のケース)。したがって、は、マスター定理を使用してT(n) <= 2 . T(n/2) + O(n) + log(n) = T(n/2) + O(n)私たちに与えます。O(n log(n))

def from_postorder_2(nodes: Sequence[int]) -> BinaryTree[int]:
    inorder: Sequence[int] = sorted(nodes)
    index_map: Mapping[int, int] = dict([(x, i) for i, x in enumerate(nodes)])

    # The indices refer to the inorder traversal sequence
    def build_subtree(lo: int, hi: int) -> BinaryTree[int]:
        if hi <= lo:
            return None
        elif hi - lo == 1:
            return BinaryTree(inorder[lo])

        root = max(map(lambda i: index_map[inorder[i]], range(lo, hi)))
        root_node = BinaryTree(nodes[root])
        x = bisect.bisect_left(inorder, root_node.val, lo, hi)
        root_node.left = build_subtree(lo, x)
        root_node.right = build_subtree(x + 1, hi)

        return root_node

    return build_subtree(0, len(nodes))
于 2020-08-30T00:21:44.320 に答える