PAT 1043 Is It a Binary Search Tree (25分) 由前序遍歷得到二叉搜尋樹的後序遍歷

無程式碼,非程式發表於2020-06-14

題目

A Binary Search Tree (BST) is recursively defined as a binary tree which has the following properties:

  • The left subtree of a node contains only nodes with keys less than the node's key.
  • The right subtree of a node contains only nodes with keys greater than or equal to the node's key.
  • Both the left and right subtrees must also be binary search trees.

If we swap the left and right subtrees of every node, then the resulting tree is called the Mirror Image of a BST.

Now given a sequence of integer keys, you are supposed to tell if it is the preorder traversal sequence of a BST or the mirror image of a BST.

Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤1000). Then N integer keys are given in the next line. All the numbers in a line are separated by a space.

Output Specification:
For each test case, first print in a line YES if the sequence is the preorder traversal sequence of a BST or the mirror image of a BST, or NO if not. Then if the answer is YES, print in the next line the postorder traversal sequence of that tree. All the numbers in a line must be separated by a space, and there must be no extra space at the end of the line.

Sample Input 1:
7
8 6 5 7 10 8 11
Sample Output 1:
YES
5 7 6 8 11 10 8
Sample Input 2:
7
8 10 11 8 6 7 5
Sample Output 2:
YES
11 8 10 7 5 6 8
Sample Input 3:
7
8 6 8 5 10 9 11
Sample Output 3:
NO

題目解讀

給定一個輸入序列,判斷它是否是一棵二叉搜尋樹或其映象樹的前序遍歷序列,如果是,輸出 YES 以及它的後序遍歷序列;如果不是,輸出 NO。

二叉搜尋樹的定義:

  • 左子樹所有節點的鍵值小於根節點
  • 右子樹所有節點的鍵值大於等於根有點
  • 左子樹和右子樹也必須是二叉搜尋樹(滿足上面兩點)

映象樹:交換原二叉搜尋樹每個節點的左右子樹。所以它滿足:

  • 左子樹所有節點的鍵值大於等於根節點
  • 右子樹所有節點的鍵值小於根有點
  • 左子樹和右子樹也必須滿足上面兩點

首先我們應該明白一個道理:

一般情況下,我們要根據一棵二叉樹的前序遍歷結果和中序遍歷結果才能得到它的後序遍歷結果,但這裡為什麼只需要前序遍歷結果就可以得到後序遍歷結果?

二叉搜尋樹又叫二叉排序樹,由它的定義就知道它本身是有序的,對一棵二叉搜尋樹進行中序遍歷,得到就是一個非降序有序序列,這也可以作為一種排序方法。

所以如果給出的序列的確是一棵二叉搜尋樹的前序遍歷的話,對它進行一次排序就能得到它的中序遍歷結果,前序+中序就能得到後序,所以需要明白這個隱含條件。

思路分析

你可以選擇用給出的輸入構造出一棵二叉搜尋樹,然後再對這棵樹進行前序遍歷,判斷得到的結果是否和輸入一致,如果不一致,那就輸出 NO,如果一致就輸出這這棵樹的後序遍歷結果。

我這裡參考了柳神的程式碼,採用了一種更為簡單的方法。

可以想象一下,根據 前序遍歷結果和中序遍歷結果能得到正確的後序遍歷結果,那,如果給出的前序結果是錯誤的呢(它不是一棵二叉搜尋樹的前序結果),那肯定不能得到正確的後序遍歷結果,假如我用一個vector來儲存在此過程中得到的節點序列,那麼最終這個vector中的節點個數肯定不夠原來輸入的個數(當然前提是演算法正確)。

所以我們可以假設這個輸入序列是對的,然後利用這個輸入序列去得到後序序列並儲存,如果最終得到的結果中節點個數不夠,那說明它不是正確的前序遍歷,否則就直接輸出得到的結果

當然這裡還多了一步,如果把它當作前序遍歷序列不能得到正確結果,我們還要把它當作二叉搜尋樹的映象樹的前序結果再執行一次這個過程,如果這次還不能得到正確結果,那說明它的確是錯誤的前序序列。

接下來我們來研究一下這個演算法怎麼寫:

對於一棵二叉搜尋樹,因為它的有序性,對他進行前序遍歷(根,左子樹,右子樹),得到的序列肯定滿足這個樣子:根節點 所有比它小的節點 所有比它的大的節點

比如下面這棵二叉搜尋樹
在這裡插入圖片描述
它的前序遍歷結果是 8 567 91011,是不是很有特點,我們能根據這個很好的劃分出它的左右子樹:

  • 當前樹根是8,設定左指標i,初始是8的下一個位置,右指標j,初始是當前樹最後一個有效位置
  • i從左往右,掃描前序序列,遇到比根小的就i++,會停在9的位置;
  • j從右往左,掃描前序序列,遇到比根大於或等於的就j--,最後會停在7的位置
  • 這樣一次掃描後,應該滿足 j + 1 == i,對於每一個二叉搜尋樹的前序遍歷都應該滿足這個特點,所以這裡可以作為函式的一個出口,不滿足,就退出
  • 劃分成左右兩部分後,因為我們要得到後序遍歷(左,右,中),所以對做左子樹進行這個處理,對右子樹進行這個處理,把當前樹根加入vector,注意順序不能亂!

從上面可以看出:

  • 這個函式需要兩個引數,一個是當前樹的根,一個是當前樹最後一個節點在前序序列中的位置
  • 每次雙指標掃描結束後,不滿足 i == j + 1 就退出
  • 最小的子樹是隻有一個節點,此時這兩個引數應該相等,所以函式剛進來的時候,判斷一個,如果根的位置 > 最後一個有效位置,就直接退出。

上面的過程是針對於 把輸入序列當作二叉搜尋樹的前序遍歷進行的,如果要把輸入序列當作映象樹的前序遍歷序列呢?很簡單,根據對稱性,只需要把 i 和 j 的掃描時的判斷條件取個反就行了,也就是 > 變成 <=> 變成 <=.

所以你可以寫成兩個函式分別處理這兩種情況,也可以設定一個標誌在函式內部分情況,我選擇第二種,直接看程式碼很清楚。

綜上,總體執行流程為:

  • 把輸入序列當作二叉搜尋樹前序遍歷,執行這個函式,得到後序結果,
  • 如果所得結果中節點個數與輸入一致,直接輸出
  • 如果不一致,把輸入序列當作二叉搜尋樹映象樹的前序遍歷,也就是改變標誌位,再執行一次這個函式,得到後序結果
  • 再判斷所得結果中節點個數是否與輸入一致,一致則輸出,不一致則輸出 NO。

完整程式碼

#include<iostream>
#include<vector>
using namespace std;
// 前序遍歷序列,後序遍歷序列
vector<int> pre, post;
// 是否當作二叉搜尋樹的映象樹處理
bool ismirror = false;

// 當前處理的樹的根節點在前序序列中的下標
// 當前處理的樹最後一個節點在前序序列中的有效位置
void GetPost(int root, int tail) {
    // 最小的樹就一個節點,root=tail
    if (root > tail) return;
    int i = root + 1, j = tail;
    // 當作二叉搜尋樹樹處理
    if (!ismirror) {
        // 左孩子都小於根
        while (i <= tail && pre[i] < pre[root]) i++;
        // 右孩子都大於等於根
        while (j > root && pre[root] <= pre[j]) j--;
    // 當作二叉搜尋樹的映象樹處理
    } else {
        // 左孩子都大於等於根
        while (i <= tail && pre[i] >= pre[root]) i++;
        // 右孩子都小於根
        while (j > root && pre[root] > pre[j]) j--;
    }
    // 不滿足二叉搜尋樹前序遍歷結果特點
    if (i != j + 1) return;
    // 對左子樹執行此操作
    GetPost(root + 1, j);
    // 對右子樹執行此操作
    GetPost(i, tail);
    // 儲存當前根
    post.push_back(pre[root]);
}

int main() {
    int n;
    cin >> n;
    pre.resize(n);
    for (int i = 0; i < n; ++i) cin >> pre[i];
    // 當作前序遍歷結果,嘗試得到後序結果
    GetPost(0, n - 1);
    // 所得結果節點個數錯誤
    if (post.size() != n) {
        // 改變標誌位
        ismirror = true;
        post.clear();
        // 當作映象樹前序遍歷結果,嘗試獲取後序結果
        GetPost(0, n - 1);
    }
    // 所得結果正確
    if (post.size() == n) {
        cout << "YES" << endl;
        cout << post[0];
        for (int i = 1; i < n; ++i) cout << " " << post[i];
    // 所得結果依然錯誤
    } else {
        cout << "NO";
    }
    return 0;
}

相關文章