题目:二叉树的结点定义如下:
1 struct TreeNode 2 { 3 int m_nvalue; 4 TreeNode* m_pLeft; 5 TreeNode* m_pRight; 6 };
输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。
分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。
第一变种是二叉树是一种特殊的二叉树:查找二叉树。也就是树是排序过的,位于左子树上的结点都比父结点小,而位于右子树的结点都比父结点大。我们只需要从根结点开始和两个结点进行比较。如果当前结点的值比两个结点都大,则最低的共同父结点一定在当前结点的左子树中。如果当前结点的值比两个结点都小,则最低的共同父结点一定在当前结点的右子树中。
第二个变种是树不一定是二叉树,每个结点都有一个指针指向它的父结点。于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表。因此这个问题转换为两个单向链表的第一个公共结点。我们在本面试题系列的第35题讨论了这个问题。
前面我们提过如果结点中有一个指向父结点的指针,我们可以把问题转化为求两个链表的共同结点。现在我们可以想办法得到这个链表。我们在本面试题系列的第4题中分析过如何得到一条中根结点开始的路径。我们在这里稍作变化即可:
1 ///////////////////////////////////////////////////////////////////////////////// 2 // Get the path form pHead and pNode in a tree with head pHead 3 ///////////////////////////////////////////////////////////////////////////////// 4 bool GetNodePath(TreeNode* pHead, TreeNode* pNode, std::list<TreeNode*>& path) 5 { 6 if(pHead == pNode) 7 return true; 8 9 path.push_back(pHead); 10 11 bool found = false; 12 if(pHead->m_pLeft != NULL) 13 found = GetNodePath(pHead->m_pLeft, pNode, path); 14 if(!found && pHead->m_pRight) 15 found = GetNodePath(pHead->m_pRight, pNode, path); 16 17 if(!found) 18 path.pop_back(); 19 20 return found; 21 }
由于这个路径是从跟结点开始的。最低的共同父结点就是路径中的最后一个共同结点:
1 ///////////////////////////////////////////////////////////////////////////////// 2 // Get the last common Node in two lists: path1 and path2 3 ///////////////////////////////////////////////////////////////////////////////// 4 TreeNode* LastCommonNode 5 ( 6 const std::list<TreeNode*>& path1, 7 const std::list<TreeNode*>& path2 8 ) 9 { 10 std::list<TreeNode*>::const_iterator iterator1 = path1.begin(); 11 std::list<TreeNode*>::const_iterator iterator2 = path2.begin(); 12 13 TreeNode* pLast = NULL; 14 15 while(iterator1 != path1.end() && iterator2 != path2.end()) 16 { 17 if(*iterator1 == *iterator2) 18 pLast = *iterator1; 19 20 iterator1++; 21 iterator2++; 22 } 23 24 return pLast; 25 }
有了前面两个子函数之后,求两个结点的最低共同父结点就很容易了。我们先求出从根结点出发到两个结点的两条路径,再求出两条路径的最后一个共同结点。代码如下:
1 ///////////////////////////////////////////////////////////////////////////////// 2 // Find the last parent of pNode1 and pNode2 in a tree with head pHead 3 ///////////////////////////////////////////////////////////////////////////////// 4 TreeNode* LastCommonParent_2(TreeNode* pHead, TreeNode* pNode1, TreeNode* pNode2) 5 { 6 if(pHead == NULL || pNode1 == NULL || pNode2 == NULL) 7 return NULL; 8 9 std::list<TreeNode*> path1; 10 GetNodePath(pHead, pNode1, path1); 11 12 std::list<TreeNode*> path2; 13 GetNodePath(pHead, pNode2, path2); 14 15 return LastCommonNode(path1, path2); 16 }
这种思路的时间复杂度是O(n),时间效率要比第一种方法好很多。但同时我们也要注意到,这种思路需要两个链表来保存路径,空间效率比较低。
以上转自何海涛博客
要求两节点的最近共同父节点(LCA,lowest common ancestor),可以采用树的后序遍历。如果这两个节点不在一条线上,则它们必定分别在所求节点A的左子树和右子树上,后序遍历到第一个满足这个条件的节点就是所要求的节点A。另外,当这两个节点在一条线上,所求节点A则是这两个节点中层次最低的节点的父节点。
1 static bool lca(Node *root, int va, int vb, Node *&result, Node* parrent) 2 { 3 // left/right 左/右子树是否含有要判断的两节点之一 4 bool left = false, right = false; 5 if (!result && root->left) left = lca(root->left,va,vb,result,root); 6 if (!result && root->right) right = lca(root->right,va,vb,result,root); 7 // mid 当前节点是否是要判断的两节点之一 8 bool mid = false; 9 if (root->data == va || root->data == vb) mid=true; 10 if (!result && int(left + right + mid) == 2) { 11 if (mid) result = parrent; 12 else result = root; 13 } 14 return left | mid | right ; 15 } 16 17 Node *lca(Node *root,int va, int vb) 18 { 19 if (root == NULL) return NULL; 20 Node *result = NULL; 21 lca(root, va, vb,result, NULL); 22 return result; 23 }
以上转自flyinghearts