最近公共祖先
2014-08-13 14:46
316 查看
主要是按照offer上的例子,
1.若是二叉搜索树
大小就可以判定
2.若是节点有指针指向父节点
转换成链表找公共节点第一个
3.若是一般的树,无多余指针,不止二叉
先是找到两条path,然后从paths中找到第一个两个节点相等,但是下一个不等的位置
bool GetPath(TreeNode* root, TreeNode* node, vector<TreeNode* >& path )
{
if(root == node)
return true;
path.push_back(root);
bool found = false;
vector<TreeNode* >:: iterator it = root->children.begin();
while(!found && it != root->children.end())
{
GetPath(*it,node,path);
}
if(!found)
path.pop_back();
return found;
}
另有方法:
node* getLCA(node* root, node* node1, node* node2)
{
if(root == null)
return null;
if(root== node1 || root==node2)
return root;
node* left = getLCA(root->left, node1, node2);
node* right = getLCA(root->right, node1, node2);
if(left != null && right != null)
return root;
else if(left != null)
return left;
else if (right != null)
return right;
else
return null;
}
一次询问O(N),两次2*O(N)
然不论是针对普通的二叉树,还是针对二叉查找树,上面的解法有一个很大的弊端就是:如需N 次查询,则总体复杂度会扩大N 倍,故这种暴力解法仅适合一次查询,不适合多次查询。
接下来的解法,将不再区别对待是否为二叉查找树,而是一致当做是一棵普通的二叉树。总体来说,由于可以把LCA问题看成是询问式的,即给出一系列询问,程序对每一个询问尽快做出反应。故处理这类问题一般有两种解决方法:
一种是在线算法,相当于循序渐进处理;
另外一种则是离线算法,如Tarjan算法,相当于一次性批量处理,一开始就知道了全部查询,只待询问。
4.更快的算法Tarjan离线算法
最近公共祖先lca 离线算法/Tarjan算法
方法举例说明:
1
/ \
2 3
/ \
4 5
/ /
7 8
/
9
查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点);
查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(9)=2;
查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先;
我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。
下面是专业说明,反正我是没看懂。。
利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,
再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,
同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,
且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,
而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。
#include<iostream>
5. RMQ区间树见july http://blog.csdn.net/v_july_v/article/details/18312089
1.若是二叉搜索树
大小就可以判定
2.若是节点有指针指向父节点
转换成链表找公共节点第一个
3.若是一般的树,无多余指针,不止二叉
先是找到两条path,然后从paths中找到第一个两个节点相等,但是下一个不等的位置
bool GetPath(TreeNode* root, TreeNode* node, vector<TreeNode* >& path )
{
if(root == node)
return true;
path.push_back(root);
bool found = false;
vector<TreeNode* >:: iterator it = root->children.begin();
while(!found && it != root->children.end())
{
GetPath(*it,node,path);
}
if(!found)
path.pop_back();
return found;
}
另有方法:
node* getLCA(node* root, node* node1, node* node2)
{
if(root == null)
return null;
if(root== node1 || root==node2)
return root;
node* left = getLCA(root->left, node1, node2);
node* right = getLCA(root->right, node1, node2);
if(left != null && right != null)
return root;
else if(left != null)
return left;
else if (right != null)
return right;
else
return null;
}
一次询问O(N),两次2*O(N)
然不论是针对普通的二叉树,还是针对二叉查找树,上面的解法有一个很大的弊端就是:如需N 次查询,则总体复杂度会扩大N 倍,故这种暴力解法仅适合一次查询,不适合多次查询。
接下来的解法,将不再区别对待是否为二叉查找树,而是一致当做是一棵普通的二叉树。总体来说,由于可以把LCA问题看成是询问式的,即给出一系列询问,程序对每一个询问尽快做出反应。故处理这类问题一般有两种解决方法:
一种是在线算法,相当于循序渐进处理;
另外一种则是离线算法,如Tarjan算法,相当于一次性批量处理,一开始就知道了全部查询,只待询问。
4.更快的算法Tarjan离线算法
最近公共祖先lca 离线算法/Tarjan算法
方法举例说明:
1
/ \
2 3
/ \
4 5
/ /
7 8
/
9
查询(4,9):到4时,由于vis[9]=0,所以继续;到9后,最近公共祖先就是f[4]=4(回溯的时候记录父节点);
查询(9,8):深度优先搜索,所以到8时才询问;要到8必须回溯到2,在进到8这个子树,所以以记录f[9]=7;f[7]=4;f[4]=2;f[2]=2;所以find(9)=2;
查询(8,3):跟上条相似,必须回溯1才能到3,而find(8)=1就是最近公共祖先;
我们可以发现,对于查询的两点,都要在先查询到的点开始,回溯到最近公共祖先,才查询相对应的点。这也就是离线算法的思路了。。这是我自己理解的。。
下面是专业说明,反正我是没看懂。。
利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。
Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,
再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。
其他的LCA询问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。
之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,
同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,
且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,
而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。
#include<iostream>
#include<vector>
using namespace std;
vector< int> f;//每个节点所属集合 vector< int> r;//每个节点的秩 vector< int> indegree;//每个节点的入度 vector<int> visit;//只有0,1表示节点是否已处理完毕 vector<vector<int> > tree,Qes;//前者是树的边集合,后者是查询节点集合 vector<int> ancestor;//祖先集合 void init(int n) { vector<int> temp; f.clear(); r.clear(); indegree.clear(); visit.clear(); ancestor.clear(); if(tree.size() != 0) { int x = tree.size()-1;//第二个错误点,size_t是无符号整型,所以不能表示-1,x>=0在实际-1处任然执行 for(; x >= 0 ; -- x) tree[x].clear();//tree是vector<vector<int> >所以除了内部clear(),外部也clear(); tree.clear(); } if(Qes.size() != 0) { int x = Qes.size()-1; for(; x >= 0 ; -- x) Qes[x].clear(); Qes.clear(); } for(int i = 0; i <= n; ++ i) { f.push_back(i); r.push_back(1); indegree.push_back(0); visit.push_back(0); tree.push_back(temp); Qes.push_back(temp); ancestor.push_back(i); } } int find(int n) { if(n == f ) return n; else f = find(f ); return f ; } int Union(int x, int y) { int a = f[x]; int b = f[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, vector<int> &res)//Tarjan { for(size_t i = 0; i < tree[u].size(); ++ i) { LCA(tree[u][i],res);//dfs,后序 Union(u, tree[u][i]); ancestor[find(u)] = u;//每次Union更新的都是集合的代表,也就是会按照秩形成并查集的树,需要在每次对子树进行处理后更新他的代表的祖先为根节点 } visit[u] = 1; for(size_t i = 0; i < Qes[u].size(); ++ i) { if(1 == visit[Qes[u][i]]) res.push_back(ancestor[find(Qes[u][i])]); } } int main() { int testnum = 0; cin>>testnum;//输入测试数目 vector<int> results; while(testnum -- )//进入第一个测试的输入集合 { int nodeNum = 0; cin>>nodeNum;//输入测试的节点个数 init(nodeNum); int i=0, j=0, k = nodeNum-1;//第一点错误nodeNum因为下面还要使用所以先应该赋值给一个临时变量 while(k--)//依次输入条边 { cin>>i>>j; tree[i].push_back(j); ++ indegree[j]; } cin>>i>>j;//输入要判断最近公共祖先的两个节点 Qes[i].push_back(j); Qes[j].push_back(i); for(int x = 1; x <= nodeNum; ++x ) { if(0 == indegree[x])//入度为0是根节点 { LCA(x, results);//因为最后输出所有结果,results保存中间结果 break; } } } for(size_t x = 0; x < results.size(); ++ x) cout<<results[x]<<endl; }
5. RMQ区间树见july http://blog.csdn.net/v_july_v/article/details/18312089
相关文章推荐
- Timus 1329. Galactic History。LCA最近公共祖先或dfs递归离线处理!
- 最近公共祖先
- lintcode-最近公共祖先-88
- Hdu 5454,Minimum Cut,最近公共祖先+dfs遍历
- 树上倍增求LCA(最近公共祖先)
- hiho刷题日记——第十七天最近公共祖先·三
- hiho 17 最近公共祖先(三) 转换为区间最值查找 ST
- 最近公共祖先(LCA)
- 4.7---最近公共祖先(CC150)
- lintcode--最近公共祖先
- day15之求二叉树中两个节点的最近公共祖先
- LCA最近公共祖先算法
- poj 1986 最近公共祖先 (lca 倍增)
- 如何求树中的两个节点的最近公共祖先?
- LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现
- LCA-最近公共祖先-Tarjan解法
- 最近公共祖先 LCA
- 求解二叉树中两个节点的最近公共祖先(LCA)
- LCA(最近公共祖先)倍增算法
- 二叉搜索数求最近公共祖先