树的公共祖先问题可以分为在线法和离线法,在线法就是来一个请求就处理一次,
离线法就是收集所有请求然后统一给出回复。
1. 在线法。LCA可以转换为RMQ
1) 对树进行dfs,访问每个节点时要记录节点的层次,同时要记录每个节点第一次出现的位置,对于节点个数为n的树,最后会形成一个2n-1长度的序列
2) 如果要查询节点a和节点b的公共祖先,只需找出a和b节点在在序列中第一次
出现的位置,形成了一个从a到b的子序列, 对应序列中层数最小的节点即为公
共祖先。
3) 因此把LCA转化为RMQ问题。对于给定序列求出指定区间中的最小值
1 #include <stdio.h> 2 #include <string.h> 3 #include <math.h> 4 #include <malloc.h> 5 6 #define max(a,b) ((a)>(b)?(a):(b)) 7 #define min(a,b) ((a)>(b)?(b):(a)) 8 9 struct tree{ 10 int n; 11 int left, right; 12 }p[100]; 13 14 int first[9]; 15 int sequence[20]; 16 int dep[20]; 17 int point=0; 18 int deep=0; 19 20 /* 建树 */ 21 int createTree() 22 { 23 int temp[8]={1,2,7,3,4,8,5,6}; 24 for(int i=0;i<8;i++) 25 p[i].n=temp[i]; 26 p[0].left=1;p[0].right=2; 27 p[1].left=3;p[1].right=4; 28 p[2].left=-1;p[2].right=5; 29 p[3].left=-1;p[3].right=-1; 30 p[4].left=6;p[4].right=7; 31 p[5].left=-1;p[5].right=-1; 32 p[6].left=-1;p[6].right=-1; 33 p[7].left=-1;p[7].right=-1; 34 return 0; 35 } 36 37 /* 结点访问顺序是: 1 2 3 2 4 5 4 6 4 2 1 7 8 7 1 共2n-1个值 */ 38 /* 结点对应深度是: 0 1 2 1 2 3 2 3 2 1 0 1 2 1 0 */ 39 void dfs(int root) 40 { 41 if(p[root].n < 0) 42 return; 43 sequence[point]=p[root].n; 44 dep[point]=deep; 45 if(first[p[root].n] < 0) 46 first[p[root].n]=point; 47 point++; 48 if(p[root].left > 0){ 49 deep++; 50 dfs(p[root].left); 51 deep--; 52 sequence[point]=p[root].n; 53 dep[point]=deep; 54 point++; 55 } 56 if(p[root].right > 0){ 57 deep++; 58 dfs(p[root].right); 59 deep--; 60 sequence[point]=p[root].n; 61 dep[point]=deep; 62 point++; 63 } 64 } 65 66 int map[100][100]; 67 /** 68 * 花费O(nlogn)的时间进行预处理 递推公式 69 * f[i,j]=max(f[i, j-1], f[i + 2^(j-1), j-1]) 70 * @param m 存储数值的数组 71 * @param n 数组长度 72 */ 73 void pre_handle(int * m , int n) 74 { 75 memset(map, 0, sizeof(map)); 76 for(int i=0;i<n;i++) 77 map[i][0]=dep[i]; 78 int k = (int)(log(n) / log(2)); 79 for(int i=1;i<=k;i++) /* i表示列,j表示行, 每一列的结果只依赖于前一列 */ 80 for(int j = 0; j + pow(2,i-1) < n; j++) 81 /* 注意因为每列都限制j + pow(2,i-1) < n,而除了第一列以外 82 * 其它都不到n-1,所有每一行的最后一个数有可能是不对的,但 83 * 是不影响最后结果 */ 84 map[j][i]=min(map[j][i-1], map[j+(int)pow(2, i-1)][i-1]); 85 } 86 87 int RMQ(int a, int b) 88 { 89 int k = (int)(log(b-a+1)/log(2)); /* 区间长度为b-a+1 */ 90 /* 两个区间之间有重合 */ 91 return min(map[a][k], map[b+1-(int)pow(2, k)][k]); 92 } 93 94 95 int main() 96 { 97 int root = createTree(); 98 memset(first, -1, sizeof(first)); 99 dfs(root); 100 pre_handle(sequence, point); 101 printf("%d\n", RMQ(4, 11)); 102 return 0; 103 }
2. 离线法。Tarjan
算法主要是采用dfs+并查集
1 /** 2 * @file code.c 3 * @author <kong@KONG-PC> 4 * @date Mon Dec 03 20:52:03 2012 5 * 6 * @brief Tarjan算法,基本思想是深度优先遍历+并查集,利用并查集可以将 7 * 查询两个数字是否在一个集合中的操作变为O(1) 8 */ 9 10 #include <stdio.h> 11 #include <vector> 12 using namespace std; 13 #define N 100 14 15 vector<int> tree[N],request[N]; /* 用来记录子节点和对应节点的询问信息 */ 16 int ancestor[N]; /* 记录节点的祖先节点 */ 17 18 int visit[N]; /* 记录节点的访问情况 */ 19 int p[N]; /* 临时记录并查集中的父亲节点,在遍历的过程 20 * 中它的值会不断变化 */ 21 int rank[N]; 22 23 void init(int n) 24 { 25 for(int i=0;i<n;i++) 26 { 27 rank[i]=1; /* 初始化所在集合的节点个数 */ 28 p[i]=i; 29 visit[i]=0; 30 ancestor[i]=-1; /* 把所有节点的祖先节点先初始化为-1 */ 31 tree[i].clear(); 32 request[i].clear(); 33 } 34 } 35 36 /* 返回并查集的根节点 */ 37 int find(int t) 38 { 39 if(p[t]==t) 40 return t; 41 return p[t]=find(p[t]); /* 在查询的过程中对并查集进行优化,使 42 * 用路径压缩 */ 43 } 44 /* 合并两个集合,使用启发算法,将深度较小的树指到深度较大的树的根上, 45 * 可以防止树的退化 */ 46 int unionSet(int a, int b) 47 { 48 int m=find(a); /* 首先获取m,n所在集合的根元素 */ 49 int n=find(b); 50 if(m==n) 51 return 0; 52 if(rank[m]>=rank[n]) /* 判断两个集合的rank值,并把值小的并 53 * 到值大的集合中 */ 54 { 55 p[n]=m; 56 rank[m]+=rank[n]; 57 } 58 else 59 { 60 p[m]=n; 61 rank[n]+=rank[m]; 62 } 63 return 1; 64 } 65 /* 深度优先遍历+并查集 */ 66 void LCS(int t) 67 { 68 ancestor[t]=t; 69 for(int i=0;i<(int)tree[t].size();i++) 70 { 71 LCS(tree[t][i]); 72 unionSet(tree[t][i],t); 73 ancestor[p[t]]=t; 74 } 75 visit[t]=1; 76 for(int i=0;i<(int)request[t].size();i++) 77 if(visit[request[t][i]]==1) 78 printf("%d %d %d\n", t, request[t][i],ancestor[p[request[t][i]]]); /* 这里需要知道request[t][i]所在集合的祖先节点 */ 79 } 80 81 int main() 82 { 83 freopen("in","r",stdin); 84 int n,t,r; /* 分别表示节点个数、边的个数和询问个 85 * 数 */ 86 int a,b; 87 scanf("%d%d%d",&n,&t,&r); 88 init(n); /* 初始化结构 */ 89 while(t--) /* 读入树结构 */ 90 { 91 scanf("%d%d",&a,&b); 92 tree[a].push_back(b); 93 } 94 while(r--) /* 读入询问并存入对应的列表中 */ 95 { 96 scanf("%d%d",&a,&b); 97 request[a].push_back(b); 98 request[b].push_back(a); 99 } 100 LCS(0); 101 return 0; 102 }