求树中任意两个结点的最低公共祖先

       在上个月头条二面中,被问到求树中任意两个结点的最低公共祖先这个问题,寒假刷《剑指offer》又看到这个题目,遂在此简单总结一下。对于这个开放性问题,我们需要和面试官沟通题目的具体要求,比如题目中谈及的树是普通树、二叉树还是二叉搜索树,树结点的定义是否包含父结点信息,是否对时间复杂度和空间复杂度有要求等等。所以下面我将该问题细分为三个小问题:求二叉搜索树中任意两个结点的最低公共祖先、求二叉树(含父结点信息)中任意两个结点的最低公共祖先、求二叉树(不含父结点信息)中任意两个结点的最低公共祖先。

1. 求二叉搜索树中任意两个结点的最低公共祖先

       对于这个问题,我们可以充分利用二叉搜索树的性质(若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值)。于是我们产生了这样的思路:若两结点的值均小于当前结点的值,则这两结点的最低公共祖先只可能出现在该结点的左子树上;若两结点的值均大于当前结点的值,则这两结点的最低公共祖先只可能出现在该结点的右子树上;若不满足以上两种情况,则有一个结点的值小于当前结点,另一个根结点的值大于当前结点,则该结点为最低公共祖先。

代码实现:

#include<iostream>
using namespace std;

// 定义树结点
struct TreeNode
{
	int val;
	struct TreeNode * left;
	struct TreeNode * right;
	TreeNode(int x):val(x), left(NULL), right(NULL){}
};

// 创建一棵二叉树
TreeNode * CreateBinaryTree()
{
	int val;
	cin>>val;
	if(val==-1)
		return NULL;
	TreeNode * root = new TreeNode(val);
	root->left = CreateBinaryTree();
	root->right = CreateBinaryTree();
	return root;
}

// 前序遍历二叉树
void PreOrder(TreeNode * root)
{
	if(root!=NULL)
		cout<<root->val<<ends;
	if(root->left!=NULL)
		PreOrder(root->left);
	if(root->right!=NULL)
		PreOrder(root->right);
}

TreeNode * CommonAncestorBinarySearch(TreeNode * root, TreeNode * node1, TreeNode * node2) // 二叉搜索树的最低公共祖先
{
	if(root==NULL||node1==NULL||node2==NULL) 
		return NULL;  // 树为空或者查找的结点也有一个为空,返回空指针
	TreeNode * p = root;
	while(p)
	{
		if(node1->val<p->val && node2->val<p->val) // 两结点都小于当前结点,则最低公共祖先在该结点的左子树上
			p = p->left;
		else if(node1->val>p->val && node2->val>p->val) // 两结点都大于当前结点,则最低公共祖先在该结点的右子树上
			p = p->right;
		else
			break;
	}
	return p;
}

int main()
{
	TreeNode * root = NULL;
	root = CreateBinaryTree(); // 创建一个二叉树
	// 二叉搜索树的测试数据:5 2 1 -1 -1 4 3 -1 -1 -1 7 6 -1 -1 9 8 -1 -1 10 -1 -1
	PreOrder(root); // 前序遍历二叉树
	cout<<endl;
	cout<<root->left->left->val<<ends<<root->left->right->left->val<<endl;
	TreeNode * result = CommonAncestorBinarySearch(root, root->left->left, root->left->right->left);
	if(result)
		cout<<result->val<<endl;
	else
		cout<<"Not Found!"<<endl;
	return 0;
}

2. 求二叉树(含父结点信息)中任意两个结点的最低公共祖先

       该问题和上个问题一样,要充分利用题目中包含的信息。由于题目所给二叉树是包含指向父结点的信息的,因此我们可以利用该信息找到树中任意一结点的路径(从当前结点出发,终止于根结点)。因此我们的解题思路是:首先找出两个结点的路径(这样就把问题转换成“两个链表的第一个公共结点”问题),然后求这两条路径的第一个公共结点即可(考虑到时间复杂度,这里采用O(n)的算法)。

代码实现:

#include<iostream>
using namespace std;

struct TreeNode
{
	int val;
	struct TreeNode * parent;
	struct TreeNode * left;
	struct TreeNode * right;
	TreeNode(int x):val(x), parent(NULL), left(NULL), right(NULL){}
};

// 创建一棵二叉树
TreeNode * CreateBinaryTree()
{
	int val;
	cin>>val;
	if(val==-1)
		return NULL;
	TreeNode * root = new TreeNode(val);
	root->left = CreateBinaryTree();
	if(root->left!=NULL)
		root->left->parent = root;
	root->right = CreateBinaryTree();
	if(root->right!=NULL)
		root->right->parent = root;
	return root;
}

// 从当前结点开始遍历到根节点
void Travel(TreeNode * root)
{
	if(root==NULL)
		return ;
	TreeNode * p = root;
	while(p!=NULL)
	{
		cout<<p->val<<ends;
		p = p->parent;
	}
}

TreeNode * CommonAncestorBinary(TreeNode * root, TreeNode * node1, TreeNode * node2) // 二叉的最低公共祖先(含父结点)
{
	if(root==NULL||node1==NULL||node2==NULL) 
		return NULL;  // 树为空或者查找的结点也有一个为空,返回空指针
	TreeNode * p1 = node1;
	TreeNode * p2 = node2;
	int len1 = 0;
	int len2 = 0;

	/*统计路径长度*/
	while(p1)
	{
		len1++;
		p1 = p1->parent;
	}
	while(p2)
	{
		len2++;
		p2 = p2->parent;
	}

	/*长的先走几步*/
	p1 = node1;
	p2 = node2;
	if(len1>len2)
	{
		p1 = p1->parent;
		len1--;
	}
	if(len2>len1)
	{
		p2 = p2->parent;
		len2--;
	}

	/*同步走,结点相同即为最低公共祖先*/
	while(p1&&p2)
	{
		if(p1==p2)
			break;
		p1 = p1->parent;
		p2 = p2->parent;
	}
	return p1;
}

int main()
{
	TreeNode * root = NULL;
	root = CreateBinaryTree(); // 创建一个二叉树
	// 二叉树的测试数据:5 2 1 -1 -1 4 3 -1 -1 -1 7 6 -1 -1 9 8 -1 -1 10 -1 -1
	Travel(root->left->right->left); // 从该结点开始遍历至根结点
	cout<<endl;
	cout<<root->left->left->val<<ends<<root->left->right->left->val<<endl;
	TreeNode * result = CommonAncestorBinary(root, root->left->left, root->left->right->left);
	if(result)
		cout<<result->val<<endl;
	else
		cout<<"Not Found!"<<endl;
	return 0;
}

3. 求二叉树(不含父结点信息)中任意两个结点的最低公共祖先

       对于该问题,明显比前两个问题复杂一些。但是只要我们能够分析出复杂之处并进行正确处理,也就可以想出不错的思路。与问题2而相比,问题3复杂之处就是二叉树结点的定义中不包含父结点信息,我们无法直接获得二叉树任意一结点的路径。接下来,我们会想到如果我间接获得任意一结点的路径呢?这里就需要开辟额外的空间来存放任意一结点的路径(从根结点开始,终止于该结点),关于“求某一结点的路径”问题,可以根据递归遍历二叉树的算法改进而来。得到两个结点的路径之后,从根结点开始同时遍历这两条路径,第一个不同结点的上一结点即为最低公共祖先。

       如果第一次拿到这个题目,可能不会立刻想到上面的解法。一般可以想到时间复杂度为O(n^2)的思路:从根节点开始遍历,如果当前结点的左结点是这两个结点的公共祖先,则进一步遍历左子树找到最低公共祖先;如果当前结点的右结点是这两个结点的公共祖先,则进一步遍历右子树找到最低公共祖先;若不满足以上两种情况,则只需要判断当前结点是否是这两个结点的祖先,若是则该结点为最低公共祖先,反之则不存在!因此,这种思路的重点变成了如何判断某个结点A是否为另一结点B的祖先。很简单,从A结点出发遍历子树(该子树可以看做以A结点为根结点的二叉树),若找到B结点则可认为A结点为B结点的祖先,若找不到则不是!

代码实现:

扫描二维码关注公众号,回复: 1114608 查看本文章
#include<iostream>
using namespace std;
#include<vector>

struct TreeNode
{
	int val;
	struct TreeNode * left;
	struct TreeNode * right;
	TreeNode(int x):val(x), left(NULL), right(NULL){}
};

// 创建一棵二叉树
TreeNode * CreateBinaryTree()
{
	int val;
	cin>>val;
	if(val==-1)
		return NULL;
	TreeNode * root = new TreeNode(val);
	root->left = CreateBinaryTree();
	root->right = CreateBinaryTree();
	return root;
}

// 前序遍历二叉树
void PreOrder(TreeNode * root)
{
	if(root!=NULL)
		cout<<root->val<<ends;
	if(root->left!=NULL)
		PreOrder(root->left);
	if(root->right!=NULL)
		PreOrder(root->right);
}

bool IsAncestor(TreeNode * root, TreeNode * node) // 判断当前子树是否包含node结点
{
	if(root==NULL||node==NULL)
		return false;
	if(root==node)
		return true;
	return IsAncestor(root->left, node) || IsAncestor(root->right, node);
}

TreeNode * CommonAncestorBinary(TreeNode * root, TreeNode * node1, TreeNode * node2) // 二叉树的最低公共祖先(不含父节点),O(n^2)时间复杂度
{
	if(root==NULL||node1==NULL||node2==NULL) 
		return NULL;  // 树为空或者查找的结点也有一个为空,返回空指针
	if(IsAncestor(root->left,node1) && IsAncestor(root->left,node2)) // 左结点是公共祖先
	{
		return CommonAncestorBinary(root->left,node1,node2);
	}
	else if(IsAncestor(root->right,node1) && IsAncestor(root->right,node2)) // 右结点是公共祖先
	{
		return CommonAncestorBinary(root->right,node1,node2);
	}
	else
	{
		if(IsAncestor(root,node1)&&IsAncestor(root,node2))
			return root;
		else
			return NULL;
	}
}

bool FindPath(TreeNode * root, TreeNode * node, vector<TreeNode*> &path)
{
	if(root==NULL)
		return false;
	path.push_back(root);
	if(root==node) // 遍历到该结点
		return true;
	bool left = false;
	bool right = false;
	left = FindPath(root->left,node,path);
	if(!left)
	{
		right = FindPath(root->right,node,path);
	}
	if(left==false&&right==false)
		path.pop_back();
	return (left||right);
}


TreeNode * CommonAncestorBinary2(TreeNode * root, TreeNode * node1, TreeNode * node2) // 二叉树的最低公共祖先(不含父节点),O(n)时间复杂度
{
	if(root==NULL||node1==NULL||node2==NULL) 
		return NULL;  // 树为空或者查找的结点也有一个为空,返回空指针
	
	vector<TreeNode*> path1; 
	bool is_path1 = FindPath(root, node1, path1); //找到node1的路径

	vector<TreeNode*> path2;
	bool is_path2 = FindPath(root, node2, path2); //找到node2的路径
	
	if(is_path1==true&&is_path2==true)
	{
		for(int i=0;i<path1.size()||i<path2.size();i++)
		{
			if(path1[i]!=path2[i])
				break;
		}
		if(i>0&&(i<path1.size()||i<path2.size()))
			return path1[i-1]; 
		else
			return NULL;

	}
	else
		return NULL;
	
}

int main()
{
	TreeNode * root = NULL;
	root = CreateBinaryTree(); // 创建一个二叉树
	// 二叉搜索树的测试数据:5 2 1 -1 -1 4 3 -1 -1 -1 7 6 -1 -1 9 8 -1 -1 10 -1 -1
	PreOrder(root); // 前序遍历二叉树
	cout<<endl;
	cout<<root->left->left->val<<ends<<root->left->right->left->val<<endl;
	/*bool is = IsAncestor(root->left->left, root->right);
	if(is)
		cout<<root->val<<" is the ancestor of "<<root->left->left->val<<endl;
	else
		cout<<root->val<<" is not the ancestor of "<<root->left->left->val<<endl;*/
	TreeNode * result = CommonAncestorBinary2(root, root->left->left, root->left->right->left);
	if(result)
		cout<<result->val<<endl;
	else
		cout<<"Not Found!"<<endl;
	return 0;
}

4. 总结

    二叉树的遍历:前序遍历、中序遍历、后序遍历、广度遍历非常重要! 递归与非递归两种方式都要会!以上算法都是基于这些遍历的变形,如果还不清楚的话,恐怕面试的时候会悲伤到变形吧!


猜你喜欢

转载自blog.csdn.net/jingyi130705008/article/details/79393289