算法导论 21-3 Tarjan的脱机最小公共祖先算法

题目

算法导论 21-3 Tarjan的脱机最小公共祖先算法_第1张图片


最小公共祖先(Least Common Ancestor)

    在一棵有根树T中,顶点x,y的最小公共祖先是x和y在通往根的路径上的第一个汇合点。如下图实例:

算法导论 21-3 Tarjan的脱机最小公共祖先算法_第2张图片

顶点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;
}






你可能感兴趣的:(算法和数据结构,算法导论,Tarjan,最小公共祖先,LCA,不相交数据集合)