問題
在前面的文章二叉樹兩結點最低公用祖先結點(一) 中有探討過這個問題,不過本文考慮的是另外一種二叉樹,該二叉樹每個結點包含一個指向父結點的指標,根結點的父結點為NULL。其結構如下:
struct node { int data; struct node* left; struct node* right; struct node* parent; };
同樣,該二叉樹不一定是二叉搜尋樹(BST)。在前面文章中的自底向上的方法,可以在O(N)的時間之內找到LCA,不過由於本文中的二叉樹有指向父結點的指標,所以並不用遞迴實現,求解應該更加簡單。給定一棵二叉樹如下:
_______3______ / \ ___5__ ___1__ / \ / \ 6 _2_ 0 8 / \ 7 4
基本解法
可以從兩個結點的位置開始向上回溯到根結點,最終這兩個結點會合并成一條路徑。可以使用一個hash_set來記錄已經訪問過的結點,如果在回溯的過程中訪問到一個已經訪問過的結點,則該結點一定是LCA,直接返回即可。該方法由於使用hash_set,因此需要額外的儲存空間。時間複雜度為O(h),h為二叉樹的高度。
typedef struct node Node;Node *LCA(Node *root, Node *p, Node *q) { hash_set<Node *> visited; while (p || q) { if (p) { if (!visited.insert(p).second) return p; // 如果插入p不成功,表示p已經訪問過,找到了LCA p = p->parent; } if (q) { if (!visited.insert(q).second) return q; // 如果插入q不成功,表示q已經訪問過,找到了LCA q = q->parent; } } return NULL;}
更好的解法
由於每個結點都有指向父結點的指標,因此可以求出這兩個結點的高度差dh。可以看到,在回溯的過程中離根結點近的結點總是領先離根結點遠的結點的dh步。這樣,可以讓離根結點遠的(更深的結點)先走dh步,然後兩個結點一起走,最終一定會在某一個結點相遇,則相遇的結點為LCA。如果不相遇,表示這兩個結點不在同一棵樹中,則返回NULL(由於我們假定了兩個結點都在二叉樹中,所以這種情況不可能出現)。
int getHeight(Node *p) { int height = 0; while (p) { height++; p = p->parent; } return height;} // 因為root->parent= NULL,所以參數中不需要傳遞rootNode *LCA(Node *p, Node *q) { int h1 = getHeight(p); int h2 = getHeight(q); // 交換高度大小 if (h1 > h2) { swap(h1, h2); swap(p, q); } //保證h2>=h1 int dh = h2 - h1; for (int h = 0; h < dh; h++) q = q->parent; while (p && q) { if (p == q) return p; p = p->parent; q = q->parent; } return NULL; // p和q不在同一棵樹中,這種情況在本題中不會出現。}