1. 程式人生 > >樹中兩結點的最低公共祖先(C++實現)

樹中兩結點的最低公共祖先(C++實現)

題目是,輸入兩個樹結點,求它們的最低公共祖先

首先,要說明的是,這是一組題目,根據劍指Offer上所講的,這道題可能會分好幾種情況,因此,如果在面試時候遇到,我們需要和麵試官溝通,而不是一上來就寫程式碼。

1. 如果給定樹是二叉搜尋樹

這裡寫圖片描述

二叉搜尋樹(又叫二叉排序樹),中序遍歷是可以得到有序序列的,因此,我們可以利用這個性質來解題。

假設給定的兩個樹結點為4,1,根據圖示,顯然3為最低公共祖先;
假設給定的兩個樹結點為4,3,根據圖示,顯然3為最低公共祖先;
假設給定的兩個樹結點為2,1,根據圖示,顯然2為最低公共祖先;
假設給定的兩個樹結點為6,8,根據圖示,顯然7為最低公共祖先;
假設給定的兩個樹結點為8,1,根據圖示,顯然5為最低公共祖先;
因此我們發現,公共祖先的值都是在給定兩個樹結點的範圍之內。

所以,我們可以由根結點遍歷,如果當前結點的值比兩結點大,需要到當前結點的左子樹中找,如果當前結點的值比兩結點小,需要到當前結點的右子樹中找,僅噹噹前結點的值在兩結點的範圍內(等於也算)

程式碼比較簡單,如下:

Node* FindLowestCommonAncestor(Node* root, Node* n1, Node* n2)
{
    //引數有一個為空返回NULL
    if (root == NULL || n1 == NULL || n2 == NULL)
        return NULL;

    if (root->data > n1->
data && root->data > n2->data) return FindLowestCommonAncestor(root->left, n1, n2); else if (root->data < n1->data && root->data < n2->data) return FindLowestCommonAncestor(root->right, n1, n2); else return root; }

2. 如果給定樹由指向父節點的指標

這裡寫圖片描述

假設我們給定的結點為G,E,顯然由圖示,最低公共祖先為B。

由於有了指向父結點的指標,因此我們可以由G,E出發,找到最低公共祖先,所以此題其實本質是兩個連結串列的第一個公共結點。

連結串列求交問題不難,有不少方法,比如可以先把G,E到根節點A所經過的所有結點壓棧,此時有兩個序列,
GDBA,EBA,兩棧棧頂相同Pop,最後一個相同的結點即為所求結果,這樣的演算法需要藉助兩個棧(其他類似容器也可以)。

在這裡,我給出一種不需要藉助容器的演算法,分別由G,E遍歷到根節點得到各自的長度,4和3,讓長度長一點的那個連結串列先走一步,比如G先走到D,此時序列為DBA和EBA,同時走,遇到相同的結點即為所求結果。

程式碼如下:

Node* FindLowestCommonAncestor(Node* root, Node* n1, Node* n2)
{
    //引數有一個為空返回NULL
    if (root == NULL || n1 == NULL || n2 == NULL)
        return NULL;

    Node* p1 = n1;
    Node* p2 = n2;
    int count1 = 0; //求n2到根節點的長度
    int count2 = 0; //求n2到根節點的長度
    while (p1 != NULL)
    {
        ++count1;
        p1 = p1->next;
    }

    while (p2 != NULL)
    {
        ++count2;
        p2 = p2->next;
    }

    if (count1 >= count2)
    {
        int n = count1 - count2;
        p1 = n1;
        p2 = n2;
        while (n--)
        {
            p1 = p1->next;
        }
    }
    else
    {
        int n = count2 - count1;
        p1 = n1;
        p2 = n2;
        while (n--)
        {
            p2 = p2->next;
        }
    }

    while (p1 != p2)
    {
        p1 = p1->next;
        p2 = p2->next;
    }
    return p1;
}

3. 如果給定樹是普通的二叉樹

我們往往可能遇到更一般的情況,也就是給定樹只是普通的二叉樹。那麼這道題如何解?

這裡寫圖片描述

我們還是以之前的圖來看,不過需要注意的是此時沒有指向父節點的指標。

3. 1一般解法

其實,我們可以類比第一種情況(二叉搜尋樹)的解法,我們舉例來分析:

假設給定樹結點為D,E,求最低公共結點。我們由根節點A出發,D,E都在A的左子樹中,於是我們遍歷到B,此時D,E分別在B的左右子樹中,B即為所求答案。

假設給定樹結點為E,H,我們由A結點出發,發現E,H都在它的左子樹中,於是我們遍歷到B,發現他們都在B的右子樹中,於是我們遍歷到了E,此時E為所求答案。

因此,我們發現,如果給定的兩個樹結點都在當前結點的左子樹裡面,最低公共祖先必在左子樹中,如果給定的兩個樹結點都在當前結點的右子樹裡面,最低公共祖先必在右子樹中,如果不滿足這兩個條件,當前結點即為最低公共祖先。

思路有了,我們需要完成程式碼

//判斷某個結點在當前樹中
bool IsInTheBinaryTree(Node* root, Node* n)
{
    if (root == NULL || n == NULL)
        return false;
    if (root == n)
        return true;
    bool found = IsInTheBinaryTree(root->left, n);
    if (!found)
        found = IsInTheBinaryTree(root->right, n);
    return found;
}


Node* FindLowestCommonAncestor(Node* root, Node* n1, Node* n2)
{
    //引數有一個為空返回NULL
    if (root == NULL || n1 == NULL || n2 == NULL)
        return NULL;
    //都在左子樹,結果必在左子樹中
    if (IsInTheBinaryTree(root->left, n1) && IsInTheBinaryTree(root->left, n2))
        return FindLowestCommonAncestor(root->left, n1, n2);
    //都在右子樹,結果必在右子樹中
    else if (IsInTheBinaryTree(root->right, n1) && IsInTheBinaryTree(root->right, n2))
        return FindLowestCommonAncestor(root->right, n1, n2);
    else
        return root;
}

3. 2更快的解法

我們已經完成基本功能了,然而我們仔細分析下程式碼發現,我們判斷某個結點在樹中遞迴了一次樹,求公共結點又遞迴了一次樹,這樣子會對某些結點重複遍歷很多次,效率比較低,能否有一個更好的演算法?

為了解決這個問題,我們需要儲存從根節點到兩個給定樹結點的路徑,這樣子由變成了求連結串列的最後公共結點了。還是拿上面的圖舉例:

這裡寫圖片描述

求G,E的最低公共祖先。

由A到G的路徑為:ABDG
由A到E的路徑為:ABE

如果看作是連結串列,我們只需要求最後一個公共交點了,當然藉助其他類似容器也是同樣的道理,這裡我使用vector儲存路徑。

bool GetPath(Node* root, Node* n, vector<Node*>& v)
{
    if (root == NULL || n == NULL)
        return false;
    if (root == n)
    {
        v.push_back(root);
        return true;
    }
    v.push_back(root);

    bool found = false;
    found = GetPath(root->left, n, v);

    if (!found)
        found = GetPath(root->right, n, v);

    if (!found)
        v.pop_back();
    return found;
}



Node* FindLowestCommonAncestor(Node* root, Node* n1, Node* n2)
{
    //引數有一個為空返回NULL
    if (root == NULL || n1 == NULL || n2 == NULL)
        return NULL;
    vector<Node*> v1;
    bool hasPath1 = GetPath(root, n1, v1);
    vector<Node*> v2;
    bool hasPath2 = GetPath(root, n2, v2);

    if (hasPath1 && hasPath2)
    {
        int i = 0;
        while (v1[i] == v2[i])
        {
            ++i;
        }
        return v1[i - 1];
    }
    else
    {
        return NULL;
    }

}