最近公共祖先(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->datadatapRight;
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++pParent;
}
pIter1 = pTemp;
pIter2 = pNode2;
}
else
{
bstNode* pTemp = pNode1;
while (k++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
bool nodePath (bstNode* pRoot, int value, std::vector& 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 path1;
std::vector 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
下面说一下本文的题目,也就是POJ1330,用网上流行的LCA算法Tarjan求解(并查集+深搜)。
#include
#include
using namespace std;
const int MAX=17;
int f[MAX];//每个节点所属集合
int r[MAX];//r是rank(秩)合并
int indegree[MAX];//保存每个节点的入度
int visit[MAX];//只有0和1,表示节点是否已处理完毕
vector 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]>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
http://kmplayer.iteye.com/blog/604518
http://blog.csdn.net/lixiandejian/article/details/6661074