二叉树经典面试题3~树中两个结点的最低公共祖先

 剑指offer面试题50~树中两个结点的最低公共祖先
一.问题描述
     输入一颗树中的两个结点,求他们的最低公共祖先
二.问题分析

   1.什仫是最低公共祖先?

     所谓的最低公共祖先就是从根结点开始到这两个结点的两条路径中的最后一个公共结点。最低公共祖先的查找类似下图:

        

   2.在求最低公共祖先的时候会出现什仫样的情况?

     1).该树为二叉搜索树
     2).普通的树但存在父结点
     3).普通的树不存在父结点
情况一.所查找的树是二叉搜索树

    这是最简单的情况,我们知道在二叉搜索树中左子树的所有结点的值一定比根结点小,右子树中的所有结点的值一定比根结点大。根据二叉树的这个性质,我们只需要从根结点开始和两个输入结点的值进行比较,如果当前结点的值比输入的两个结点的值都大,那仫最低的公共祖先一定在左子树中,则继续比遍历左子树;如果当前结点的值比输入的两个结点的值都小,那仫最低的公共祖先一定在右子树中,则继续遍历右子树。这样在树中从上到下找到的第一个在两个输入结点的值之间的结点,就是最低的公共祖先。

    

#pragma once
template<class T>
struct BinaryTreeNode
{
	T _data;
	BinaryTreeNode<T> *_left;
	BinaryTreeNode<T> *_right;
	BinaryTreeNode(const T& data)
		:_data(data)
		,_left(NULL)
		,_right(NULL)
	{}
};
template<class T>
class PublicLowParent
{
	typedef BinaryTreeNode<T> Node;
public:
	PublicLowParent()
		:_root(NULL)
	{}
	PublicLowParent(const T* a,size_t size,const T& invalid)
	{
		size_t index=0;
		_root=_CreatTree(a,size,index,invalid);
	}
	~PublicLowParent()
	{
		_Destroy(_root);
	}
public:
	//该树是二叉搜索树的情况
	Node *FindPublicLowParent(const T& index1,const T& index2)
	{
		Node *cur=_root;
		while (cur)
		{
			//根结点的值比两个要查找结点的值都大,在左子树中查找
			if(cur->_data > index1 && cur->_data > index2)
			{
				cur=cur->_left;
			}
			//根结点的值比两个要查找结点的值都小,在右子树中查找
			else if(cur->_data < index1 && cur->_data < index2)
			{
				cur=cur->_right;
			}
			//找到最低公共祖先结点
			else if(cur->_data >= index1 && cur->_data <= index2
				|| cur->_data <= index1 && cur->_data >= index2)
			{
				return cur;
			}
		}
		return NULL;
	}
protected:
	Node *_CreatTree(const T* a,size_t size,size_t& index,const T& invalid)
	{
		assert(a);
		Node *root=NULL;
		if(index < size && a[index] != invalid)
		{
			root=new Node(a[index]);
			root->_left=_CreatTree(a,size,++index,invalid);
			root->_right=_CreatTree(a,size,++index,invalid);
		}
		return root;
	}
	void Destroy(Node *&root)
	{
		if(NULL == root)
			return ;
		Node *cur=root;
		if(cur)
		{
			Destroy(cur->_left);
			Destroy(cur->_right);
			delete cur;
			cur=NULL;
		}
	}
protected:
	Node *_root;
};
void testPublicLowParent()
{
	int array[]={8,6,5,'#','#',7,'#','#',10,9,'#','#',11};
	size_t size=sizeof(array)/sizeof(array[0]);
	PublicLowParent<int> bt(array,size,'#');
	BinaryTreeNode<int> *ret=bt.FindPublicLowParent(10,11);
	if(ret != NULL)
		cout<<"存在最低公共祖先:"<<ret->_data<<endl;
	else
		cout<<"不存在最低公共祖先"<<endl;
}

情况二.普通的树但存在父指针

    对于二叉搜索树我们可以很容易的找出最低的公共祖先,那仫如果不是二叉搜索树呢?甚至都不是二叉树又该如何找找到它的最低祖先结点呢?我们给这个不是搜索二叉树的树加上一个指向父结点的指针会发生什仫情况呢?

    看到这个变态的要求这使我想起了之前实现过的一道面试题:找到两个链表的第一个公共结点。我觉得这两道题是有相同点的,为什仫呢?之前不是说查找的是最后的一个公共结点为什仫可以转化成在链表中的第一个结点呢?

    试想一下,如果这棵树存在指向父结点的指针,从两个不同的结点向根结点看过去,我们发现它们具有相同的尾结点,输入的两个结点我们可以假设它在两条链表上,他们的最低公共祖先刚好是这两条链表的第一个公共结点。在这里用到了逆向思维的方式。

    

   如何找到这个双链表的第一个公共结点呢?如果同时从目标节点向上遍历是永远找不到公共点的,如果我们让较长路径的结点先走它们的长度差步呢?然后再让指向两个结点的指针同时向前推进那仫这两个指针第一次相遇的结点就是满足题意的结点了。这是利用了长度差的办法解决,当然也有其他的办法不过此时还没有想到啦!!!

   

#pragma once
template<class T>
struct TreeNode
{
	T _data;
	TreeNode<T> *_left;
	TreeNode<T> *_right;
	TreeNode<T> *_parent;
	TreeNode(const T& data)
		:_data(data)
		,_left(NULL)
		,_right(NULL)
		,_parent(NULL)
	{}
};
template<class T>
class ThreeBinaryTree
{
	typedef TreeNode<T> Node;
public:
	ThreeBinaryTree()
		:_root(NULL)
	{}
	ThreeBinaryTree(const T*a,size_t size,const T& invalid)
	{
		size_t index=0;
		Node *parent=NULL;
		_root=_CreatTree(a,size,index,parent,invalid);
	}
	int FindPublicLowParent(const T& index1,const T& index2)
	{
		if(index1 == index1)
			return index1;
		Node *first=NULL;
		_FindNode(_root,index1,first);
		Node *second=NULL;
		_FindNode(_root,index2,second);
		//从该结点到叶子结点的路径长
		int count1=1;
		int count2=1;
		Node *cur=first;
		while(cur->_parent)
		{
			count1++;
			cur=cur->_parent;
		}
		cur=second;
		while(cur->_parent)
		{
			count2++;
			cur=cur->_parent;
		}
		//求两条路径的差值
		int sub=abs(count1-count2);
		if(count1 >= count2)
			_FindFirstParentNode(first,second,sub);
		else
			_FindFirstParentNode(second,first,sub);
	}
protected:
	int _FindFirstParentNode(Node *first,Node *second,int sub)
	{
		//使路径较长的先走sub步
		while(--sub)
		{
			first=first->_parent;
		}
		//此时令两个结点同时向前走
		//找到的第一个相同的结点就是满足题意的
		while(first != second)
		{
			first=first->_parent;
			second=second->_parent;
		}
		return first->_data;
	}
	//前序遍历查找结点
	void _FindNode(Node *root,const T&index,Node *&node)
	{
		if(NULL == root)
			return ;
		if(root->_data == index)
			node=root;
		_FindNode(root->_left,index,node);
		_FindNode(root->_right,index,node);
	}
	Node *_CreatTree(const T* a,size_t size,size_t& index,Node *parent,const T& invalid)
	{
		assert(a);
		Node *root=NULL;
		if(index < size && a[index] != invalid)
		{
			root=new Node(a[index]);
			root->_parent=parent;
			root->_left=_CreatTree(a,size,++index,root,invalid);
			root->_right=_CreatTree(a,size,++index,root,invalid);
		}
		return root;
	}
protected:
	Node *_root;
};

void testThreeTree()
{
	int array[]={1,2,3,'#','#',4,'#','#',5,6,'#','#','#'};
	size_t size=sizeof(array)/sizeof(array[0]);
	ThreeBinaryTree<int> bt(array,size,'#');
	int ret=bt.FindPublicLowParent(2,4);
	cout<<"存在最低公共祖先:"<<ret<<endl;
}

 

情况三.普通的树不存在父结点

    到这里你会觉得这个问题解决了吧!其实不是,此时HR又问了,如果不给你指向父结点的指针也不是一颗二叉搜索树呢?我的小心脏啊快要吓的不跳啦!

1).递归的方法

    此时就没有什仫技巧可用了,只能使用最原始的办法啦,从根结点开始遍历,每遍历到一个结点的时候就判断两个输入的结点是否在它的子树中,如果在,则分别遍历它的所有子节点,并判断输入的两个结点是否在他们的子节点中,这样一直向下遍历,直到找到最低祖先为止。

    

	Node *FindLowParent(const T& index1,const T& index2)
	{
		return _FindLowParentNode(_root,index1,index2);
	}
	Node *_FindLowParentNode(Node *root,const T& index1,const T& index2)
	{
		Node *first=NULL;
		Node *second=NULL;
		if(NULL == root)
			return NULL;
		if(root->_data == index1 || root->_data == index2)
			return root;
		first=_FindLowParentNode(root->_left,index1,index2);
		second=_FindLowParentNode(root->_right,index1,index2);
		if(first && second)
			return root;
		return first ? first : second;
	}

    但是上面的办法是很耗费时间的,有些结点被遍历了多次,这样它的效率就有点低了,其实可以使用栈这种辅助内存的方式来解决的。其实利用辅助内存的方法也就是保存路径,而栈的后进先出的特性又使得该问题转化成找第一个公共结点的问题了

     

    5和4的路径先后入栈,如上图所示我发现还是需要找到两条路径的路径差的,先让路径长的先pop掉路径差个元素,然后两个栈的元素一起出栈,知道找到第一个key值相同的结点就是最低的公共祖先了。

    

	Node * FindPublicParentNode(const T& index1,const T& index2)
	{
		int sub=0;
		stack<Node *>s1;
		_FindPath(_root,s1,index1);
		stack<Node *>s2;
		_FindPath(_root,s2,index2);
		if(s1.size() > s2.size())
		{
			sub=s1.size()-s2.size();
			return _FindFirstParentNode(s1,s2,sub);
		}
		else   //s1.size() <= s2.size()
		{
			sub=s2.size()-s1.size();
			return _FindFirstParentNode(s2,s1,sub);
		}
		return NULL;
	}
	Node *_FindFirstParentNode(stack<Node *>s1,stack<Node *>s2,int sub)
	{
		//使长的路径先走sub步
		while(sub--)
		{
			s1.pop();
		}
		//两个栈一起直到找到第一个相同的元素
		while(!s1.empty() && !s2.empty() && s1.top() != s2.top())
		{
			s1.pop();
			s2.pop();
		}
		return s1.top();
	}
	bool _FindPath(Node *root,stack<Node *>&s,const T index)
	{
		if(NULL == root)
			return false;
		if(root->_data == index)
		{
			s.push(root);
			return true;
		}
		s.push(root);
		bool left=_FindPath(root->_left,s,index);
		bool right=_FindPath(root->_right,s,index);
		if(left == false && right == false)
		{
			s.pop();
			return false;
		}
		return true;
	}

    

   在有的想法中只粘贴了部分代码,源码见如下链接:https://github.com/CTTCassie/DataStruct

总结:

    虽然这只是一道面试题,可是却是个一组题目,在不同的结构中会有不同的解决办法,比如这上面实现的路径差,辅助栈等等方法,而且也和两条链表中查找第一个公共结点这道题联系起来,总之以后遇到问题要多扩展~~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值