1. 程式人生 > >《程式設計師面試金典》--尋找二叉樹中兩個節點的第一個公共祖先(三種情況)

《程式設計師面試金典》--尋找二叉樹中兩個節點的第一個公共祖先(三種情況)

/****************************************************************************************************************
題目描述:
給定一顆二叉樹,以及二叉樹中的兩個節點,找出這兩個節點的第一個公共祖先節點。

*****************************************************************************************************************/

題目分析:

假設1:這個二叉樹是二叉排序樹(O(N))

  如果題目中的二叉樹是二叉排序樹,那麼這道題目變的相對簡單很多,我們只需要從根節點開始遍歷,有以下三種情況發生:

      (1)如果題目給的兩個節點的值都大於當前節點,那麼繼續遍歷當前節點的右子樹

      (2)如果題目給的兩個節點的值都小於當前節點,那麼繼續遍歷當前節點的左子樹

      (3)如果當前節點大於其中一個節點而小於其中另外一個節點,那麼則返回該節點,該節點便是兩個節點的公共                  祖先節點。

假設2:這個二叉樹是普通的二叉樹,但是存在指向父節點的指標

由於每個節點存在指向父節點的指標,所以如果給定任意一個節點,便可以找到從該節點到根節點的路徑,因此我們可以找到題目中給予的兩個節點分別到根節點的路徑,因此該題目便變成了求兩個單向連結串列的第一個公共節點。

假設2延伸:求兩個單向連結串列第一個公共節點有兩種方法:

(1)第一種方法是:兩個連結串列如果有公共節點,那麼從第一個公共節點開始往後的所有節點都相同,我們首先遍歷兩個連結串列,求出兩個連結串列的長度。然後設定兩個指標分別指向這兩個單向連結串列的頭結點,首先讓連結串列較長的指標先移動,直至移動的剩餘長度與連結串列較短的那個連結串列長度相同為止,這個時候兩個指標開始同時移動,直到兩個指標指向的節點相同,這個節點便是這兩個單向連結串列的第一個公共節點。

      (2)第二種方法是:可以設定兩個棧,首先分別把兩個連結串列中的節點依次入棧,接下來從兩個棧中同時一個一個的將節點彈出,遇到第一對彈出的節點不同時,那麼前一次彈出相同的節點便是這兩個連結串列的第一個公共節點。

假設3:這個二叉樹是普通的二叉樹,並且不存在指向父節點的指標

<方法1>:判斷子樹中是否存在某節點(時間複雜度O(N^2))

          首先建立一個方法判斷一棵樹中是否存在某個節點,這個比較簡單,只需要遍歷整棵樹,如果存在返回true,如果不存在則返回false。

      接下來的判斷過程就相當於二叉排序數的判斷過程了,只不過二叉排序樹判斷一個節點是否在左子樹或者右子樹中只需要和父節點比較,而普通的樹需要遍歷整個左子樹或者右子樹才能實現,

(1)如果題目給的兩個節點都在右子樹,那麼繼續遍歷當前節點的右子樹

      (2)如果題目給的兩個節點都在左子樹,那麼繼續遍歷當前節點的左子樹

      (3)如果給定的兩個節點一個在左子樹,一個在右子樹,則返回當前節點。

<方法2>:尋找從根節點到子節點的路徑

如果找到從根節點到兩個子節點的路徑,那麼題目就迎刃而解了,可以用棧遍歷二叉樹,最後找到從根節點到相應節點的路徑,類似二叉樹的先序非遞迴遍歷一樣,先遍歷根節點,接著遍歷左子樹,接著遍歷右子樹,直到遍歷到相應節點為止,這時棧中的元素便是路徑元素。

<方法3>:遞迴遍歷

設根節點為root,兩個節點分別為p,q,用前序遍歷方法遞迴遍歷整個二叉樹,如果只找到p則返回p,如果只找到q則返回q,如果一個節點的左右子樹分別找到p,q,則返回該節點,其他情況返回NULL。

程式程式碼如下:

#include<iostream>
using namespace std;
struct BiTree
{
	int val;
	BiTree* left,*right;
};
BiTree* commonAncestor(BiTree* root,BiTree*p,BiTree*q,bool &sign)
{
	if(root==NULL)
	{
		sign=false;
		return NULL;
	}
	//p,q是同一個節點,則返回該節點,並標誌找到了
	if(root==p&&root==q)
	{
		sign=true;
		return root;
	}
	BiTree* x=commonAncestor(root->left,p,q,sign);
	//如果在左子樹中找到了公共節點,則直接返回
	if(sign==true)
		return x;
	BiTree* y=commonAncestor(root->right,p,q,sign);
	//如果在右子樹中找到了公共節點,則直接返回
	if(sign==true)
		return y;
	//如果左子樹和右子樹分別找到了,則返回當前節點
	if(x!=NULL&&y!=NULL)
	{
		sign=true;
		return root;
	}
	//如果當前節點為p或者q,則返回當前節點
	if(root==p||root==q)
		return root;
	return x==NULL?y:x;
}