LCA(最近公共祖先) 的两种求解方法

LCA,即最近公共祖先,是图论中关于树的一个非常重要的定义。

在一棵树中,若节点Z既是节点X的祖先节点,又是节点Y的祖先节点,那么称节点Z是节点X和Y的公共祖先节点。

两种解法: 

  • Ans1

    首先记录树中每个节点的深度,如果节点xy的深度不一样,将深度调整为同一深度(让更深的节点用和浅节点深度相同的它的祖先节点代替),然后从这两个点开始想上扫描整棵树,每次上升单位为1的高度,知道两个节点第一次到达同一个节点,那么这个节点就是两个节点的最近公共祖先。时间复杂度O(N)

  • Ans2​​​​​​

    首先进行预处理,f[i][j]表示i节点的1<<j辈祖先,如果j=0,那么就是i节点的直接祖先,即父亲节点,可以得到一个关系,f[i][j] = f[f[i][j - 1]][j - 1],自己画一下图就得出来,不作详细说明。类似于一个dp的递推式,可以用两个循环来进行预处理,而且在遍历辈数的时候,只需要遍历到log_{2}(N)即可,预处理的复杂度为O(Nlog(N)),然后也是记录每个节点的深度,调整连个点的深度一致,对每次循环,采用倍增的思想,首先尝试两个点的k辈祖先,是否可行,(k从log(N)开始log(N)开始递减),如果抵达的点不同,说明未寻到LCA,则让x=f[x][k] ,y=f[y][k],最后结束的时候,xy必定只差一步,所以答案就是f[x][0],每次询问的复杂度是O(log(N)),总体复杂度是O(Nlog(N))

其实还有第三种方法,可以用tarjan算法优化第一种情况,但是前提条件是需要将询问离线,算法时间复杂度是O(N) 的,以后有机会了补上吧。

// LCA O(Nlog(N))
#include <bits/stdc++.h>
#pragma warning (disable:6031)
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 310;
int head[N], nex[N], to[N];
int f[N][N]; //倍增思想,依然用不到N * N的区间,大概是N * (log(N) / log(2))的大小就够了
int cnt;
int d[N];
typedef struct build_tree {
	build_tree() {
		cnt = 0;
		mem(head, -1);
		mem(nex, -1);
		mem(f, -1);
		mem(d, 0);
	}
	void init() {
		build_tree();
	}
	void add(int x, int y) {
		cnt++;
		nex[cnt] = head[x];
		head[x] = cnt;
		to[cnt] = y;
	}
}bt;
int n; // 树上n个点,n - 1条边
void dfs(int x, int depth) {
	// 计算深度
	d[x] = depth;
	for (int i = head[x]; i != -1; i = nex[i]) {
		int y = to[i];
		dfs(y, depth + 1);
	}
}
int ans(int x, int y) {
	// 查询x和y的lca
	if (d[x] < d[y])swap(x, y);
	// x 比 y 深
	for (int i = log(n) / log(2); i >= 0; i--) {
		if (d[f[x][i]] >= d[y])x = f[x][i];
	}
	if (x == y)return x;
	for (int i = log(n) / log(2); i >= 0; i--) {
		if (f[x][i] != f[y][i])x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
int main()
{
	bt t;
	t.init();
	scanf("%d", &n);
	for (int i = 1; i < n; i++) {
		int a, b;
		scanf("%d %d", &a, &b);
		t.add(a, b);
		f[b][0] = a;
		// 步长为0就是直接祖先,父亲节点
	}
	int tt = log(n) / log(2);
	for (int i = 1; i <= tt; i++) {
		for (int j = 1; j <= n; j++) {
			f[j][i] = f[f[j][i - 1]][i - 1];
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值