您的位置:首页 > 其它

最近公共祖先

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>
#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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: