1. 程式人生 > >二叉樹問題——尋找二叉樹中兩個節點的最近公共祖先

二叉樹問題——尋找二叉樹中兩個節點的最近公共祖先

此題大概分為3種情況:
1、節點中無parent,但提供了root
(1)、此種情況又分為兩種,開闢空間,使用容器來儲存路徑,將其轉換為求連結串列公共節點的問題,時間複雜度為O(N),空間複雜度為O(N)
(2)、不開闢空間,在節點的左右子樹上尋找兩個節點,若兩個節點存在在節點的左右子樹,則該節點為最近的公共節點,否則繼續在該節點的左右子樹尋找,主要使用遞迴來完成尋找,時間複雜度為O(N*N)
綜合分析,推薦使用第一種方法
2、節點中有parent,但未提供root
3、此二叉樹是二叉搜尋樹

解決方法:
前面兩種情況都是想辦法將此題轉換為求解兩個相交連結串列的交點:
第一種情況需要我們將兩個節點到root的路徑儲存起來,此時路徑就好像是連結串列;
第二種情況比第一種情況還要簡單些,我們只需要不斷向前找,直至parent為空,統計兩個節點的深度,此時依舊是將兩個節點看做兩個連結串列的起點,依舊是相交連結串列的問題;
第三種情況:我們只需由根節點開始不斷和兩個節點的值做對比,若節點值均大於兩個節點的值,則在左子樹繼續尋找,若節點均小於兩個節點的值,則在右子樹繼續尋找,若節點值恰好在兩個節點之間,則此節點就是最近的公共祖先。
三種方法均寫在一個類中了,節點共用,但三種方法對節點的使用時按照三種條件嚴格區分的,程式碼如下:

#pragma once

#include<iostream>
#include<stack>

using namespace std;


//Lowest Common Ancestor最小公共節點問題,簡稱LCA
//三種情況,普通二叉樹,無parent,已知root;有parent,無root;二叉搜尋樹

template<class T>
struct BSNode
{
    int key;
    BSNode* left;
    BSNode* right;
    BSNode* parent;
    BSNode(T value):
        key(value)
        , left(NULL
) , right(NULL) , parent(NULL) {} }; template<class T> class BSTree { typedef BSNode<T> Node; public: BSTree(): _root(NULL) {} ~BSTree() {} public: void Insert(T key) { Node* newnode = new Node(key); if (_root == NULL
) { _root = newnode; } else { Node* cur = _root; Node* parent = NULL; while (cur) { parent = cur; if (key > cur->key) { cur = cur->right; } else if (key < cur->key) { cur = cur->left; } else { return; } } if (parent->key > key) { parent->left = newnode; } else { parent->right = newnode; } newnode->parent = parent; } } void Print() { if (_root == NULL) { return; } _Print(_root); } void _Print(Node* cur) { if (cur == NULL) { return; } else if (cur->left) { _Print(cur->left); } cout << cur->key << " "; if (cur->right) { _Print(cur->right); } } Node* Find(int key) { Node* cur = _root; while (true) { if (cur->key > key) { cur = cur->left; } else if (cur->key < key) { cur = cur->right; } else { return cur; } } return NULL; } // 1、普通二叉樹,第一種方法,使用容器來儲存路徑 //(1)、由根節點尋找給定的兩個節點,用棧儲存路徑節點的值 //(2)、此時問題轉換為求兩個連結串列的公共節點問題,將較長的 void Path(Node* root, Node* node,stack<Node*> &s,bool &f) { s.push(root); if (root == node) { f = false; return; } if (f && root->left != NULL) { Path(root->left, node, s,f); } if (f && root->right != NULL) { Path(root->right, node, s,f); } if (f) { s.pop(); } } Node* FindLCA1(Node* node1, Node* node2) { if (node1 == NULL || node2 == NULL || _root == NULL) { return NULL; } stack<Node*> s1; stack<Node*> s2; bool f = true; Path(_root, node1, s1,f); f = true; Path(_root, node2, s2,f); //將兩個棧調整為長度一樣的 int count = 0; if (s1.size() > s2.size()) { count = s1.size() - s2.size(); while (count--) { s1.pop(); } } else { count = s2.size() - s1.size(); while (count--) { s2.pop(); } } //逐個比較,尋找相同節點 while (s1.top() != s2.top()) { s1.pop(); s2.pop(); } Node* LCA = s1.top(); return LCA; } //第一類第二種方法:普通二叉樹無需開闢空間的方法,遞迴在節點左右子樹查詢兩個節點是否分別在左右子樹中,若存在,則該節點是最近公共祖先,否則遞迴左右子樹繼續查詢 //1、從根節點開始在左右子樹上查詢兩個節點是否存在 //2、若存在,則該節點就是最近公共祖先,否則繼續在該節點的左右子樹繼續查詢 Node* FindR(Node* root, Node* node) { if (root == NULL || node == NULL) { return NULL; } while (root!=NULL) { if (root->key > node->key) { root = root->left; } else if (root->key < node->key) { root = root->right; } else { return root; } } return NULL; } Node* FindLCA4R(Node* root, Node* node1, Node* node2) { if (root == NULL) { return NULL; } else if (FindR(root->left, node1) && FindR(root->right, node2)) { return root; } if (FindLCA4R(root->left, node1, node2) == NULL) { FindLCA4R(root->right, node1, node2); } } Node* FindLCA4(Node* node1, Node* node2) { if (_root == NULL || node1 == NULL || node2 == NULL) { return NULL; } else if (node1 == _root || node2 == _root) { return _root; } return FindLCA4R(_root, node1, node2); } //第三種情況,二叉搜尋樹,充分利用二叉搜尋樹的特性 //(1)、由根節點開始逐個和兩個節點的key值對比 //(2)、若均大於,則在該節點的左子樹;若均小於,則在該節點的右子樹;若此時節點的值恰好在兩個節點的中間,則此節點必然為最小公共節點 Node* FindLCA3(Node* node1, Node* node2) { if (_root == NULL || node1 == NULL || node2 == NULL) { return NULL; } BSNode<T>* cur = _root; while (true) { if (cur->key > node1->key && cur->key > node2->key) { cur = cur->left; } else if (cur->key < node1->key && cur->key < node2->key) { cur = cur->right; } else { return cur; } } return NULL; } //第二種,有parent,但無root(用當前節點向前找,直至父節點為NULL) //(1)、有兩個節點向前找,計算出兩個節點的深度 //(2)、此時問題和第一種情況類似 Node* FindLCA2(Node* node1, Node* node2) { if (node1 == NULL || node2 == NULL) { return NULL; } int node1Len = 0; int node2Len = 0; Node* cur = node1; while (cur) { ++node1Len; cur = cur->parent; } cur = node2; while (cur) { ++node2Len; cur = cur->parent; } //調整節點 int count = 0; if (node1Len > node2Len) { count = node1Len - node2Len; while (count--) { node1 = node1->parent; } } else { count = node2Len - node1Len; while (count--) { node2 = node2->parent; } } //由下向上尋找相同的節點 while (node1 != node2) { node1 = node1->parent; node2 = node2->parent; } return node1; } private: BSNode<T>* _root; };