题目
最小公共祖先(Least Common Ancestor)
在一棵有根树T中,顶点x,y的最小公共祖先是x和y在通往根的路径上的第一个汇合点。如下图实例:
顶点5和6的LCA就是2;顶点4和11的LCA是1;顶点10和12的LCA是10等等。
如何求得任意一个顶点对的LCA呢?这需要结合第22章 图的深度优先搜索和本章不相交集合数据结构的知识。根据题目,可知该算法的流程如下:
1、对于顶点u,创建一个仅含它的集合,同时设置u的祖先为自己;
2、深度优先搜索u的每一个孩子,对于孩子v,搜索结束之后,合并集合v和集合u;
3、在合并过程中可能会改变集合的代表,因而紧接着必须保持该集合元素的祖先为u;
4、设置u为已访问,同时检查在所有询问中是否存在与u关联的询问,若有且另一顶点已访问,则输出该顶点对的LCA。
例如对询问(2,5),我们从根开始按序号递增顺序搜索,过程如下:
1、首先运行LCA(1),接着深度搜索2,调用LCA(2),再是LCA(5);
2、由于顶点5没有孩子,DFS结束,其自成一个集合,祖先为自己,被设置为黑;遍历询问,发现询问(2,5),但是2还没有访问完毕,LCA(5)结束,返回LCA(2);
3、合并集合2(此时也只有2)和集合5,并设置该集合祖先为2,继续搜索顶点6,LCA(6)开始;
4、LCA(6)过程和LCA(5)类似,将顶点6设置为黑,结束,返回LCA(2);
5、合并集合(2,5)和集合6,设置该集合祖先为2,设置顶点为黑色,遍历询问;
6、发现询问(2,5),由于顶点5已经访问,则找到5所处集合的根(代表)的祖先,即为2和5的LCA,输出。
C++实现代码如下,UFS的实现请参考用于不相交数据集合的数据结构
#include
#include
#include
#include"DisjointSet.h"
using namespace std;
enum color{ BLACK, WHITE };
struct edgeNode
{
size_t adjvertex;
edgeNode *nextEdge;
edgeNode(size_t f_) :adjvertex(f_), nextEdge(nullptr){}
};
class AGraph
{
private:
vector E;
size_t nodenum;
public:
AGraph(size_t n) :E(n + 1), nodenum(n){}
void initGraph();
void addEdge(size_t, size_t);
edgeNode* search(size_t, size_t);
void print();
void Tarjan_LCA(size_t, UFS&, vector>&, vector&, vector&);
};
void AGraph::initGraph()
{
size_t start, end;
ifstream infile("F:\\lca.txt");
while (infile >> start >> end)
addEdge(start, end);
}
edgeNode* AGraph::search(size_t start, size_t end)
{
edgeNode *curr = E[start];
while (curr != nullptr && curr->adjvertex != end)
curr = curr->nextEdge;
return curr;
}
void AGraph::addEdge(size_t start, size_t end)
{
edgeNode *curr = search(start, end);
if (curr == nullptr)
{
edgeNode *p = new edgeNode(end);
p->nextEdge = E[start];
E[start] = p;
}
}
void AGraph::print()
{
for (size_t i = 1; i != E.size(); ++i)
{
edgeNode *curr = E[i];
cout << i;
if (curr == nullptr) cout << " --> null";
else
while (curr != nullptr)
{
cout << " --> " << curr->adjvertex;
curr = curr->nextEdge;
}
cout << endl;
}
}
void AGraph::Tarjan_LCA(size_t root,UFS &ufs,vector> &P,
vector &c,vector &ancestor)
{//Tarjan算法求最小公共祖先
ufs.makeSet(root);//自成一个集合
ancestor[root] = root;//自为祖先
edgeNode *curr = E[root];
while (curr != nullptr)
{
Tarjan_LCA(curr->adjvertex, ufs, P, c,ancestor);//深度优先搜索每一个孩子
ufs.Union(root, curr->adjvertex);//合并孩子到根的集合中
ancestor[ufs.findSet(root)] = root;//确保合并后的集合的祖先为root
curr = curr->nextEdge;
}
c[root] = BLACK;//标识为已访问
for (size_t i = 0; i != P.size(); ++i)
{//遍历询问
if (root == P[i].first && c[P[i].second] == BLACK)//若存在此询问且另一顶点已访问
cout << P[i].first << " and " << P[i].second << " have LCA: "
<< ancestor[ufs.findSet(P[i].second)] << endl;//输出LCA
}
}
const int nodenum = 12;
int main()
{
AGraph tree(nodenum);
tree.initGraph();
tree.print();
UFS ufs(nodenum + 1);
vector ancestor(nodenum + 1);
vector c(nodenum + 1, WHITE);
ifstream inpair("F:\\pair.txt");
vector> P;
size_t f, s;
while (inpair >> s >> f)
{
P.push_back(pair(s, f));//这样存储,避免遗漏
P.push_back(pair(f, s));//也可以修改一下Tarjan算法中的if语句
}
tree.Tarjan_LCA(1, ufs, P, c, ancestor);
getchar();
return 0;
}