每日一题(71) - 树中两个结点的最低公共祖先

本文介绍了如何在二叉树中寻找两个结点的最低公共祖先(LCA),包括当树为二叉搜索树时的特殊处理,以及在一般二叉树上使用深度遍历、哈希表、路径记录和并查集等方法进行求解的思路和代码示例。
摘要由CSDN通过智能技术生成

题目:给出两个结点A和B,求解这两个结点的最低公共祖先(LCA)

前提:为了更好的说明思路,这里让问题简单化,假设树是二叉树且树中只含有一个A和一个B。

举例:

条件(1)树为二叉搜索树

思路:

如果树为二叉搜索树树中必须包含给出的两个元素,可以利用二叉搜索树的性质来做。

如果树中不包含给出的两个元素,输出的结果会是错误的。

代码:

struct BTNode
{
	int m_nValue;
	BTNode* m_pLeft;
	BTNode* m_pRight;
};

BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)
{
	if (pRoot)
	{
		if (pRoot->m_nValue > nFirst && pRoot->m_nValue > nSec)
		{
			return FindLCA(pRoot->m_pLeft,nFirst,nSec);
		}
		else if (pRoot->m_nValue < nFirst && pRoot->m_nValue < nSec)
		{
			return FindLCA(pRoot->m_pRight,nFirst,nSec);
		}
		else
		{
			return pRoot;
		}
	}
	return NULL;
}

条件(2)一般的二叉树 + 有指向父亲的指针

思路:规划为两个相交链表求交集。

代码:树使用兄弟孩子链表法存储。

struct CSNode
{
	int m_nValue;
	CSNode* m_pParent;
	CSNode* m_pFirstChild;
	CSNode* m_pNextSibling;
};

int NodeCount(CSNode* pNode)
{
	assert(pNode);
	int nLen = 0;
	while(pNode)
	{
		nLen++;
		pNode = pNode->m_pParent;
	}
	return nLen;
}

CSNode* LCA(CSNode*& pFNode,CSNode*& pSNode)
{
	assert(pFNode && pSNode);
	int nDiff = 0;
	int nCountFir = NodeCount(pFNode);
	int nCountSec = NodeCount(pSNode);
	if (nCountFir > nCountSec)
	{
		nDiff = nCountFir - nCountSec;
		while(nDiff)
		{
			pFNode = pFNode->m_pParent;
			nDiff--;
		}
	}
	else
	{
		nDiff = nCountSec - nCountFir;
		while(nDiff)
		{
			pSNode = pSNode->m_pParent;
			nDiff--;
		}
	}
	assert(pSNode && pFNode);
	while(pFNode != pSNode)
	{
		pFNode = pFNode->m_pParent;
		pSNode = pSNode->m_pParent;
	}
	assert(pFNode);
	return pFNode;
}

CSNode* FindLCA(CSNode* pRoot,int nFirst,int nSec,CSNode*& pFNode,CSNode*& pSNode)
{
	if (!pRoot)
	{
		return NULL;		
	}
	if (pFNode && pSNode)
	{
		return NULL;
	}
	//比较值
	if (pRoot->m_nValue == nFirst)
	{
		pFNode = pRoot;
	}
	if (pRoot->m_nValue == nSec)
	{
		pSNode = pRoot;
	}
	//递归
	if (pFNode && pSNode)
	{
		return LCA(pFNode,pSNode);
	}
	
	CSNode* pLCALeft = FindLCA(pRoot->m_pFirstChild,nFirst,nSec,pFNode,pSNode);
	CSNode* pLCARight = FindLCA(pRoot->m_pNextSibling,nFirst,nSec,pFNode,pSNode);
	return pLCALeft == NULL ? pLCARight : pLCALeft;
}
条件(3)一般的二叉树 + 自顶向下深度遍历

方法一:深度遍历二叉树,且每到一个结点,都检查该节点是否包含两个结点。

如果本节点包含A和B 且 其左右子树都不包含A和B,则此节点为LCA

如果本节点包含A和B 且 其某个子树也包含A和B,则也深度遍历该子树。

代码:

struct BTNode  
{  
	int m_nValue;  
	BTNode* m_pLeft;  
	BTNode* m_pRight;  
};  
/*求解LCA*/
BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)
{
	BTNode* pLeftTmp = NULL;
	BTNode* pRightTmp = NULL;

	if (!pRoot)
	{
		return NULL;
	}
	
	bool bIsInclude = IsInclude(pRoot,nFirst,nSec);

	if (!bIsInclude)
	{
		return NULL;
	}
	else 
	{
		pLeftTmp = FindLCA(pRoot->m_pLeft,nFirst,nSec);
		pRightTmp = FindLCA(pRoot->m_pRight,nFirst,nSec);
		if (!pLeftTmp && !pRightTmp)
		{
			return pRoot;
		}
		else if (!pLeftTmp && pRightTmp)
		{
			return pRightTmp;
		}
		else
		{
			return pLeftTmp;
		}
	}
}

在上述代码中,没有给出判断一个结点是否包含A和B的代码,这里给出两种方法来判断一个结点是否包含A和B

方法(1)深度遍历,判断结点是否包含A和B

缺点:每确定一个结点是否包含A和B时,都需要一次深度遍历二叉树。

代码:

/*判断结点是否同时包含A和B*/
bool IsInclude(BTNode* pRoot,int nFirst,int nSec,bool& bIsInFir,bool& bIsInSec)
{
	if (!pRoot)
	{
		return false;
	}

	if (pRoot->m_nValue == nFirst)
	{
		bIsInFir = true;
	}
	if (pRoot->m_nValue == nSec)
	{
		bIsInSec = true;
	}

	if (bIsInFir && bIsInSec)
	{
		return true;
	}
	else
	{
		if(IsInclude(pRoot->m_pLeft,nFirst,nSec,bIsInFir,bIsInSec))
		{
			return true;
		}
		else
		{
			return IsInclude(pRoot->m_pRight,nFirst,nSec,bIsInFir,bIsInSec);
		}
	}
}

/*判断结点是否同时包含A和B*/
bool IsInclude(BTNode* pRoot,int nFirst,int nSec)
{
	bool bIsInFir = false;
	bool bIsInSec = false;
	return IsInclude(pRoot,nFirst,nSec,bIsInFir,bIsInSec);
}

方法(2)使用哈希,判断结点是否包含A和B。

我们可以为每一个结点都维护一个哈希,来保存以该节点为根的子树包含的结点。

这样一来,检测某个结点是否包含A和B时,只需要去map检测A和B是否存在即可。

另外,为了节省时间,该Map的构造可以在程序执行前预处理,这样就可以支持在线查询。

缺点:需要为每一个结点维护一个哈希,存在空间浪费。

代码:

void CreateMap(BTNode* pRoot,map<int,map<int,int>>& mapEle)
{
	if (!pRoot)
	{
		return;
	}
	CreateMap(pRoot->m_pLeft,mapEle);
	CreateMap(pRoot->m_pRight,mapEle);
	
	map<int,int> mapTmp;
	mapTmp.insert(pair<int,int>(pRoot->m_nValue,pRoot->m_nValue));
	
	//把俩孩子包含的结点值也给它
	map<int,map<int,int>>::iterator itCur;
	if (pRoot->m_pLeft)
	{
		itCur = mapEle.find(pRoot->m_pLeft->m_nValue);
		mapTmp.insert(itCur->second.begin(),itCur->second.end());
	}
	if (pRoot->m_pRight)
	{
		itCur = mapEle.find(pRoot->m_pRight->m_nValue);
		mapTmp.insert(itCur->second.begin(),itCur->second.end());
	}
	//把该节点的Map插入总Map中
	mapEle[pRoot->m_nValue] = mapTmp;
}

/*判断结点是否同时包含A和B*/
bool IsInclude(map<int,map<int,int>>& mapEle,BTNode* pRoot,int nFirst,int nSec)
{
	if (!pRoot)
	{
		return false;
	}

	map<int,map<int,int>>::iterator itCur;
	itCur = mapEle.find(pRoot->m_nValue);
	if (itCur == mapEle.end())
	{
		return false;
	}
	else
	{
		map<int,int>::iterator it;
		//查找nFirst
		it = itCur->second.find(nFirst);
		if (it == itCur->second.end())
		{
			return false;
		}
		//查找nSec
		it = itCur->second.find(nSec);
		if (it == itCur->second.end())
		{
			return false;
		}
		return true;
	}
}

方法二:边深度遍历,变保存路径

代码:

struct BTNode  
{  
	int m_nValue;  
	BTNode* m_pLeft;  
	BTNode* m_pRight;  
};

/*求解LCA*/
void FindLCA(BTNode* pRoot,int nFirst,int nSec,bool& bIsFindFir,bool& bIsFindSec,vector<BTNode*>& vArrPathFir,vector<BTNode*>& vArrPathSec)
{
	if (!pRoot)
	{
		return;
	}
	if (bIsFindFir && bIsFindSec)
	{
		return;
	}
	
	vArrPathFir.push_back(pRoot);
	vArrPathSec.push_back(pRoot);
	if (pRoot->m_nValue == nFirst)
	{
		bIsFindFir = true;
	}
	if (pRoot->m_nValue == nSec)
	{
		bIsFindSec = true;
	}
	FindLCA(pRoot->m_pLeft,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrPathSec);
	FindLCA(pRoot->m_pRight,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrPathSec);
	
	if (!bIsFindFir)
	{
		vArrPathFir.pop_back();
	}
	if (!bIsFindSec)
	{
		vArrPathSec.pop_back();
	}
}

/*求解LCA*/
BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)
{
	vector<BTNode*> vArrPathFir;
	vector<BTNode*> vArrParhSec;
	bool bIsFindFir = false;
	bool bIsFindSec = false; 
	
	FindLCA(pRoot,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrParhSec);

	int nCount = min(vArrPathFir.size(),vArrParhSec.size());
	int nCur = 0;
	while(nCur < nCount && vArrPathFir[nCur] == vArrParhSec[nCur])
	{
		nCur++;
	}
	return nCount == 0 ? NULL : vArrPathFir[nCur - 1];
}

思路(4)一般的二叉树 + 自底向上

方法:递归回溯时记录是否包含A和B。

如果包含,则是LCA

如果不包含,则把包含信息网上传。

代码:

struct BTNode  
{  
	int m_nValue;
	int m_nFlag; //记录结点是否包含A和B
	
	BTNode* m_pLeft;  
	BTNode* m_pRight;  
};

bool IsIncude(int nFlag)
{
	int nTmpFlag = ~(~0 << 2);
	if ((nFlag & nTmpFlag) == nTmpFlag)
	{
		return true;
	}
	return false;
}

/*求解LCA*/
void FindLCA(BTNode* pRoot,int nFirst,int nSec,BTNode*& pLCA,BTNode* pParent)
{
	//出口
	if (!pRoot)
	{
		return;
	}
	if (pLCA)//已经找到LCA
	{
		return;
	}
	//处理本节点
	if (pRoot->m_nValue == nFirst)
	{
		pRoot->m_nFlag |= 0x1;
	}
	if (pRoot->m_nValue == nSec)
	{
		pRoot->m_nFlag |= 0x2;
	}
	if (IsIncude(pRoot->m_nFlag))
	{
		pLCA = pRoot;
		return;
	}
	//递归入口
	FindLCA(pRoot->m_pLeft,nFirst,nSec,pLCA,pRoot);
	FindLCA(pRoot->m_pRight,nFirst,nSec,pLCA,pRoot);
	
	if (pLCA)//在子树中已经找到LCA,此时包含信息不需要网上传了
	{
		return;
	}
	if (IsIncude(pRoot->m_nFlag))//判断该节点是否是LCA
	{
		pLCA = pRoot;
		return;
	}
	if (pParent)//上传关键字信息
	{
		pParent->m_nFlag |= pRoot->m_nFlag;
	}
}

BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)
{
	BTNode* pLCA = NULL;
	FindLCA(pRoot,nFirst,nSec,pLCA,NULL);
	return pLCA;
}

思路(5)DFS + 并查集(Tarjan算法)

思路(6)DFS + ST算法

该算法组合是一种在线算法。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值