题目:给出两个结点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;
- }
方法一:深度遍历二叉树,且每到一个结点,都检查该节点是否包含两个结点。
如果本节点包含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算法
该算法组合是一种在线算法。