1. 程式人生 > >九度OJ-題目1509:樹中兩個結點的最低公共祖先

九度OJ-題目1509:樹中兩個結點的最低公共祖先

題目連結地址:

題目描述:
給定一棵樹,同時給出樹中的兩個結點,求它們的最低公共祖先。

輸入:
輸入可能包含多個測試樣例。
對於每個測試案例,輸入的第一行為一個數n(0<n<1000),代表測試樣例的個數。
其中每個測試樣例包括兩行,第一行為一個二叉樹的先序遍歷序列,其中左右子樹若為空則用0代替,其中二叉樹的結點個數node_num<10000。
第二行為樹中的兩個結點的值m1與m2(0<m1,m2<10000)。

輸出:
對應每個測試案例,
輸出給定的樹中兩個結點的最低公共祖先結點的值,若兩個給定結點無最低公共祖先,則輸出“My God”。

樣例輸入:
2
1  2  4  6  0  0  7  0  0  5  8  0  0  9  0  0  3  0  0


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

樣例輸出:
2
My God

解題思路:

尋找樹中兩個結點的最低公共祖先,我的想法是對樹中的每個結點都增加一個指向其父結點的指標,每個結點到二叉樹根結點的路徑就會形成一個連結串列,這樣就把尋找樹中兩個結點的最低公共祖先轉化成了尋找兩個連結串列中的第一個公共結點。
例如對於測試用例:
1  2  4  6  0  0  7  0  0  5  8  0  0  9  0  0  3  0  0
6  8
根據先序遍歷構造的二叉樹如圖1所示:

圖1  根據先序遍歷構造的二叉樹

由圖1可以得知,結點6到根結點1的路徑所構成的連結串列是:
6 --> 4 --> 2 --> 1             ①
結點8到根結點1的路徑所構成的連結串列是:
8 --> 5 --> 2 --> 1             ②
接下來從頭至尾依次遍歷連結串列①中的每個結點node1,判斷node1是否存在於連結串列②中。如果node1存在於連結串列②中,則表示node1是這兩個連結串列的第一個公共結點;否則繼續遍歷連結串列①的下一個結點,直至連結串列①中的結點全部遍歷完為止。可以得知連結串列①和連結串列②的第一個公共結點是2,因此2是結點6和結點8的最低公共祖先。這種演算法是從兩個結點開始,從下往上

查詢這兩個結點的最低公共祖先。但是這種演算法沒有通過第4組測試資料,WA了。。。
後來上作者的部落格 程式設計師面試題精選100題(48)-二叉樹兩結點的最低共同父結點[資料結構]  看了一下,發現了一種從根結點開始,從上向下查詢樹中兩個結點的最低公共祖先的演算法,我是採用這種演算法AC的,下面簡單介紹一下該演算法:

(1)先判斷結點m1和m2是否都在二叉樹中,如果m1和m2都在二叉樹中,則進入步驟(2),否則可以認為這兩個結點不具有最低公共祖先;
(2)從樹的根結點root開始,分以下4種情況進行討論:
        1) 如果根結點root與m1和m2都不相等則進入步驟2),
            否則分以下兩種情況討論:
            如果根結點root等於結點m1,則說明m1是m1和m2的最低公共祖先,
            如果根結點root等於結點m2,則說明m2是m1和m2的最低公共祖先;
        2) 如果m1和m2分別位於根結點的左右兩棵子樹中,則可以認為根結點root就是m1和m2的最低公共祖先;
        3) 如果m1和m2都位於根結點的左子樹中,則說明m1和m2的最低公共祖先存在於根結點的左子樹中,

            此時可繼續遞迴查詢根結點的左子樹,直至找到m1和m2的最低公共祖先為止;

        4) 如果m1和m2都位於根結點的右子樹中,則說明m1和m2的最低公共祖先存在於根結點的右子樹中,

            此時可繼續遞迴查詢根結點的右子樹,直至找到m1和m2的最低公共祖先為止。
“從兩個結點開始,從下往上查詢這兩個結點的最低公共祖先”與“從根結點開始,從上向下查詢樹中兩個結點的最低公共祖先”這兩種演算法,在大多數測試用例上執行時所得到的結果是一樣的,但是當樹中出現重複的結點時,這兩種演算法的執行結果可能就不相同了,具體檢視圖2:


圖2  存在重複結點的二叉樹

對於圖2中的二叉樹,如果要查詢結點2和結點2的最低公共祖先,從下往上查詢得到結果是2從上向下查詢的結果是1。我不知道從下往上查詢演算法WA,是不是因為測試資料中包含了這組測試資料。這是我根據AC程式碼生成的一些測試資料:題目1509:樹中兩個結點的最低公共祖先的測試資料

AC程式碼如下:

#include<stdio.h>
#include<malloc.h>
#include<vector>
using namespace std;
 
// 定義二叉樹的結點
typedef struct Node
{
   int data;             // 資料域
   Node * lChild;        // 左孩子
   Node * rChild;        // 右孩子
}BinaryTreeNode;
 
vector <int> preOrderSequence;   // 用於儲存二叉樹的先序遍歷序列
 
/**
* 根據先序遍歷序列構造二叉樹
* @return 所構二叉樹的根結點
*/
BinaryTreeNode * createBinaryTreeByPreOrder()
{
  int data;
  BinaryTreeNode * root = NULL;
  scanf("%d",&data);
  if(0 != data)
  {
      preOrderSequence.push_back(data);
      root = (BinaryTreeNode *)malloc(sizeof(BinaryTreeNode));
      root -> data = data;
      root -> lChild = createBinaryTreeByPreOrder();   // 遞迴構造左子樹
      root -> rChild = createBinaryTreeByPreOrder();   // 遞迴構造右子樹
  }
  return root;
}
 
/**
* 從根結點從上自下尋找兩個結點的最低公共祖先
* @param BianaryTreeNode * root  二叉樹的根結點
* @param int m1  輸入的結點m1
* @param int m2  輸入的結點m2
* @return 返回m1和m2最低公共祖先結點,如果二者不存在最低公共祖先結點,則返回NULL
*/
BinaryTreeNode * findLatestCommonAncestor(BinaryTreeNode * root,int m1,int m2)
{
  if(NULL == root)
     return NULL;
  else
  {
     BinaryTreeNode * commonAncestor = NULL;    // commonAncestor指向m1和m2的最低公共祖先結點
     int rootNodeData = root -> data;           // 當前二叉樹根結點的值
     // 如果m1是m2的祖先結點,則二者的最低公共祖先是m1;如果m2是m1的祖先結點,則二者的最低公共祖先是m2。
     if(rootNodeData == m1 || rootNodeData == m2)
     {
        commonAncestor = root;
     }
     else
     {
        //lChildCommonAncestor表示最低公共祖先出現在左子樹中,rChildCommonAncestor表示最低公共祖先出現在右子樹中
        BinaryTreeNode * lChildCommonAncestor = findLatestCommonAncestor(root -> lChild,m1,m2);
        BinaryTreeNode * rChildCommonAncestor = findLatestCommonAncestor(root -> rChild,m1,m2);
        //如果lChildCommonAncestor和rChildCommonAncestor都不為NULL
        //則說明m1,m2分別出現在root的左右子樹中,此時root為m1和m2的最低公共祖先結點
        if(NULL != lChildCommonAncestor && NULL != rChildCommonAncestor)
        {
            commonAncestor = root;
        }
        else
        {
            if(NULL != lChildCommonAncestor) // m1和m2都出現在root的左子樹中,lChildCommonAncestor是它們的最低公共祖先結點
            {
                commonAncestor = lChildCommonAncestor;
            }
            if(NULL != rChildCommonAncestor) // m1和m2都出現在root的右子樹中,rChildCommonAncestor是它們的最低公共祖先結點
            {
                commonAncestor = rChildCommonAncestor;
            }
        }
     }
     return commonAncestor;
  }
 
}
 
/**
* 通過後序遍歷釋放二叉樹中的所有結點
* @param BinaryTreeNode * root  二叉樹的根結點
* @return void
*/
void deleteBinaryTreeByPostOrder(BinaryTreeNode * root)
{
   if(NULL == root)
      return;
   else
   {
       deleteBinaryTreeByPostOrder(root -> lChild);
       deleteBinaryTreeByPostOrder(root -> rChild);
       //printf("%d ",root -> data);
       free(root);
       root = NULL;
   }
}
 
int main()
{
    BinaryTreeNode * root;            // 二叉樹的根結點
    BinaryTreeNode * commonAncestor;  // 最低公共祖先結點
    int i,n;
    int m1,m2;
    bool isM1inBinaryTree,isM2inBinaryTree;
    scanf("%d",&n);
    while(n--)
    {
        preOrderSequence.clear();
        root = createBinaryTreeByPreOrder();
        scanf("%d%d",&m1,&m2);
        isM1inBinaryTree = false;
        isM2inBinaryTree = false;
        // 判斷m1和m2是否都在二叉樹中
        for(i = 0;i < preOrderSequence.size();i++)
        {
            if(preOrderSequence[i] == m1)
            {
                isM1inBinaryTree = true;
            }
            if(preOrderSequence[i] == m2)
            {
                isM2inBinaryTree = true;
            }
        }
        if(isM1inBinaryTree && isM2inBinaryTree)
        {
           commonAncestor = findLatestCommonAncestor(root,m1,m2);
           if(NULL != commonAncestor)
           {
               printf("%d\n",commonAncestor -> data);
           }
           else
           {
               printf("My God\n");
           }
        }
        else
        {
            printf("My God\n");
        }
        deleteBinaryTreeByPostOrder(root);
    }
    return 0;
}
/**************************************************************
    Problem: 1509
    User: blueshell
    Language: C++
    Result: Accepted
    Time:130 ms
    Memory:1024 kb
****************************************************************/