最近碰到的一些题,主要问的就是LCA(最小公共祖先)
一开始是暴力,但是随着数据的增多,预感到不能继续暴力了,于是开始学习关于 最小公共祖先 的算法
网上有很多关于这方面的博客,但是很多的文章让我感觉看的云里雾里的(本人太菜),终于在浩如烟海的众多文章中找到了相对来说比较简单的一些,东拼西凑,最后总算稍微了解一点LCA算法了;
下面就介绍一下简单的LCA实现(个人觉得非常好理解)(目前我认为的LCA)
LCA算法就是在一颗树上找两个叶子节点的公共祖先,算法的前提要保证只有一棵树(家族的话只有一个家族),而且必须知道根节点,然后来确定这棵树(这棵树不能被改变了)
然后对于节点的查询,仍是让两个节点向上寻找,直到两个节点相同,即为要寻找的 最小公共祖先 ;
LAC的倍增;
引入一个数组,fa[i][j];对于节点i,它的第2^j个父亲节点的编号
第i个节点的第2^j个父亲节点的编号;
建图:
利用临界表,从根节点出发,用DFS来建表。
这里每次son的建边都需要对它的fa[son][]数组进行更新,
fa[son][i+1] = fa[ fa[son][i] ][i];(倍增思想)
保证deep[X]>=deep[Y];只操作X(或只操作Y),
让x向上搜,知道其深度等于y的深度,
这里通过倍增算法使其快速上搜 ,等二者深度相同时,同时上溯至同一节点;
此节点即为x与y的LCA;
其中用到了倍增的思想,废话不多说,树应该很容易就能想到,因此直接就上代码了:
首先对数据的初始化:
int fa[MAXN][30],deep[MAXN];
//fa[i][j]中记录的是i节点的第2^j的祖先是谁(fa中存的就是祖先)
//deep[i]记录i节点的深度
int k;
int n,m;
void intt()
{
clean(head,-1);//向前星的初始化
clean(deep,0);//深度为0
clean(fa,-1);//初始化父节点都是-1
ecnt=0;
k=1;
}
先建边(我用的是向前星模拟连接表,核心没有变,因此用自己熟悉的随便什么都行,无所谓)
struct node{
int v,w,nxt;
node(int _v=0,int _nxt=0):
v(_v),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN],ecnt;
//事先初始化
void add(int u,int v)
{
edge[ecnt]=node(v,head[u]);
head[u]=ecnt++;
}
然后是建树;
建树的时候用deep数组记录一下该节点到根节点的距离(该节点的深度)
注意,因为节点可能会重复访问,因此要有一个bool vis[]数组标记一下,防止被重复访问,我这里deep数组顺被充当vis,因此就没有写
void dfs(int u)
{
for(int i=head[u];i+1;i=edge[i].nxt)//便利这个边的邻接表
{
int temp=edge[i].v;
if(deep[temp]==0)//如果这个节点没有被标记
{
deep[temp]=deep[u]+1;//深度+1
fa[temp][0]=u;//父节点是u
int up=0,pre=u;//从父节点开始向上找
while(fa[pre][up]>=0)//
{
fa[temp][up+1]=fa[pre][up];//子节点的第2^(up+1)父辈是pre的2^up父辈
pre=fa[pre][up++];//刷新pre
}
dfs(temp);//该节点继续向下走
}
}
}
然后是LCA查找(倍增)了:
int lca(int a,int b)
{
if(deep[a]<deep[b])
swap(a,b);//默认a节点的深度最深
int lim=log2(deep[a])+1;//最多访问的第几个夫节点
//使他们的深度相同
for(int i=lim;i>=0;--i)
{
if(deep[fa[a][i]]>=deep[b])
a=fa[a][i];
}
//现在a和b的深度相同
if(a==b)
return a;
//寻找最小的公共祖先
for(int i=lim;i>=0;--i)
{
//如果fa[a][i]==fa[b][i],说明他们的公共祖先深度可能更小,因此不用更新,向更小的方向查找
//如果fa[a][i]!=fa[b][i],说明目前他们的公共祖先比当前的祖先深度更浅,但是要深于上一个i,因此刷新a和b,向更小的范围查找祖先
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}//最后发现祖先
if(fa[a][0]==fa[b][0])
return fa[a][0];
else
return -1;
}
这就是LCA的步骤了
下面是完整的代码,例题:hihoCoder #1069 : 最近公共祖先·三
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
//#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
const int MAXN=1e5+10;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
struct node{
int v,w,nxt;
node(int _v=0,int _nxt=0):
v(_v),nxt(_nxt){}
}edge[MAXN<<1];
int head[MAXN],ecnt;
int fa[MAXN][30],deep[MAXN];
int k;
int n,m;
map<string,int> mp1;
map<int,string> mp2;
void intt()
{
mp1.clear();
mp2.clear();
clean(head,-1);
clean(deep,0);
clean(fa,-1);
ecnt=0;
k=1;
}
void add(int u,int v)
{
edge[ecnt]=node(v,head[u]);
head[u]=ecnt++;
}
void dfs(int u)
{
for(int i=head[u];i+1;i=edge[i].nxt)
{
int temp=edge[i].v;
if(deep[temp]==0)
{
deep[temp]=deep[u]+1;
fa[temp][0]=u;//父节点是u
int up=0,pre=u;//从父节点开始向上找
while(fa[pre][up]>=0)
{
fa[temp][up+1]=fa[pre][up];//子节点的第2^(up+1)父辈是pre的2^up父辈
pre=fa[pre][up++];
}
dfs(temp);
}
}
}
int lca(int a,int b)
{
if(deep[a]<deep[b])
swap(a,b);
int lim=log2(deep[a])+1;
//使他们的深度相同
for(int i=lim;i>=0;--i)
{
if(deep[fa[a][i]]>=deep[b])
a=fa[a][i];
}
if(a==b)
return a;
for(int i=lim;i>=0;--i)
{
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}
if(fa[a][0]==fa[b][0])
return fa[a][0];
else
return -1;
}
int main()
{
std::ios::sync_with_stdio(false);
intt();
cin>>n;
string name1,name2;
for(int i=1;i<=n;++i)
{
cin>>name1>>name2;
if(mp1[name1]==0)//name1对应int
{
mp1[name1]=k;
mp2[k++]=name1;
}
if(mp1[name2]==0)//name2对应int
{
mp1[name2]=k;
mp2[k++]=name2;
}
add(mp1[name1],mp1[name2]);
// add(mp1[name2],mp1[name1]);
}
deep[1]=1;
dfs(1);
cin>>m;
for(int i=1;i<=m;++i)
{
cin>>name1>>name2;
if(name1==name2)
cout<<name1<<endl;
else
{
int ans=lca(mp1[name1],mp1[name2]);
if(ans==-1)
cout<<ans<<endl;
else
cout<<mp2[ans]<<endl;
}
}
}