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

1 篇文章 0 订阅
1 篇文章 0 订阅

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

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

举例:

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

思路:

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

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

代码:

[cpp]  view plain  copy
  1. struct BTNode  
  2. {  
  3.     int m_nValue;  
  4.     BTNode* m_pLeft;  
  5.     BTNode* m_pRight;  
  6. };  
  7.   
  8. BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)  
  9. {  
  10.     if (pRoot)  
  11.     {  
  12.         if (pRoot->m_nValue > nFirst && pRoot->m_nValue > nSec)  
  13.         {  
  14.             return FindLCA(pRoot->m_pLeft,nFirst,nSec);  
  15.         }  
  16.         else if (pRoot->m_nValue < nFirst && pRoot->m_nValue < nSec)  
  17.         {  
  18.             return FindLCA(pRoot->m_pRight,nFirst,nSec);  
  19.         }  
  20.         else  
  21.         {  
  22.             return pRoot;  
  23.         }  
  24.     }  
  25.     return NULL;  
  26. }  

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

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

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

[cpp]  view plain  copy
  1. struct CSNode  
  2. {  
  3.     int m_nValue;  
  4.     CSNode* m_pParent;  
  5.     CSNode* m_pFirstChild;  
  6.     CSNode* m_pNextSibling;  
  7. };  
  8.   
  9. int NodeCount(CSNode* pNode)  
  10. {  
  11.     assert(pNode);  
  12.     int nLen = 0;  
  13.     while(pNode)  
  14.     {  
  15.         nLen++;  
  16.         pNode = pNode->m_pParent;  
  17.     }  
  18.     return nLen;  
  19. }  
  20.   
  21. CSNode* LCA(CSNode*& pFNode,CSNode*& pSNode)  
  22. {  
  23.     assert(pFNode && pSNode);  
  24.     int nDiff = 0;  
  25.     int nCountFir = NodeCount(pFNode);  
  26.     int nCountSec = NodeCount(pSNode);  
  27.     if (nCountFir > nCountSec)  
  28.     {  
  29.         nDiff = nCountFir - nCountSec;  
  30.         while(nDiff)  
  31.         {  
  32.             pFNode = pFNode->m_pParent;  
  33.             nDiff--;  
  34.         }  
  35.     }  
  36.     else  
  37.     {  
  38.         nDiff = nCountSec - nCountFir;  
  39.         while(nDiff)  
  40.         {  
  41.             pSNode = pSNode->m_pParent;  
  42.             nDiff--;  
  43.         }  
  44.     }  
  45.     assert(pSNode && pFNode);  
  46.     while(pFNode != pSNode)  
  47.     {  
  48.         pFNode = pFNode->m_pParent;  
  49.         pSNode = pSNode->m_pParent;  
  50.     }  
  51.     assert(pFNode);  
  52.     return pFNode;  
  53. }  
  54.   
  55. CSNode* FindLCA(CSNode* pRoot,int nFirst,int nSec,CSNode*& pFNode,CSNode*& pSNode)  
  56. {  
  57.     if (!pRoot)  
  58.     {  
  59.         return NULL;          
  60.     }  
  61.     if (pFNode && pSNode)  
  62.     {  
  63.         return NULL;  
  64.     }  
  65.     //比较值  
  66.     if (pRoot->m_nValue == nFirst)  
  67.     {  
  68.         pFNode = pRoot;  
  69.     }  
  70.     if (pRoot->m_nValue == nSec)  
  71.     {  
  72.         pSNode = pRoot;  
  73.     }  
  74.     //递归  
  75.     if (pFNode && pSNode)  
  76.     {  
  77.         return LCA(pFNode,pSNode);  
  78.     }  
  79.       
  80.     CSNode* pLCALeft = FindLCA(pRoot->m_pFirstChild,nFirst,nSec,pFNode,pSNode);  
  81.     CSNode* pLCARight = FindLCA(pRoot->m_pNextSibling,nFirst,nSec,pFNode,pSNode);  
  82.     return pLCALeft == NULL ? pLCARight : pLCALeft;  
  83. }  
条件(3)一般的二叉树 + 自顶向下深度遍历

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

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

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

代码:

[cpp]  view plain  copy
  1. struct BTNode    
  2. {    
  3.     int m_nValue;    
  4.     BTNode* m_pLeft;    
  5.     BTNode* m_pRight;    
  6. };    
  7. /*求解LCA*/  
  8. BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)  
  9. {  
  10.     BTNode* pLeftTmp = NULL;  
  11.     BTNode* pRightTmp = NULL;  
  12.   
  13.     if (!pRoot)  
  14.     {  
  15.         return NULL;  
  16.     }  
  17.       
  18.     bool bIsInclude = IsInclude(pRoot,nFirst,nSec);  
  19.   
  20.     if (!bIsInclude)  
  21.     {  
  22.         return NULL;  
  23.     }  
  24.     else   
  25.     {  
  26.         pLeftTmp = FindLCA(pRoot->m_pLeft,nFirst,nSec);  
  27.         pRightTmp = FindLCA(pRoot->m_pRight,nFirst,nSec);  
  28.         if (!pLeftTmp && !pRightTmp)  
  29.         {  
  30.             return pRoot;  
  31.         }  
  32.         else if (!pLeftTmp && pRightTmp)  
  33.         {  
  34.             return pRightTmp;  
  35.         }  
  36.         else  
  37.         {  
  38.             return pLeftTmp;  
  39.         }  
  40.     }  
  41. }  

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

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

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

代码:

[cpp]  view plain  copy
  1. /*判断结点是否同时包含A和B*/  
  2. bool IsInclude(BTNode* pRoot,int nFirst,int nSec,bool& bIsInFir,bool& bIsInSec)  
  3. {  
  4.     if (!pRoot)  
  5.     {  
  6.         return false;  
  7.     }  
  8.   
  9.     if (pRoot->m_nValue == nFirst)  
  10.     {  
  11.         bIsInFir = true;  
  12.     }  
  13.     if (pRoot->m_nValue == nSec)  
  14.     {  
  15.         bIsInSec = true;  
  16.     }  
  17.   
  18.     if (bIsInFir && bIsInSec)  
  19.     {  
  20.         return true;  
  21.     }  
  22.     else  
  23.     {  
  24.         if(IsInclude(pRoot->m_pLeft,nFirst,nSec,bIsInFir,bIsInSec))  
  25.         {  
  26.             return true;  
  27.         }  
  28.         else  
  29.         {  
  30.             return IsInclude(pRoot->m_pRight,nFirst,nSec,bIsInFir,bIsInSec);  
  31.         }  
  32.     }  
  33. }  
  34.   
  35. /*判断结点是否同时包含A和B*/  
  36. bool IsInclude(BTNode* pRoot,int nFirst,int nSec)  
  37. {  
  38.     bool bIsInFir = false;  
  39.     bool bIsInSec = false;  
  40.     return IsInclude(pRoot,nFirst,nSec,bIsInFir,bIsInSec);  
  41. }  

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

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

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

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

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

代码:

[cpp]  view plain  copy
  1. void CreateMap(BTNode* pRoot,map<int,map<int,int>>& mapEle)  
  2. {  
  3.     if (!pRoot)  
  4.     {  
  5.         return;  
  6.     }  
  7.     CreateMap(pRoot->m_pLeft,mapEle);  
  8.     CreateMap(pRoot->m_pRight,mapEle);  
  9.       
  10.     map<int,int> mapTmp;  
  11.     mapTmp.insert(pair<int,int>(pRoot->m_nValue,pRoot->m_nValue));  
  12.       
  13.     //把俩孩子包含的结点值也给它  
  14.     map<int,map<int,int>>::iterator itCur;  
  15.     if (pRoot->m_pLeft)  
  16.     {  
  17.         itCur = mapEle.find(pRoot->m_pLeft->m_nValue);  
  18.         mapTmp.insert(itCur->second.begin(),itCur->second.end());  
  19.     }  
  20.     if (pRoot->m_pRight)  
  21.     {  
  22.         itCur = mapEle.find(pRoot->m_pRight->m_nValue);  
  23.         mapTmp.insert(itCur->second.begin(),itCur->second.end());  
  24.     }  
  25.     //把该节点的Map插入总Map中  
  26.     mapEle[pRoot->m_nValue] = mapTmp;  
  27. }  
  28.   
  29. /*判断结点是否同时包含A和B*/  
  30. bool IsInclude(map<int,map<int,int>>& mapEle,BTNode* pRoot,int nFirst,int nSec)  
  31. {  
  32.     if (!pRoot)  
  33.     {  
  34.         return false;  
  35.     }  
  36.   
  37.     map<int,map<int,int>>::iterator itCur;  
  38.     itCur = mapEle.find(pRoot->m_nValue);  
  39.     if (itCur == mapEle.end())  
  40.     {  
  41.         return false;  
  42.     }  
  43.     else  
  44.     {  
  45.         map<int,int>::iterator it;  
  46.         //查找nFirst  
  47.         it = itCur->second.find(nFirst);  
  48.         if (it == itCur->second.end())  
  49.         {  
  50.             return false;  
  51.         }  
  52.         //查找nSec  
  53.         it = itCur->second.find(nSec);  
  54.         if (it == itCur->second.end())  
  55.         {  
  56.             return false;  
  57.         }  
  58.         return true;  
  59.     }  
  60. }  

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

代码:

[cpp]  view plain  copy
  1. struct BTNode    
  2. {    
  3.     int m_nValue;    
  4.     BTNode* m_pLeft;    
  5.     BTNode* m_pRight;    
  6. };  
  7.   
  8. /*求解LCA*/  
  9. void FindLCA(BTNode* pRoot,int nFirst,int nSec,bool& bIsFindFir,bool& bIsFindSec,vector<BTNode*>& vArrPathFir,vector<BTNode*>& vArrPathSec)  
  10. {  
  11.     if (!pRoot)  
  12.     {  
  13.         return;  
  14.     }  
  15.     if (bIsFindFir && bIsFindSec)  
  16.     {  
  17.         return;  
  18.     }  
  19.       
  20.     vArrPathFir.push_back(pRoot);  
  21.     vArrPathSec.push_back(pRoot);  
  22.     if (pRoot->m_nValue == nFirst)  
  23.     {  
  24.         bIsFindFir = true;  
  25.     }  
  26.     if (pRoot->m_nValue == nSec)  
  27.     {  
  28.         bIsFindSec = true;  
  29.     }  
  30.     FindLCA(pRoot->m_pLeft,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrPathSec);  
  31.     FindLCA(pRoot->m_pRight,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrPathSec);  
  32.       
  33.     if (!bIsFindFir)  
  34.     {  
  35.         vArrPathFir.pop_back();  
  36.     }  
  37.     if (!bIsFindSec)  
  38.     {  
  39.         vArrPathSec.pop_back();  
  40.     }  
  41. }  
  42.   
  43. /*求解LCA*/  
  44. BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)  
  45. {  
  46.     vector<BTNode*> vArrPathFir;  
  47.     vector<BTNode*> vArrParhSec;  
  48.     bool bIsFindFir = false;  
  49.     bool bIsFindSec = false;   
  50.       
  51.     FindLCA(pRoot,nFirst,nSec,bIsFindFir,bIsFindSec,vArrPathFir,vArrParhSec);  
  52.   
  53.     int nCount = min(vArrPathFir.size(),vArrParhSec.size());  
  54.     int nCur = 0;  
  55.     while(nCur < nCount && vArrPathFir[nCur] == vArrParhSec[nCur])  
  56.     {  
  57.         nCur++;  
  58.     }  
  59.     return nCount == 0 ? NULL : vArrPathFir[nCur - 1];  
  60. }  

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

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

如果包含,则是LCA

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

代码:

[cpp]  view plain  copy
  1. struct BTNode    
  2. {    
  3.     int m_nValue;  
  4.     int m_nFlag; //记录结点是否包含A和B  
  5.       
  6.     BTNode* m_pLeft;    
  7.     BTNode* m_pRight;    
  8. };  
  9.   
  10. bool IsIncude(int nFlag)  
  11. {  
  12.     int nTmpFlag = ~(~0 << 2);  
  13.     if ((nFlag & nTmpFlag) == nTmpFlag)  
  14.     {  
  15.         return true;  
  16.     }  
  17.     return false;  
  18. }  
  19.   
  20. /*求解LCA*/  
  21. void FindLCA(BTNode* pRoot,int nFirst,int nSec,BTNode*& pLCA,BTNode* pParent)  
  22. {  
  23.     //出口  
  24.     if (!pRoot)  
  25.     {  
  26.         return;  
  27.     }  
  28.     if (pLCA)//已经找到LCA  
  29.     {  
  30.         return;  
  31.     }  
  32.     //处理本节点  
  33.     if (pRoot->m_nValue == nFirst)  
  34.     {  
  35.         pRoot->m_nFlag |= 0x1;  
  36.     }  
  37.     if (pRoot->m_nValue == nSec)  
  38.     {  
  39.         pRoot->m_nFlag |= 0x2;  
  40.     }  
  41.     if (IsIncude(pRoot->m_nFlag))  
  42.     {  
  43.         pLCA = pRoot;  
  44.         return;  
  45.     }  
  46.     //递归入口  
  47.     FindLCA(pRoot->m_pLeft,nFirst,nSec,pLCA,pRoot);  
  48.     FindLCA(pRoot->m_pRight,nFirst,nSec,pLCA,pRoot);  
  49.       
  50.     if (pLCA)//在子树中已经找到LCA,此时包含信息不需要网上传了  
  51.     {  
  52.         return;  
  53.     }  
  54.     if (IsIncude(pRoot->m_nFlag))//判断该节点是否是LCA  
  55.     {  
  56.         pLCA = pRoot;  
  57.         return;  
  58.     }  
  59.     if (pParent)//上传关键字信息  
  60.     {  
  61.         pParent->m_nFlag |= pRoot->m_nFlag;  
  62.     }  
  63. }  
  64.   
  65. BTNode* FindLCA(BTNode* pRoot,int nFirst,int nSec)  
  66. {  
  67.     BTNode* pLCA = NULL;  
  68.     FindLCA(pRoot,nFirst,nSec,pLCA,NULL);  
  69.     return pLCA;  
  70. }  

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

思路(6)DFS + ST算法

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值