LCA(最近公共子序列)

转载自:https://blog.csdn.net/ZCY19990813/article/details/81357251

LCA 最近公共祖先  此博客主要介绍 Tajan(现在只了解这个算法)

需要用到很多铺垫  比如前向星了  链式前向星了  并查集了   下面我们一一来介绍一下

首先看一下前向星 

前向星是一种数据结构,以储存边的方式来存储图  用到两个数组 len[i]是来表示以i为起点边的个数  head[i]是表示i点第一条边的下标

用前向星时要先按照起点大小排序  会增加很多复杂度  所以引申了链式前向星

链式前向星

首先需要定义一个结构体

struct EDGE{
	int next;   //下一条边的存储下标(初值为-1) 
	int to;     //这条边的终点 
	int w;      //权值 
}edge[500010];

1 结构体数组edge存边,edge[i]表示第i条边,

2 head[i]存以i为起点的第一条边(在edge中的下标)

主要代码

typedef long long ll;
void add(ll u, ll v, ll w) {  //起点u, 终点v, 权值w 
	//cnt为边的计数,从1开始计 
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	edge[cnt].w = w;	
	head[u] = cnt++;    //第一条边为当前边 
} 

举个例子 输入起点  终点(先不考虑权值)

1 2
2 3
3 4
1 3
4 1
1 5
4 5

按照代码走一遍  会出现下表

下标tonexthead
12-1head[1]=1
23-1head[2]=2
34-1

head[3]=3;

431head[1]=4
51-1head[4]=5
654head[1]=6
755head[4]=7

遍历以u为起点的边

for(int i=head[u];i!=-1;i=edge[i].next)

下面一个简单输出的代码  如有问题  欢迎私信

输入

4 5
1 2 1
2 3 3
2 4 2
4 3 2
1 4 2
 
2

输出

Start: 2
End: 4
W: 2
 
Start: 2
End: 3
W: 3

代码:
 

#include <iostream>
using namespace std;
 
#define MAXM 500010
#define MAXN 10010
 
struct EDGE
{
    int next;   //下一条边的存储下标
    int to;     //这条边的终点
    int w;      //权值
}edge[MAXM];
 
int n, m, cnt;
int head[MAXN];  //head[i]表示以i为起点的第一条边
 
void Add(int u, int v, int w)    //起点u, 终点v, 权值w
{
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    edge[cnt].w = w;
    head[u] = cnt++;    //第一条边为当前边
}
void Print()
{
    int st;
    cout << "Begin with[Please Input]: \n";
    cin >> u;
    for(int i=head[u]; i!=0; i=edge[i].next)  //i开始为第一条边,每次指向下一条(以-1为结束标志)
    {
        cout << "Start: " << u << endl;
        cout << "End: " << edge[i].to << endl;
        cout << "W: " << edge[i].w << endl << endl;
    }
}
 
int main()
{
    int s, t, w;
    cin  >>n >> m;//n为几个点 m为几条边
    for(int i=1; i<=m; i++)
    {
        cin >> s >> t >> w;
        Add(s, t, w);
    }
    Print();
    return 0;
}

链式前向星介绍完啦  要想用lca求最近公共祖先还差一个并查集

并查集  是一种非常精巧而实用的数据结构,它主要用于处理一些不相交集合的合并问题

并查集的基本操作有两个:

  merge(x, y):把元素x 和元素y 所在的集合合并,要求x 和y 所在的集合不相交,如果相交则不合并。

  find(x):找到元素x 所在的集合的代表,该操作也可以用于判断两个元素是否位于同一个集合,只要将它们各自的代表比较一下就可以了。

此代码摘抄自师哥博客      百度搜素 爱国呐博客园

#include<iostream>
#include<stack>
#include<queue>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int pre[1010];
int rank[1010];
int find(int x)//使用递归写find函数,同时有路径压缩
{
    if(x!=pre[x])
        pre[x]=find(pre[x]);
    return pre[x];
}
void merge(int x,int y)
{
    x = find(x);
    y = find(y);
    if(rank[x]<rank[y])//rank为树的高度,这里为按秩合并
        pre[x] = y;
    else
    {
        pre[y] = x;
        if(rank[x]==rank[y])
            rank[x]++;
    }
}
int main()
{
    int m,n;
    while(1)
    {
        cin>>n;
        if(n==0) break;
        cin>>m;
        for(int i=1; i<=n; i++)//初始化数组
        {
            pre[i]=i;
            rank[i]=0;
        }
        int x,y;
        for(int i=0; i<m; i++)
        {
            cin>>x>>y;
            merge(x,y);
        }
        int sum=0;
        for(int i=1; i<=n; i++)
        {
            if(pre[i]==i)
                sum++;
        }
        cout<<sum-1<<endl;
    }
    return 0;
} 

下面进入正题  tajian(离线)求最近公共祖先

下面详细介绍一下Tarjan算法的基本思路:

      1.任选一个点为根节点,从根节点开始。

      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。

      3.若是v还有子节点,返回,否则下一步。

      4.合并v到u上。

      5.寻找与当前点u有询问关系的点v。

      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

 详解见博客  https://blog.csdn.net/Akatsuki__Itachi/article/details/81279220

下面代码转自~~上边这位https://blog.csdn.net/Akatsuki__Itachi/article/details/81279173

题目   Nearest Common Ancestors

A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:


In the figure, each node is labeled with an integer from {1, 2,...,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is.

For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y.

Write a program that finds the nearest common ancestor of two distinct nodes in a tree.
 

Input

The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,..., N. Each of the next N -1 lines contains a pair of integers that represent an edge --the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N - 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.

Output

Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.

Sample Input

2
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
5
2 3
3 4
3 1
1 5
3 5

Sample Output

4
3

链式前向星写法

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#define eps 1e-8
#define memset(a,v) memset(a,v,sizeof(a))
using namespace std;
typedef long long int LL;
const int MAXL(1e6);
const int INF(0x7f7f7f7f);
const int mod(1e9+7);
int dir[4][2]= {{-1,0},{1,0},{0,1},{0,-1}};
struct node
{
    int to;
    int next;
}edge[MAXL+50];
int head[MAXL+50];
int father[MAXL+50];
bool vis[MAXL+50];
bool is_root[MAXL+50];
int n;
int cnt;
int cx,cy;
int ans;
int root;
 
 
int Find(int x)
{
    if(x!=father[x])
        father[x]=Find(father[x]);
    return father[x];
}
 
void Join(int x,int y)
{
    int fx=Find(x),fy=Find(y);
    if(fx!=fy)
        father[fy]=fx;
}
 
void add_edge(int x,int y)
{
    edge[cnt].to=y;
    edge[cnt].next=head[x];
    head[x]=cnt++;
}
 
void init()
{
    cnt=0;
    memset(head,-1);
    memset(vis,false);
    memset(is_root,true);
    scanf("%d",&n);
    for(int i=0;i<=n;i++)
        father[i]=i;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add_edge(x,y);
        is_root[y]=false;
    }
    for(int i=1;i<=n;i++)
        if(is_root[i]==true)
            root=i;
}
 
void LCA(int u)
{
    for(int i=head[u];~i;i=edge[i].next)
    {
        int v=edge[i].to;
        LCA(v);
        Join(u,v);
        vis[v]=true;
 
    }
    if(cx==u&&vis[cy]==true)
        ans=Find(cy);
    if(cy==u&&vis[cx]==true)
        ans=Find(cx);
}
void solve()
{
    scanf("%d%d",&cx,&cy);
    LCA(root);
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        solve();
        cout<<ans<<endl;
    }
}

上面链接中还有vector模拟邻接表写法
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#define eps 1e-8
#define memset(a,v) memset(a,v,sizeof(a))
using namespace std;
typedef long long int LL;
const int MAXL(1e4);
const int INF(0x7f7f7f7f);
const int mod(1e9+7);
int dir[4][2]= {{-1,0},{1,0},{0,1},{0,-1}};
int father[MAXL+50];
bool is_root[MAXL+50];  //记录该店是不是根结点
bool vis[MAXL+50];      //标记该点是否被访问过
vector<int>v[MAXL+50];  //存图
int root;    //为找到的根结点
int cx,cy;   //要查询的两点
int ans;
int Find(int x)
{
    if(x!=father[x])
        father[x]=Find(father[x]);
    return father[x];
}
 
void Join(int x,int y)
{
    int fx=Find(x),fy=Find(y);
    if(fx!=fy)
        father[fy]=fx;
}
 
void LCA(int u)
{
    for(int i=0; i<v[u].size(); i++)  //对根结点的所有子结点遍历
    {
        int child=v[u][i];
        if(!vis[child])
        {
            LCA(child);     //递归查找每一个结点,直到到叶子结点为止
            Join(u,child);  //回溯的时候更新father用的
            //例如把题目中的[9]更新为5
            vis[child]=true;  //访问过的点vis数组为true
        }
    }
    if(u==cx&&vis[cy]==true) //若和u有关系的点被访问过,
        ans=Find(cy);    //则最近公共祖先为那个有关系点的祖先
    if(u==cy&&vis[cx]==true)  //若没有被访问过,则不操作
        ans=Find(cx);
 
}
 
void init()
{
    memset(is_root,true);
    memset(vis,false);
    int n;
    scanf("%d",&n);
    for(int i=0; i<=n; i++)
        v[i].clear();
    for(int i=1; i<=n; i++)
        father[i]=i;
    for(int i=1; i<n; i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        is_root[y]=false;   //y点有入度,所以y点不是根结点
    }
    scanf("%d%d",&cx,&cy);  //cx,cy为要查询的两点
    for(int i=1; i<=n; i++) //找根结点
    {
        if(is_root[i]==true)
        {
            root=i;
            break;
        }
    }
 
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init();      //建树(图),找根结点
        LCA(root);   
        cout<<ans<<endl;
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值