三种LCA算法(二):Tarjan算法

来源:互联网 发布:淘宝怎么找类似店铺 编辑:程序博客网 时间:2024/06/04 19:14

Tarjan算法也是一种常用的解决LCA问题的算法,算法复杂度低,O(n + Q),Q为查询次数,基于DFS+并查集离线算法首先读入所有的询问(求一次LCA叫做一次询问),然后在算法执行过程中完成所有查询,把查询结果存储起来,再去根据每次查询直接输出结果),离线算法局限性较大。

算法思路:

Tarjan算法也是基于DFS的,对于新搜索到的一个节点,首先创建由这个节点构成的集合,再对当前节点的每一棵子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA查询都已解决,其他的LCA查询的结果必然在这个子树之外,这时把子树所形成的集合与当前节点的集合合并,并将当前节点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前节点的所有子树搜索完毕。这时把当前节点也设为已被检查过的,同时可以处理有关当前节点的LCA询问,如果有一个从当前节点到结点v的询问,且v已被检查过,则由于进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包含v的子树一定已经搜索过了,那么这个最近公共祖先一定是v所在集合的祖先。

其中关于集合的操作都是使用并查集高效完成,由于并查集的时间复杂度接近常数,所以算法十分高效。

代码:

#include <cstdio>#include <cstring>#include <algorithm>#include <iostream>using namespace std;int n;//树节点数量//前向星存树int cnt1, head1[10005];struct edges {int to, next;}edge[20010];//前向星存查询int cnt2, head2[10005];struct query {int index, to, next;//index为查询点对的编号}q[200010];int answer[100005];//查询结果(查询下标->查询结果)int fa[10005],rank[10005];//并查集int book[10005];//标记是否检查过int ancestor[10005];//祖先  此处fa和ancestor的区别是,fa实际上代表的是一个集合,用于合并后的效果;而ancestor是为了实现算法而指定的集合祖先void addEdge(int x, int y) {//树加边cnt1++;edge[cnt1].to = y;edge[cnt1].next = head1[x];head1[x] = cnt1;}void addQuery(int x, int y, int index) {//添加查询cnt2++;q[cnt2].to = y;q[cnt2].index = index;q[cnt2].next = head2[x];head2[x] = cnt2;}int find(int x) {//并查集查找祖先return fa[x] = x == fa[x] ? x : find(fa[x]);}void merge(int x, int y) {//并查集集合合并int a = find(x);int b = find(y);if (a != b) {if (::rank[a] > ::rank[b]) fa[b] = a;else {fa[a] = b;::rank[b] += ::rank[a] == ::rank[b];}}}void dfs(int x, int fa) {//深搜for (int i = head1[x]; i; i = edge[i].next) {int y = edge[i].to;if (y != fa) dfs(y, x);merge(x, y);//合并集合ancestor[find(x)] = x;//设x为合并后集合的祖先}book[x] = 1;for (int i = head2[x]; i; i = q[i].next) {int v = q[i].to;if (book[v]) {answer[q[i].index] = ancestor[find(v)];//查询结果}}}void init() {cnt1 = 0;memset(head1, 0, sizeof(head1));cnt2 = 0;memset(head2, 0, sizeof(head2));memset(book, 0, sizeof(book));for (int i = 0; i < n; i++) {fa[i] = i;ancestor[i] = i;}memset(::rank, 0, sizeof(::rank));}int main() {int Q;int a, b;while (scanf("%d%d", &n, &Q) != EOF) {init();for (int i = 0; i < n - 1; i++) {scanf("%d%d", &a, &b);addEdge(a, b);addEdge(b, a);}for (int i = 0; i < Q; i++) {scanf("%d%d", &a, &b);addQuery(a, b, i);addQuery(b, a, i);}dfs(0,-1);//设0为根for (int i = 0; i < Q; i++) {printf("%d\n", answer[i]);}}return 0;}


原创粉丝点击