最近公共祖先(LCA)問題常見於各種面試題中,針對不同情況算法也不盡相同。
情況1:二叉樹是個二叉查找樹,且root和兩個節點的值(a, b)已知。
如果該二叉樹是二叉查找樹,那麼求解LCA十分簡單。
基本思想爲:從樹根開始,該節點的值爲t,如果t大於t1和t2,說明t1和t2都位於t的左側,所以它們的共同祖先必定在t的左子樹中,從t.left開始搜索;如果t小於t1和t2,說明t1和t2都位於t的右側,那麼從t.right開始搜索;如果t1<=t<= t2,說明t1和t2位於t的兩側(或t=t1,或t=t2),那麼該節點t爲公共祖先。
bstNode* LCA(bstNode* pNode, int value1, int value2)
{
bstNode* pTemp = pNode;
while (pTemp)
{
if (pTemp->data>value1 && pTemp->data>value2)
pTemp = pTemp->pLeft;
else if(pTemp->data<value1 && pTemp->data<value2)
pTemp = pTemp->pRight;
else
return pTemp;
}
return NULL;
}
情況2:普通二叉樹,root未知,但是每個節點都有parent指針。
基本思想:分別從給定的兩個節點出發上溯到根節點,形成兩條相交的鏈表,問題轉化爲求這兩個相交鏈表的第一個交點,即傳統方法:求出linkedList A的長度lengthA, linkedList B的長度LengthB。然後讓長的那個鏈表走過abs(lengthA-lengthB)步之後,齊頭並進,就能解決了。
int getLength (bstNode* pNode)
{
int length = 0;
bstNode* pTemp = pNode;
while (pTemp)
{
length ++ ;
pTemp = pTemp->pParent;
}
return length;
}
bstNode* LCAC(bstNode* pNode1, bstNode* pNode2)
{
int length1 = getLength(pNode1);
int length2 = getLength(pNode2);
// skip the abs(length1-length2)
bstNode* pIter1 = NULL;
bstNode* pIter2 = NULL;
int k=0;
if (length1>=length2)
{
bstNode* pTemp = pNode1;
while (k++<length1-length2)
{
pTemp = pTemp->pParent;
}
pIter1 = pTemp;
pIter2 = pNode2;
}
else
{
bstNode* pTemp = pNode1;
while (k++<length2-length1)
{
pTemp = pTemp->pParent;
}
pIter1 = pNode1;
pIter2 = pTemp;
}
while (pIter1&&pIter2 && pIter1!= pIter2)
{
pIter1 = pIter1->pParent;
pIter2 = pIter2->pParent;
}
return pIter1;
}
情況3:也是最普通的情況,二叉樹是普通的二叉樹,節點只有left/right,沒有parent指針。
10
/ /
6 14
/ / / /
4 8 12 16
/ /
3 5
基本思想:記錄從根找到node1和node2的路徑,然後再把它們的路徑用類似的情況一來做分析,比如還是node1=3,node2=8這個case.我們肯定可以從根節點開始找到3這個節點,同時記錄下路徑3,4,6,10,類似的我們也可以找到8,6,10。我們把這樣的信息存儲到兩個vector裏面,把長的vector開始的多餘節點3扔掉,從相同剩餘長度開始比較,4!=8, 6==6,我們找到了我們的答案。
#include <vector>
bool nodePath (bstNode* pRoot, int value, std::vector<bstNode*>& path)
{
if (pRoot==NULL) return false;
if (pRoot->data!=value)
{
if (nodePath(pRoot->pLeft,value,path))
{
path.push_back(pRoot);
return true;
}
else
{
if (nodePath(pRoot->pRight,value,path))
{
path.push_back(pRoot);
return true;
}
else
return false;
}
}
else
{
path.push_back(pRoot);
return true;
}
}
bstNode* LCAC(bstNode* pNode, int value1, int value2)
{
std::vector<bstNode*> path1;
std::vector<bstNode*> path2;
bool find = false;
find |= nodePath(pNode, value1, path1);
find &= nodePath(pNode, value2, path2);
bstNode* pReturn=NULL;
if (find)
{
int minSize = path1.size()>path2.size()?path2.size():path1.size();
int it1 = path1.size()-minSize;
int it2 = path2.size()-minSize;
for (;it1<path1.size(),it2<path2.size();it1++,it2++)
{
if (path1[it1]==path2[it2])
{
pReturn = path1[it1];
break;
}
}
}
return pReturn;
}
下面說一下本文的題目,也就是POJ1330,用網上流行的LCA算法Tarjan求解(並查集+深搜)。
#include <vector>
#include <iostream>
using namespace std;
const int MAX=17;
int f[MAX];//每個節點所屬集合
int r[MAX];//r是rank(秩)合併
int indegree[MAX];//保存每個節點的入度
int visit[MAX];//只有0和1,表示節點是否已處理完畢
vector<int> tree[MAX], Qes[MAX];//數,待查詢的節點組合
int ancestor[MAX];//祖先集合
void init(int n)//初始化
{
for(int i=1; i<=n; i++)
{
r[i]=1;//初始秩爲1
f[i]=i;//每個節點的父節點初始爲自身
indegree[i]=0;
visit[i]=0;
ancestor[i]=0;
tree[i].clear();
Qes[i].clear();
}
}
int find(int n)//查找n所在集合,並壓縮路徑
{
if(f[n]==n)
return n;
else
f[n]=find(f[n]);
return f[n];
}
int Union(int x, int y)//合併函數,若屬於同一分支則返回0,成功合併返回1
{
int a=find(x);
int b=find(y);
if(a==b)
return 0;
else if(r[a]<r[b])
{
f[a]=b;
r[b]+=r[a];
}
else
{
f[b]=a;
r[a]+=r[b];
}
return 1;
}
void LCA(int u)//tarjan求最近公共祖先
{
ancestor[u]=u;
int size=tree[u].size();
//一個一個子節點處理
for(int i=0; i<size; i++)
{
LCA(tree[u][i]);
Union(u, tree[u][i]);
ancestor[find(u)]=u;
}
//處理完子節點,置visit[u]=1
visit[u]=1;
//求當前節點與有關的節點的最近公共祖先
size=Qes[u].size();
for(i=0; i<size; i++)
{
if(visit[Qes[u][i]]==1)//如果這個節點已處理過
{
cout<<ancestor[find(Qes[u][i])]<<endl;
continue;
}
}
}
int main()
{
int n=16;//樹的總節點
init(n);
int s, t;
//構造樹
tree[8].push_back(5); indegree[5]++;
tree[8].push_back(4); indegree[4]++;
tree[8].push_back(1); indegree[1]++;
tree[5].push_back(9); indegree[9]++;
tree[4].push_back(6); indegree[6]++;
tree[4].push_back(10); indegree[10]++;
tree[1].push_back(14); indegree[14]++;
tree[1].push_back(13); indegree[13]++;
tree[6].push_back(15); indegree[15]++;
tree[6].push_back(7); indegree[7]++;
tree[10].push_back(11); indegree[11]++;
tree[10].push_back(16); indegree[16]++;
tree[10].push_back(2); indegree[2]++;
tree[16].push_back(3); indegree[3]++;
tree[16].push_back(12); indegree[12]++;
//輸入要查詢最近公共祖先的兩個節點
cin>>s>>t;
//如果s在t左邊,那麼在遍歷完s時還不能求得LCA,所以這裏相當於訪問兩次,在訪問t時即可求得結果
Qes[s].push_back(t);
Qes[t].push_back(s);
for(int i=1; i<=n; i++)
{
//尋找根節點
if(indegree[i]==0)//根節點的入度爲0
{
LCA(i);
break;
}
}
return 0;
}
感謝以下參考:
http://poj.org/problem?id=1330
http://apps.hi.baidu.com/share/detail/16279376