LCA <O(N logN, O(logN)>

参考资料:http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor#Another

这里加上了RQM转LCA,可以参考RMQ转换成LCA <O(N), O(1)>》

这个算法的思想与上一篇LCA <O(N), O(sqrt(N))>》的思想一样:

1、求出树中每个节点的lev(树中的高度)以及每个节点的pare(节点的父亲)。

2、假设求a和b,当lev不同时,将lev大,即深一些的节点往上移,即转换成该节点的“父亲”,a=pare[a]。直到两个点的lev相同。此时,若a==b,实际上就是b就是a的祖先。

3、若此时a!=b,a、b、a和b的共同祖先c,这3点,就好比构成了一个“人”字型,a和b的lev相同。这时,可以采用逐步一层一层地往上跳,逼近公共祖先,但这样显然效率比较低。在这种算法里,先预处理一个p数组,p[i][j]表示i的第2^j(可恶的参考资料里掉了这个"^"符号,看了好久没懂)个祖先,转移方程为p[i][j] = p[p[i][j-1]][j-1],翻译一下意思就是,i的第2^j个祖先等于i的第2^(j-1)个祖先的第2^(j-1)个祖先,明白没?

在看一下的代码之前,再来看看强大的二进制与位运算:

设,a = 1101,b = 100,那么a>b,从a变到b,可以这么做:首先求出不大于a的最大2的阶数k,在这里,k就为3(2^3 < a < 2^4)。然后,i从k开始,依次减去1<<i,若减去后>=b,则a = a - (1<<i);若减去后<b则i--。代码如下:

int a, b, k;
k = (int)(log(n)/log(2)); //不大于n的最大的2的阶数。即 2^k <= n < 2^(k+1)
for(i=k; i>=0; i--)
	if(a - (1<<i) >= b)
		a = a - (1<<i);
这样,a就变到了b……
a的x(这里x不一定为整数,可能为小书)次方等于b,即 a^x = b,两边取log,即log(a^x) = log(b) --->  x*log(a) = log(b) ---> x = log(b)/log(a),再取个整的话,就表示不大于b的最大的a的阶数

#include "stdio.h"
#include "string.h"
#include "math.h"
#include "stdlib.h"

#define M 100

/*            0  1  2   3   4   5   6  7   8  9*/
int num[M] = {3, 9, 1, 33, 20, 18, 52, 0, 10, 7};
int n = 10;

typedef struct _Node{
	int pos;
	struct _Node* lf;
	struct _Node* rt;
}Node, *pNode;

pNode st[M];	//stack	
int top;

int pare[M];	//parent
int lev[M];		//level

pNode RMC_2_LCA(){
	int i, k;
	pNode w;
	top = -1;
	for(i=0; i<n; i++){
		w = (pNode)malloc(sizeof(Node));
		w->pos = i;
		w->lf = w->rt = 0;

		k = top;
		while(k>=0 && num[i]<num[st[k]->pos])
			k--;
		if(k>=0)
			st[k]->rt = w;
		if(k<top)
			w->lf = st[k+1];
		st[++k] = w;
		top = k;
	}
	return st[0];
}

void DFS(pNode r, int l){
	lev[r->pos] = l;
	if(r->lf){
		pare[r->lf->pos] = r->pos;
		DFS(r->lf, l+1);
	}
	if(r->rt){
		pare[r->rt->pos] = r->pos;
		DFS(r->rt, l+1);
	}
}

/*前面是RMC转LCA,后面就是LCA的操作……*/

int p[M][M/2];  //p[i][j]表示下标为i的节点的第2^j个祖先,例如,p[i][0]表示第2^0=1,即,i的爹

/*DP预处理,求出所有节点的第2^j个祖先*/
void process(){
	int i, j, k;
	memset(p, -1, sizeof(p));
	for(i=0; i<n; i++)
		p[i][0] = pare[i];
	k = (int)(log(n)/log(2)); //不大于n的最大的2的阶数。即 2^k <= n < 2^(k+1)
	for(j=1; j<=k; j++){
		for(i=0; i<n; i++){
			if(p[i][j-1]!=-1)
				p[i][j] = p[p[i][j-1]][j-1];
		}
	}
}

/*很漂亮的查询*/
int query(int a, int b){
	int i, k;
	if(lev[a] < lev[b])
		a ^= b ^= a ^= b;
	k = (int)(log(n)/log(2));
	for(i=k; i>=0; i--)
		if(lev[a] - (1<<i) >= lev[b])
			a = p[a][i];
	if(a==b) return a;  //a==b,实际上就是b原本是a的祖先
	for(i=k; i>=0; i--)
		if(p[a][i]!=-1 && p[a][i]!=p[b][i]){
			a = p[a][i];
			b = p[b][i];
		}
	return pare[a]; //注意,最后p[a][i]==p[b][i]的时候,a,b肯定是都变成了最近共同祖先的直接孩子。这里比较难理解,好好想一下。
}

void main(){
	int i, j;
	pNode root;
	root = RMC_2_LCA();
	pare[root->pos] = -1;
	DFS(root, 1);
	process();

	for(i=0; i<n; i++){
		for(j=i+1; j<n; j++)
			printf("%d-%d:%d ", i, j, query(i, j));
		printf("\n");
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值