Tarjan算法_LCA

首先我们要知道什么是LCA、深度优先生成树以及并查集

在计算LCA的算法中我们有离线算法以及在线算法两种,我们这里使用的Tarjan算法是使用的是离线算法,即将所有的问题一次性输入,然后再一次性输出答案

本人感觉这个算法的写法和Tarjan写法还是有很大差距的,相同的地方大部分是在思想上

我们首先来举个例子,模拟一下这个算法的实现过程

Tarjan算法_LCA_第1张图片

假设这是我们要计算的树,我们要计算7--8,5--6,5--2,4--6,它们四个的LCA。首先我们要从根节点1向下遍历,首先遍历到2,然后遍历2的叶子节点,继续遍历到节点7,我们发现节点7没有叶子节点了,那么我们就把vis[7] = true,然后我们要在问题中寻找是否有和7相关的,我们发现7-8中包含7,但是vis[8] = false,所以先不管,接着向上回溯,然后我们就将per[7] = 4,per数组就是并查集中不断向上传递的数组。到达4,我们发现4的所有叶子节点也已经全部遍历完了,然后更新vis[4] = true,然后再从问题中找含有4的问题,发现4--6包含4,但是vis[6] = false,所以什么都不能做,继续回溯到节点2,节点4已经完成了,更新per[4] =2;

Tarjan算法_LCA_第2张图片

我们据需从节点2遍历,然后进入节点5,之后进入节点8,但是在节点8处,vis[8] = true;我们在找包含8的问题,我们发现7--8,这是vis[7]=true,所以这是这个问题的答案就是Find(7),Find函数表示并查集中寻找最终父亲节点的函数,这是Find(7) = 2,所以7和8的LCA = 2;继续回溯到5,然后接着更新per[8] = 5,vis[5] = true;和上面类似,没有问题可以求解回溯更新per[5] = 2。

Tarjan算法_LCA_第3张图片

走到2时,2已经没有叶子节点没有遍历了,所以vis[2] = true;然后再问题中找寻和2相关的问题,发现2--5中包含,并且vis[5] = true,所以2和5的LCA就是Find(5) = 2,然后接着回溯,更新per[2] = 1;

剩余的就不再接着向下说了,步骤都是类似的,并且程序写起来十分容易懂。

//不怕别人比你聪明,就怕别人比你聪明还比你努力
#include
#include
#include
#include
#include
#include
#include 
#include 
#include 
#include
#define INF 0x3f3f3f3f

using namespace std;
const int MAXN = 10005;
vector vec[MAXN];
bool vis[MAXN];
int per[MAXN],head[MAXN],in_num[MAXN];
//in_num统计每个点的入度,为了求根节点,per和并查集中的作用相同,head配合结构体前向星
int cnt,n,m;

struct Node
{
    int c,next;
}edge[MAXN];

void Init()
{
    cnt = 0;
    memset(in_num,0,sizeof(in_num));
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    for(int i =1;i <= n;i++)
    {
        vec[i].clear();
        per[i] = i;
    }
}

void add(int x,int y)
{
    edge[++cnt].next = head[x];
    edge[cnt].c = y;
    head[x] = cnt;
}

int Find(int x)
{
    if(per[x] != x)
        per[x] = Find(per[x]);
    return per[x];
}

void Union(int x,int y)
{
    x = Find(x);y = Find(y);
    if(x == y)
        return ;
    per[x] = y;
}

void Tarjan(int x)
{
    for(int i = head[x];i != -1; i =edge[i].next)
    {
        int v = edge[i].c;
        Tarjan(v);
        Union(v,x);//首先要一直遍历的叶子节点
    }
    vis[x] = 1; // 当这个节点的所有子节点都已经遍历到了,就标记这个节点
    for(int i = 0;i < vec[x].size();i ++)
        if(vis[vec[x][i]])//然后在问题中寻找是否有关于这两个节点都已经标记过的了
            printf("%d 和 %d 的LAC是 %d\n",x,vec[x][i],Find(vec[x][i]));
}
int main()
{
    int x,y;
    scanf("%d%d",&n,&m);
    Init();
    for(int i = 1;i < n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        in_num[y] ++;
    }
    for(int i = 0;i < m;i ++)
    {
        scanf("%d%d",&x,&y);
        vec[x].push_back(y);
        vec[y].push_back(x);
    }
    int root;
    for(int i = 1;i <= n;i ++)
        if(in_num[i] == 0)
            root = i;
    Tarjan(root);
}
/**
8 4
1 2
1 3
2 4
2 5
4 7
5 8
3 6
7 8
5 6
5 2
4 6
**/



在计算LCA的算法中Tarjan离线算法还是比较简单的,后面我会写一下他的在线算法.....

参考博客

深度优先生成树:https://www.cnblogs.com/llhthinker/p/4954082.html

并查集:https://segmentfault.com/a/1190000004023326

你可能感兴趣的:(Tarjan,LCA问题)