题目:给出两个结点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算法
该算法组合是一种在线算法。