文章目錄
- 1)自頂向下的方法
- 2)自底向上的方法
- 3)公用路徑法
題目
二叉樹結點的定義如下:
struct node { int data; struct node* left; struct node* right;};
給定二叉樹中的兩個結點,輸出這兩個結點的最低公用祖先結點(LCA)。注意,該二叉樹不一定是二叉搜尋樹。
比如給定的二叉樹如下所示,則可以知道結點1和5的最低公用祖先結點為5,結點4和5的最低公用祖先結點為5。
_______3______ / \ ___5__ ___1__ / \ / \ 6 _2 0 8 / \ 7 4
解法1)自頂向下的方法
我們可以從根結點出發,判斷當前結點的左右子樹是否包含這兩個結點。如果左子樹包含兩個結點,則它們的最低公用祖先結點也一定在左子樹中。如果右子樹包含兩個結點,則它們的最低公用祖先結點也一定在右子樹中。如果一個結點在左子樹,而另一個結點在右子樹中,則當前結點就是它們的最低公用祖先結點。根據該思路寫出代碼如下,注意這裡已經假定p和q是二叉樹中的結點。
/*尋找二叉樹中兩個結點最低公用祖先結點*/struct node* LCA(struct node *root, struct node *p, struct node *q) { if (hasNode(root->left, p) && hasNode(root->left, q)) //p和q都在左子樹中 return LCA(root->left, p, q); if (hasNode(root->right, p) && hasNode(root->right, q)) //p和q都在右子樹中 return LCA(root->right, p, q); return root; //p和q一個在左子樹,一個在右子樹中,直接返回root}/*判斷root為根的樹是否包含結點p*/bool hasNode(struct node* root, struct node* p){ if (!root) return false; if (root == p) return true; return hasNode(root->left, p) || hasNode(root->right, p);}
由於我們對每個結點都調用了hasNode函數,而該函數類似於遍曆二叉樹,複雜度為O(N),所以總的時間複雜度為O(N^2),我們期望找到一個更好的方法。
2)自底向上的方法
由於自頂向下的方法需要重複遍曆結點,使用自底向上的方法可以避免這種情況。
自底向上遍曆結點,一旦遇到結點等於p或者q,則將其向上傳遞給它的父結點。父結點會判斷它的左右子樹是否都包含其中一個結點,如果是,則父結點一定是這兩個節點p和q的LCA,傳遞父結點到root。如果不是,我們向上傳遞其中的包含結點p或者q的子結點,或者NULL(如果子結點不包含任何一個)。該方法時間複雜度為O(N)。
typedef struct node Node;Node *LCA(Node *root, Node *p, Node *q) { if (!root) return NULL; if (root == p || root == q) return root; Node *L = LCA(root->left, p, q); Node *R = LCA(root->right, p, q); if (L && R) return root; // 如果p和q位於不同的子樹 return L ? L : R; //p和q在相同的子樹,或者p和q不在子樹中}3)公用路徑法
還有一個方法就是依次得到從根結點到結點p和q的路徑,找出它們路徑中的最後一個公用結點即是它們的LCA。該演算法在何海濤先生的部落格中有詳細描述,這裡就不贅述,詳見http://zhedahht.blog.163.com/blog/static/25411174201081263815813/,該方法複雜度也為O(N),不過需要額外的空間儲存路徑,當然第2種方法中遞迴也是需要棧空間的,不過整體來看第2種方法簡潔但是不是很好理解,而公用路徑法則更加易懂。