您的位置:首页 > 其它

树中两个结点的最低公共祖先(超全解&&拓展)

2016-06-22 00:17 435 查看
题目:给出两个结点A和B,求解这两个结点的最低公共祖先(LCA)
前提:为了更好的说明思路,这里让问题简单化,假设树是二叉树且树中只含有一个A和一个B。
举例:
条件(1)树为二叉搜索树
思路:
如果树为二叉搜索树且树中必须包含给出的两个元素,可以利用二叉搜索树的性质来做。
如果树中不包含给出的两个元素,输出的结果会是错误的。
代码:

[cpp] view
plain copy

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)一般的二叉树 + 有指向父亲的指针

思路:规划为两个相交链表求交集。
代码:树使用兄弟孩子链表法存储。

[cpp] view
plain copy

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,则也深度遍历该子树。
代码:

[cpp] view
plain copy

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时,都需要一次深度遍历二叉树。

代码:

[cpp] view
plain copy

/*判断结点是否同时包含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的构造可以在程序执行前预处理,这样就可以支持在线查询。
缺点:需要为每一个结点维护一个哈希,存在空间浪费。
代码:

[cpp] view
plain copy

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;

}

}

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

[cpp] view
plain copy

struct BTNode

{

int m_nValue;

BTNode* m_pLeft;

BTNode* m_pRight;

};

/*求解LCA*/

void FindLCA(BTNode* pRoot,int nFirst,int nSec,bool& bIsFindFir,bool& bIsFindS
1a5ab
ec,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)一般的[b]二叉树 + 自底向上[/b]
方法:递归回溯时记录是否包含A和B。
如果包含,则是LCA
如果不包含,则把包含信息网上传。
代码:

[cpp] view
plain copy

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算法
该算法组合是一种在线算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: