1. 程式人生 > >【PAT 甲級】1151 LCA in a Binary Tree (30 分)

【PAT 甲級】1151 LCA in a Binary Tree (30 分)

lca osi 我不知道 pri lin nes %d sed father

題目描述

The lowest common ancestor (LCA) of two nodes U and V in a tree is the deepest node that has both U and V as descendants.Given any two nodes in a binary tree, you are supposed to find their LCA.


最小共同祖先(LCA)是一棵樹中兩個節點U和V最深的那個公共父節點。要求給一棵樹,以及兩個節點,請你找出它們的LCA。

輸入說明

Each input file contains one test case. For each case, the first line gives two positive integers: M (≤ 1,000), the number of pairs of nodes to be tested; and N (≤ 10,000), the number of keys in the binary tree, respectively. In each of the following two lines, N distinct integers are given as the inorder and preorder traversal sequences of the binary tree, respectively. It is guaranteed that the binary tree can be uniquely determined by the input sequences. Then M lines follow, each contains a pair of integer keys U and V. All the keys are in the range of int.


每個輸入包含一個測試用例。
在每個測試用例中,第一行給定兩個正整數M和N。M(≤ 1,000)是要查詢的節點對數,N (≤ 10,000)是這個二叉樹的節點總數。
在這之後的兩行分別給定為樹的中序遍歷先序遍歷。可以保證的是通過這兩個序列可以唯一確定一棵二叉樹。
接下來的M行為需要查詢LCA的M對節點。
樹中節點的值皆為整數。

輸出說明

For each given pair of U and V, print in a line LCA of U and V is A. If the LCA is found and A is the key. But if A is one of U and V, print X is an ancestor of Y

. where X is A and Y is the other node. If U or V is not found in the binary tree, print in a line ERROR: U is not found. or ERROR: V is not found. or ERROR: U and V are not found..


對於給出的一對節點U和V,如果能找到它們的LCA是A,輸出:LCA of U and V is A;如果A是U和V之間的某一個,輸出:X is an ancestor of Y,其中XA,而Y代表另外一個節點;
如果U和V沒有出現在樹中,視情況而定輸出:

  • ERROR: U is not found.
  • ERROR: V is not found.
  • ERROR: U and V are not found.

輸入示例

6 8
7 2 3 4 6 5 1 8
5 3 7 2 6 4 8 1
2 6
8 1
7 9
12 -3
0 8
99 99

輸出示例

LCA of 2 and 6 is 3.
8 is an ancestor of 1.
ERROR: 9 is not found.
ERROR: 12 and -3 are not found.
ERROR: 0 is not found.
ERROR: 99 and 99 are not found.

算法

我記得第一次考PAT看到這題時,我很愚蠢地用了以下算法:

  1. 通過中序遍歷和先序遍歷先建了一棵樹;
  2. 聲明兩個向量Upath和Vpath分別存放兩個路徑。然後在樹中找到U和V的位置,並將路徑上的節點依次存入向量;
  3. 從前往後同時遍歷兩個向量,理論上來說,第一個分叉點是U和V的LCA,當然還夾雜著其中一個節點是否為另一節點的祖先這樣的判斷。

當然在機考上這題我沒有寫完,只悲催地得了9分,印象深刻。
後來過了半年,第二次準備考PAT的前一個月時,我刷到這題,還是復制著當時的想法,這次寫出來了,29分。我不知道最後1分扣在哪裏,很難想出來。寫出代碼用時40分鐘吧,因為在場下,心平氣和的。之後考完18年冬季的PAT後再刷Leet-Code的時候遇到這一題,這次不知咋的,可能是刷的題目多了,我沒有這麽愚蠢了。下面先貼出那個29分的“愚蠢”版

“愚蠢”版

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

typedef struct node
{
    int var;
    struct node *left, *right, *father;
}tree;

int M, N, FindFlag;
tree *T;
vector<int> inorder, preorder;
tree* createTree(tree*, int, int, int, int);
void findLCA(int U, int V);
void findX(int x, tree *p, tree *&Node);

int main() {
    //freopen("/Users/shayue/Desktop/stdin.txt", "r", stdin);
    cin >> M >> N;

    int tmp;
    for(int i = 0; i< N; i++)
    {
        cin >> tmp;
        inorder.push_back(tmp);
    }

    for(int i = 0; i< N; i++)
    {
        cin >> tmp;
        preorder.push_back(tmp);
    }

    int leftOfIn, rightOfIn, leftOfPre, rightOfPre;
    leftOfIn = leftOfPre = 0;
    rightOfIn = rightOfPre = N - 1;
    T = createTree(T, leftOfIn, rightOfIn, leftOfPre, rightOfPre);
    T->father = NULL;

    int U, V;
    int UNotFound, VNotFound;
    for(int i = 0; i < M; i++)
    {
        cin >> U >> V;
        UNotFound = VNotFound = 0;          //標記為0時表明該數不在數組中
                // 下面代碼先判斷U和V節點是不是在這顆樹中
        for(int i = 0; i < preorder.size(); i++)
        {
            if(preorder[i] == U)
                UNotFound = 1;
            if(preorder[i] == V)
                VNotFound = 1;
        }

        if(UNotFound == 0 && VNotFound == 0)
            printf("ERROR: %d and %d are not found.\n", U, V);
        else if(UNotFound == 0)
            printf("ERROR: %d is not found.\n", U);
        else if(VNotFound == 0)
            printf("ERROR: %d is not found.\n", V);
        else
                        // 兩個節點都在樹中時,進入函數
            findLCA(U, V);
    }
    return 0;
}

tree* createTree(tree* T, int leftOfIn, int rightOfIn, int leftOfPre, int rightOfPre)
{
    int root = preorder[leftOfPre];
    if(leftOfIn < rightOfIn && leftOfPre < rightOfPre)
    {
        int root = preorder[leftOfPre], i;
        T = new tree();
        T->var = root;
        T->left = T->right = NULL;
        leftOfPre = leftOfPre + 1;
                //找到root在中序序列中的下標                                   
        for(i = leftOfIn; i < N && inorder[i] != root; i++);                    
        int differ = i - leftOfIn;
        T->left = createTree(T->left, leftOfIn, i-1, leftOfPre, leftOfPre+differ-1);
        if(T->left != NULL)
            T->left->father = T;
        T->right = createTree(T->right, i+1, rightOfIn, leftOfPre+differ, rightOfPre);
        if(T->right != NULL)
            T->right->father = T;
    }else if(leftOfIn == rightOfIn && leftOfPre == rightOfPre)
    {
        T = new tree();
        T->var = root;
        T->left = T->right = NULL;
    }
    return T;
}

void findLCA(int U, int V){
    deque<int> UPath, VPath;
    tree *UNode, *VNode;
    FindFlag = 0;
    findX(U, T, UNode);
    FindFlag = 0;
    findX(V, T, VNode);
        // UNode和VNode指向U和V節點在樹中的位置,根據father指針找到父節點
    while(UNode != NULL)
    {
        UPath.push_front(UNode->var);
        UNode = UNode->father;
    }
    while(VNode != NULL)
    {
        VPath.push_front(VNode->var);
        VNode = VNode->father;
    }
    int Usize = UPath.size();
    int Vsize = VPath.size();
    if(U == V)
    {
        printf("LCA of %d and %d is %d.\n", U, V, UPath[Usize-2]);
        return;
    }

    int i, j, flag = 0;
    for(i = 0, j = 0; i < Usize && j < Vsize; i++, j++)
    {
        if(UPath[i] != VPath[j])
        {
            flag = 1;
            break;
        }
    }

    if(flag == 0)           //自然而然地走完
    {
        if(i == Usize)
            printf("%d is an ancestor of %d.\n", U, V);
        if(j == Vsize)
            printf("%d is an ancestor of %d.\n", V, U);
    }else
        printf("LCA of %d and %d is %d.\n", U, V, UPath[i-1]);
}

void findX(int x, tree *p, tree *&Node)
{
    if(p)
    {
        if(x == p->var){
            Node = p;
            FindFlag = 1;
            return;
        }
        else
        {
            findX(x, p->left, Node);
            if(FindFlag == 1)
                return;
            findX(x, p->right, Node);
            if(FindFlag == 1)
                return;
        }
    }
}

讓我再看一遍這個代碼是痛苦的,我都不知道我居然能想出這麽多參數來??,包括結構體中增加的一個father指針,指向當前節點的父節點用來尋找路徑。

改進

已經給出了中序遍歷序列和先序遍歷序列了,根本不用建樹。

  1. 確定U和V在中序序列中的下標位置,視情況而定輸出
  2. U和V都在的情況下,先根據先序遍歷序列確定當前尋找範圍(數組)內的根節點
  3. 確定根節點在中序遍歷中的位置
  4. 比較根節點以及U、V在中序序列中的下標位置,比如剛好根節點在U、V中間,則該根節點便為U、V的LCA;或者根節點與U、V一點重合;或者U和V都在根節點的一側,此時要縮小範圍,重復執行2、3兩步。

讓我偷懶一次,我就把Leet-Code上面的代碼貼上來了。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* lowestCommonAncestor( vector<TreeNode*> &pre_order, vector<TreeNode*> &in_order, TreeNode* p, TreeNode* q) {
        /*** 利用前序遍歷和中序遍歷確定p和q的公共祖先  ***/
        
        // 1.找到p、q兩點在中序遍歷中的位置,當然Leet-Code這裏默認p、q都在樹中
        int p_index = -1, q_index = -1;
        for (int i = 0; i < in_order.size(); i++)
        {
            if (p == in_order[i])
                p_index = i;
            if (q == in_order[i])
                q_index = i;
            if (p_index != -1 && q_index != -1)
                break;
        }
        
/***
        //判斷p、q是否都在樹中
        if (p_index == -1 && q_index == -1)
        else if(p_index == -1)
        else
***/
        // 2.叠代:找到當前根節點在中序遍歷中的下標,根據p、q兩點相對於根節點的位置進行判斷
        int left_pre = 0, right_pre = in_order.size() - 1;
        int left_in = left_pre, right_in = right_pre;
        while (true)
        {
            // R是當前根節點
            TreeNode *R = pre_order[left_pre];
            // R_index是根節點在中序遍歷序列中的下標
            int R_index;
            for(R_index =left_in; in_order[R_index] != R && R_index <= right_in; R_index++);
            if ((p_index - R_index) * (q_index - R_index) <= 0)
                return in_order[R_index];
            else
            {
                // p_index和q_index都在左手邊
                if (p_index < R_index)
                {
                    right_pre = R_index - left_in + left_pre;
                    left_pre += 1;
                    right_in = R_index - 1;
                }
                // p_index和q_index都在右手邊
                else
                {
                    left_pre = R_index - left_in + left_pre + 1;
                    left_in = R_index + 1;
                }
            }
        }
    }
};

【PAT 甲級】1151 LCA in a Binary Tree (30 分)