最近公共祖先(LCA):并查集+深搜
来源:互联网 发布:淘宝累积消量是多久的 编辑:程序博客网 时间:2024/05/17 08:01
最近公共祖先(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
http://kmplayer.iteye.com/blog/604518
http://blog.csdn.net/lixiandejian/article/details/6661074
- 【POJ1330】最近公共祖先(LCA):并查集+深搜
- 【POJ1330】最近公共祖先(LCA):并查集+深搜
- 最近公共祖先(LCA):并查集+深搜
- LCA(最近公共祖先)离线算法Tarjan+并查集
- HIHO #1067 : 最近公共祖先·二(并查集+dfs LCA离线算法)
- 最近公共祖先tarjan+并查集
- POJ1330 Nearest Common Ancestors(最近公共祖先LCA 并查集+DFS)
- 最近公共祖先(LCA)——离线Tarjan算法+并查集优化
- LCA(最近公共祖先)
- 【最近公共祖先(LCA)】
- 最近公共祖先(LCA)
- 最近公共祖先(LCA)
- LCA(最近公共祖先)
- LCA----【模板】最近公共祖先(LCA)
- 最近公共祖先LCA
- 最近公共祖先(LCA)
- Lca 最近公共祖先
- LCA----最近公共祖先
- 使用Spring配置事务时遇到的问题
- 设计模式之ChainOfResponsibility:过滤Request和Response
- UI 动画之CALayer+CAAnimation 之 CAKeyframeAnimation
- 垂直居中
- Java集合框架常用类源码分析与总结
- 最近公共祖先(LCA):并查集+深搜
- 充电宝质量良莠不齐 请谨慎选择
- 【Python】Learn Python the hard way, ex3 运算符,浮点数
- UIKit学习笔记2
- 拿什么拯救你,“闯祸”的充电宝
- Python正则表达式学习小结
- ubuntu系统与win 7系统时间不一致问题
- i/o notes
- 【基础练习】【模拟】codevs1704 卡片游戏题解