二叉树两结点的最低共同父结点

题目: 二叉树的结点定义如下:
struct TreeNode
{
    int m_nvalue;
    TreeNode* m_pLeft;
    TreeNode* m_pRight;
};

输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。

 

分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。

 

第一变种是二叉树是一种特殊的二叉树:查找二叉树。也就是树是排序过的,位于左子树上的结点都比父结点小,而位于右子树的结点都比父结点大。我们只需要从根结点开始和两个结点进行比较。如果当前结点的值比两个结点都大,则最低的共同父结点一定在当前结点的左子树中。如果当前结点的值比两个结点都小,则最低的共同父结点一定在当前结点的右子树中。

 

第二个变种是树不一定是二叉树,每个结点都有一个指针指向它的父结点。于是我们可以从任何一个结点出发,得到一个到达树根结点的单向链表。因此这个问题转换为两个单向链表的第一个公共结点。我们在本面试题系列的第35题讨论了这个问题。

前面我们提过如果结点中有一个指向父结点的指针,我们可以把问题转化为求两个链表的共同结点。现在我们可以想办法得到这个链表。我们在本面试题系列的第4题中分析过如何得到一条中根结点开始的路径。我们在这里稍作变化即可:

/
 // Get the path form pHead and pNode in a tree with head pHead
 /
 bool GetNodePath(TreeNode* pHead, TreeNode* pNode, std::list<TreeNode*>& path)
 {
     if(pHead == pNode)
         return true;
  
     path.push_back(pHead);
  
     bool found = false;
     if(pHead->m_pLeft != NULL)
         found = GetNodePath(pHead->m_pLeft, pNode, path);
     if(!found && pHead->m_pRight)
         found = GetNodePath(pHead->m_pRight, pNode, path);
  
     if(!found)
         path.pop_back();
  
     return found;
 }

由于这个路径是从跟结点开始的。最低的共同父结点就是路径中的最后一个共同结点:

/
 // Get the last common Node in two lists: path1 and path2
 /
 TreeNode* LastCommonNode
 (
     const std::list<TreeNode*>& path1,
     const std::list<TreeNode*>& path2
 )
 {
     std::list<TreeNode*>::const_iterator iterator1 = path1.begin();
     std::list<TreeNode*>::const_iterator iterator2 = path2.begin();
    
     TreeNode* pLast = NULL;
  
     while(iterator1 != path1.end() && iterator2 != path2.end())
     {
         if(*iterator1 == *iterator2)
             pLast = *iterator1;
  
         iterator1++;
         iterator2++;
     }
  
     return pLast;
 }
有了前面两个子函数之后,求两个结点的最低共同父结点就很容易了。我们先求出从根结点出发到两个结点的两条路径,再求出两条路径的最后一个共同结点。代码如下:

/
 // Find the last parent of pNode1 and pNode2 in a tree with head pHead
 /
 TreeNode* LastCommonParent_2(TreeNode* pHead, TreeNode* pNode1, TreeNode* pNode2)
 {
     if(pHead == NULL || pNode1 == NULL || pNode2 == NULL)
         return NULL;
  
     std::list<TreeNode*> path1;
     GetNodePath(pHead, pNode1, path1);
  
     std::list<TreeNode*> path2;
     GetNodePath(pHead, pNode2, path2);
  
     return LastCommonNode(path1, path2);
 }

这种思路的时间复杂度是O(n),时间效率要比第一种方法好很多。但同时我们也要注意到,这种思路需要两个链表来保存路径,空间效率比较低。

以上转自何海涛博客

***************************************************************************************************************

A Bottom-up Approach (Worst case O(n) ):
Using a bottom-up approach, we can improve over the top-down approach by avoiding traversing the same nodes over and over again.

We traverse from the bottom, and once we reach a node which matches one of the two nodes, we pass it up to its parent. The parent would then test its left and right subtree if each contain one of the two nodes. If yes, then the parent must be the LCA and we pass its parent up to the root. If not, we pass the lower node which contains either one of the two nodes (if the left or right subtree contains either p or q), or NULL (if both the left and right subtree does not contain either p or q) up.

Sounds complicated? Surprisingly the code appears to be much simpler than the top-down one.

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;  // if p and q are on both sides
  return L ? L : R;  // either one of p,q is on one side OR p,q is not in L&R subtrees
}

http://www.leetcode.com/2011/07/lowest-common-ancestor-of-a-binary-tree-part-i.html


自己用程序实现了一下,为了方便演示,将传入的后两个参数变成了节点存储的字符值

#include <iostream>
#include <cassert>
#include <algorithm>
#include <stack>

using namespace std ; 

struct NODE 
{
	NODE *pLeft;
	NODE *pRight;
	char chValue;
};

void Rebuild(char *preOrder, char *inOrder, int nTreeLen, NODE **pRoot)
{
	if(preOrder==NULL&&inOrder==NULL)
	{
		*pRoot = NULL;
		return;
	}

	if (nTreeLen == 0)
	{
		return;
	}


	if (*pRoot==NULL)
	{
		*pRoot = new NODE;
		assert(*pRoot!=NULL);
		(*pRoot)->pLeft=NULL;
		(*pRoot)->pRight = NULL;
		(*pRoot)->chValue = *preOrder;
	}

	if (nTreeLen==1)
	{
		return;
	}

	char *preTmp = preOrder;
	char *inTmp = inOrder;
	int count=0;
	while(*inTmp!=*preTmp && count<nTreeLen)
	{
		if (inTmp==NULL)	//这个地方要注意判断一下,不过如果假定输入串没有错误的话,这里一般不会出问题。
		{
			return;
		}
		inTmp++;
		count++;
	}

	Rebuild(preOrder+1,inOrder,count,&((*pRoot)->pLeft));
	Rebuild(preOrder+count+1,inOrder+count+1,nTreeLen-count-1,&((*pRoot)->pRight));

}


NODE* LCA(NODE *proot,char ch1, char ch2)
{
	if (proot==NULL)
	{
		return NULL;
	}

	if (proot->chValue==ch1 || proot->chValue==ch2)
	{
		return proot;
	}

	NODE *pleft = LCA(proot->pLeft,ch1,ch2);
	NODE *pright = LCA(proot->pRight,ch1,ch2);

	if (pleft!=NULL && pright!=NULL)
	{
		return proot;
	}

	return pleft!=NULL?pleft:pright;

}

int main()
{

	char *preStr= "abdcef";
	char *inStr = "dbaecf";

	NODE *pRoot = NULL;

	Rebuild(preStr,inStr,6,&pRoot);

	NODE *pnode = LCA(pRoot,'e','f');

	cout<<pnode->chValue<<endl;

	system("pause");

	return 0;

}


***************************************************************************************************************

要求两节点的最近共同父节点(LCA,lowest common ancestor),可以采用树的后序遍历。如果这两个节点不在一条线上,则它们必定分别在所求节点A的左子树和右子树上,后序遍历到第一个满足这个条件的节点就是所要求的节点A。另外,当这两个节点在一条线上,所求节点A则是这两个节点中层次最低的节点的父节点。

static bool lca(Node *root, int va, int vb, Node *&result, Node* parrent)
 {
   // left/right 左/右子树是否含有要判断的两节点之一 
   bool left = false, right = false;
   if (!result && root->left) left = lca(root->left,va,vb,result,root);
   if (!result && root->right) right = lca(root->right,va,vb,result,root);
   // mid 当前节点是否是要判断的两节点之一 
   bool mid = false;
   if (root->data == va || root->data == vb) mid=true;
   if (!result && int(left + right + mid) == 2) {
     if (mid) result = parrent;
     else result = root;
   }
   return left | mid | right ;
 }
 
 Node *lca(Node *root,int va, int vb)
 {
   if (root == NULL) return NULL;
   Node *result = NULL;
   lca(root, va, vb,result, NULL);
   return result;
 }

上面的code最低公共父节点定义跟前面两种方法不太一样,如果在一条线上,返回了第一个节点的父节点而前两种方法是返回第一个节点,要注意。

以上转自flyinghearts




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值