LCA(Lowest Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先。
基本介紹
基本介紹
實現
- 如果當前結點t 大於結點u、v,說明u、v都在t 的左側,所以它們的共同祖先必定在t 的左子樹中,故從t 的左子樹中繼續查找;
- 如果當前結點t 小於結點u、v,說明u、v都在t 的右側,所以它們的共同祖先必定在t 的右子樹中,故從t 的右子樹中繼續查找;
- 如果當前結點t 滿足 ,說明u和v分居在t 的兩側,故當前結點t 即為最近公共祖先;
- 而如果u是v的祖先,那么返回u的父結點,同理,如果v是u的祖先,那么返回v的父結點。
int query(Node t, Node u, Node v) { int left = u.value; int right = v.value; //二叉查找樹內,如果左結點大於右結點,不對,交換 if (left > right) { int temp = left; left = right; right = temp; } while (true) { //如果t小於u、v,往t的右子樹中查找 if (t.value < left) t = t.right; //如果t大於u、v,往t的左子樹中查找 else if (t.value > right) t = t.left; else return t.value; }}
int tot, seq[N << 1], pos[N << 1], dep[N << 1];// dfs過程,預處理深度dep、dfs序數組seqvoid dfs(int now, int fa, int d) { pos[now] = ++tot, seq[tot] = now, dep[tot] = d; for (int i = head[now]; i; i = e[i].next) { int v = e[i].to; if (v == fa) continue; dfs(v, now, d + 1); seq[++tot] = now, dep[tot] = d; }}int anc[N << 1][20]; // anc[i][j]表示i節點向上跳2^j層對應的節點void init(int len) { for (int i = 1; i <= len; i++) anc[i][0] = i; for (int k = 1; (1 << k) <= len; k++) for (int i = 1; i + (1 << k) - 1 <= len; i++) if (dep[anc[i][k - 1]] < dep[anc[i + (1 << (k - 1))][k - 1]]) anc[i][k] = anc[i][k - 1]; else anc[i][k] = anc[i + (1 << (k - 1))][k - 1];}int rmq(int l, int r) { int k = log(r - l + 1) / log(2); return dep[anc[l][k]] < dep[anc[r + 1 - (1 << k)][k]] ? anc[l][k] : anc[r + 1 - (1 << k)][k];}int calc(int x, int y) { x = pos[x], y = pos[y]; if (x > y) swap(x, y); return seq[rmq(x, y)];}int lca(int a, int b) { dfs(root, 0, 1); // root為樹根節點的編號 init(0); return calc(a, b);}
void dfs(int u) { for(int i=head[u]; i!=-1; i=edge[i].next) { int to=edge[i].to; if(to==p[u][0])continue; d[to]=d[u]+1; dist[to]=dist[u]+edge[i].w; p[to][0]=u; //p[i][0]存i的父節點 dfs(to); }}void init()//i的2^j祖先就是i的(2^(j-1))祖先的2^(j-1)祖先{ for(int j=1; (1<<j)<=n; j++) for(int i=1; i<=n; i++) p[i][j]=p[p[i][j-1]][j-1];}int lca(int a,int b) { if(d[a]>d[b])swap(a,b); //b在下面 int f=d[b]-d[a]; //f是高度差 for(int i=0; (1<<i)<=f; i++) //(1<<i)&f找到f化為2進制後1的位置,移動到相應的位置 if((1<<i)&f)b=p[b][i]; //比如f=5,二進制就是101,所以首先移動2^0祖先,然後再移動2^2祖先 if(a!=b) { for(int i=(int)log2(N); i>=0; i--) if(p[a][i]!=p[b][i]) //從最大祖先開始,判斷a,b祖先,是否相同 a=p[a][i], b=p[b][i]; //如不相同,a b同時向上移動2^j a=p[a][0]; //這時a的father就是LCA } return a;}
const int mx = 10000; //最大頂點數int n, root; //實際頂點個數,樹根節點int indeg[mx]; //頂點入度,用來判斷樹根vector<int> tree[mx]; //樹的鄰接表(不一定是二叉樹)void inputTree() //輸入樹{ scanf("%d", &n); //樹的頂點數 for (int i = 0; i < n; i++) //初始化樹,頂點編號從0開始 tree[i].clear(), indeg[i] = 0; for (int i = 1; i < n; i++) //輸入n-1條樹邊 { int x, y; scanf("%d%d", &x, &y); //x->y有一條邊 tree[x].push_back(y); indeg[y]++;//加入鄰接表,y入度加一 } for (int i = 0; i < n; i++) //尋找樹根,入度為0的頂點 if (indeg[i] == 0) { root = i; break; }}vector<int> query[mx]; //所有查詢的內容void inputQuires() //輸入查詢{ for (int i = 0; i < n; i++) //清空上次查詢 query[i].clear(); int m; scanf("%d", &m); //查詢個數 while (m--) { int u, v; scanf("%d%d", &u, &v); //查詢u和v的LCA query[u].push_back(v); query[v].push_back(u); }}int father[mx], rnk[mx]; //節點的父親、秩void makeSet() //初始化並查集{ for (int i = 0; i < n; i++) father[i] = i, rnk[i] = 0;}int findSet(int x) //查找{ if (x != father[x]) father[x] = findSet(father[x]); return father[x];}void unionSet(int x, int y) //合併{ x = findSet(x), y = findSet(y); if (x == y) return; if (rnk[x] > rnk[y]) father[y] = x; else father[x] = y, rnk[y] += rnk[x] == rnk[y];}int ancestor[mx]; //已訪問節點集合的祖先bool vs[mx]; //訪問標誌void Tarjan(int x) //Tarjan算法求解LCA{ for (int i = 0; i < tree[x].size(); i++) { Tarjan(tree[x][i]); //訪問子樹 unionSet(x, tree[x][i]); //將子樹節點與根節點x的集合合併 ancestor[findSet(x)] = x;//合併後的集合的祖先為x } vs[x] = 1; //標記為已訪問 for (int i = 0; i < query[x].size(); i++) //與根節點x有關的查詢 if (vs[query[x][i]]) //如果查詢的另一個節點已訪問,則輸出結果 printf("%d和%d的最近公共祖先為:%d\n", x, query[x][i], ancestor[findSet(query[x][i])]);}int main(){ inputTree(); //輸入樹 inputQuires();//輸入查詢 makeSet(); for (int i = 0; i < n; i++) ancestor[i] = i; memset(vs, 0, sizeof(vs)); //初始化為未訪問 Tarjan(root);}
const int N=500004;int head[N*2],next[N*2],to[N*2];// 樹的鄰接表int deep[N],fa[N];// deep表示節點深度,fa表示節點的父親int size[N],son[N],top[N];// size表示節點所在的子樹的節點總數 // son表示節點的重孩子 // top表示節點所在的重鏈的頂部節點inline void add(int u,int v,int tnt)// 鄰接表加邊{ nt[tnt]=ft[u]; ft[u]=tnt; ed[tnt]=v;}void DFS(int u,int Fa)// 第一遍dfs,處理出deep,size,fa,son{ size[u]=1; for(int i=head[u];i;i=next[i]) { if(to[i]==Fa) continue; deep[to[i]]=d[u]+1; fa[to[i]]=u; DFS(to[i],u); size[u]+=size[to[i]]; if(size[to[i]]>size[son[u]]) son[u]=to[i]; }}void Dfs(int u)// 第二遍dfs,將所有相鄰的重邊連成重鏈{ if(u==son[fa[u]]) top[u]=top[fa[u]]; else top[u]=u; for(int i=head[u];i;i=next[i]) if(to[i]!=fa[u]) Dfs(to[i]);}int LCA(int u,int v)// 處理LCA{ while(top[u]!=top[v])// 如果u,v不在同一條重鏈上 { if(deep[top[u]]>deep[top[v]])// 將深度大的節點上調 u=fa[top[u]]; else v=fa[top[v]]; } return deep[u]>deep[v]?v:u;// 返回深度小的節點(即為LCA(u,v))}