经典算法:寻找最近公共祖先

题目

假设现在他知道了N个人的信息——他们的父亲是谁。给出两个人的名字,找出这两个人是否存在同一个祖先,如果存在,那么他们的所有共同祖先中辈分最低的一个是谁?

输入输出

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第1行为一个整数N,意义如前文所述。

每组测试数据的第2~N+1行,每行分别描述一对父子关系,其中第i+1行为两个由大小写字母组成的字符串Father_i, Son_i,分别表示父亲的名字和儿子的名字。

每组测试数据的第N+2行为一个整数M,表示小Hi总共询问的次数。

每组测试数据的第N+3~N+M+2行,每行分别描述一个询问,其中第N+i+2行为两个由大小写字母组成的字符串Name1_i, Name2_i,分别表示小Hi询问中的两个名字。

对于100%的数据,满足N<=10^2,M<=10^2, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人)。 
输出

对于每组测试数据,对于每个小Hi的询问,输出一行,表示查询的结果:如果根据已知信息,可以判定询问中的两个人存在共同的祖先,则输出他们的所有共同祖先中辈分最低的一个人的名字,否则输出-1。 
样例输入

11
JiaYan JiaDaihua
JiaDaihua JiaFu
JiaDaihua JiaJing
JiaJing JiaZhen
JiaZhen JiaRong
JiaYuan JiaDaishan
JiaDaishan JiaShe
JiaDaishan JiaZheng
JiaShe JiaLian
JiaZheng JiaZhu
JiaZheng JiaBaoyu
3
JiaBaoyu JiaLian
JiaBaoyu JiaZheng
JiaBaoyu LinDaiyu

样例输出

JiaDaishan
JiaZheng
-1

解法1:在线算法

这个问题也应该是树结构许许多多问题中颇为经典的一个了,如果就简单的将父子关系视作树结构中的父子结点关系的话,这个问题其实就是在问树中两个结点层数最高的公共祖先——也就是所谓的最近公共祖先。

如果数据量不大的话,完全可以直接将这两个人的祖先全部找出来,然后取它们的交集,然后再找到其中辈分最低的一个不就行了。

先将一个人的祖先全都标记出来,然后顺着另一个的父亲一直向上找,直到找到第一个被标记过的结点,便是它们的最近公共祖先结点了。

解法2:离线算法

如果数据量非常大的,或者询问的次数非常频繁,每次都用上面的算法效率会非常低。所谓离线算法,就是一次性收集若干个询问之后,在一次查找中能够解决所有的询问。 而不像一个在线系统一样对于每一个询问都即时的进行计算并给出答复。

其实这个问题的本质就是给你一棵树,然后每次询问是求这棵树种某两个结点的最近公共祖先——所有公共祖先中层数最深的。先这个图——好吧,其实是棵树,代表着我们这个问题的一个输入,而这几个是我们要处理的询问。这里写图片描述

而现在要做的是以深度优先搜索的顺序来访问这棵树。在这个过程中,我会给这棵树的结点染色,一开始所有结点都是白色的。而当我第一次经过某个结点的时候,我会将它染成灰色,而当我第二次经过这个结点的时候——也就是离开这棵子树的时候,我会将它染成黑色。

举个例子,当我们深度优先搜索到A结点时,我们发现A结点和B结点是我们需要处理的一组询问。我们这个时候就要去查看B结点的颜色——灰色,说明我们进入了以B结点为根的子树,但是还没有从这棵子树中出去。

这意味着A结点在B结点所在的子树中——那么B结点就是A和B结点的最近公共祖先了。但这只是一种简单的情况,如果我询问的不是A和B这两个结点,而是A和C这两个结点,那么在访问到A的时候,C的颜色是黑色的,这时候该怎么处理呢?首先肯定只能从所有灰色结点中找——这是A结点的所有祖先结点!

但是这样做,每次都要向上找会很吃不消的。先别着急,我们把之前的分情况讨论结束,再来细谈这个问题……那么接下来只有一种可能了,如果询问的时A和P这两个结点,而此时P还是白色的,既然是白色,那么就先不要管这个询问了,毕竟现在关于P的信息一点都不知道。而且反正深度优先搜索会处理到P结点,那个时候A结点肯定已经不是白色了,就可以沿用之前的方法进行解决了!

这里写图片描述

那么有没有发现,这样一遍处理下来,你就可以求出所有准备好的询问的结果了——我先计算每个结点涉及到的询问,然后在深度优先搜索的过程中对结点染色,如果发现当前访问的结点是涉及到某个询问,那么我就看这个询问中另一个结点的颜色,如果是白色,则留待之后处理,如果是灰色,那么最近公共祖先必然就是这个灰色结点,如果是黑色,那么最近公共祖先就是这个黑色结点向上的第一个灰色结点。

而我们唯一剩下的问题,就是怎么样快速的找到一个黑色结点向上的第一个灰色结点。

首先我们来这样想,当深度优先搜索进行到刚刚第二次经过C结点这一步的时候,是不是以C为根的子树中所有结点,它们向上找的第一个灰色结点都是C结点。

这里写图片描述

这里写图片描述

那么如果我们将C这棵子树视为一个集合,而将C结点视为这个集合的代表元素的话,那么我在第二次经过C结点之后,将C结点染成黑色后,以C为根的子树中的所有结点的代表元素——或者说向上找的第一个灰色结点都变成了C的父亲结点——D结点?那么这一过程,是不是其实就是将C结点代表的集合合并到了D结点代表的集合中去了?

这里写图片描述

我们可以认为,每个结点最开始都是一个独立的集合,每当一个结点由灰转黑的时候,就将它所在的集合合并到其父亲结点所在的集合中去。这样无论什么时候,任意一个黑色结点所在集合的代表元素就是这个结点向上的第一个灰色结点!也就是说,我只要在深度优先搜索的过程中维护这样的一些集合,我就能够对于每一个黑色结点,非常快捷的求出在当前的染色状况下,它向上的第一个灰色结点。

#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<stack>
std::vector<std::vector<int> > V;//用来存储树关系
std::map<std::string,int> Map;//用来表示名字和编号
std::vector<std::string> Name;//用编号来找名字
std::vector<int> Ancestor;//存储父子关系

enum Color{White, Grey, Black};

int FindQuestion(const std::vector<int>& Ques1,const std::vector<int>& Ques2,const std::vector<int>& Answer,const std::vector<Color>& C,int q,int start)
{
    for(int i=start;i<Ques1.size();++i)
    {
        if(Answer[i]==-1&&(Ques1[i]==q||Ques2[i]==q))
        {
            if(C[Ques1[i]]!=White&&C[Ques2[i]]!=White)
                return i;
        }
    }
    return -1;
}
int FindLowestAncestor(int n,const std::vector<Color>& C)
{
    if(C[n]==Grey||Ancestor[n]==n)
        return n;
    Ancestor[n]=FindLowestAncestor(Ancestor[n],C);//顺便更新了最低祖先结点
    return Ancestor[n];
}
/**
ancesotr表示根节点
M问题个数
Ques1、Ques2存储了问题
*/
void DFS(int ancesotr,int M, std::vector<int>& Ques1, std::vector<int>& Ques2)//深度优先遍历
{
    std::vector<int> Answer(M,-1);//问题答案存储在里面

    std::vector<Color> C(Name.size(),White);//全部初始化为白色
    std::stack<int> Stack;

    Stack.push(ancesotr);
    C[ancesotr]=Grey;
    while(!Stack.empty())//深度优先遍历
    {
        int top=Stack.top();//取出栈顶结点

        int result=FindQuestion(Ques1,Ques2,Answer,C,top,0);
        while(result!=-1)
        {
            if(Ques1[result]==top)
            {
                if(C[Ques2[result]]==Grey)//祖先就是Ques2[result]
                {
                    Answer[result]=Ques2[result];
                }
                else
                {
                    //找Ques2[result]的祖先的灰色结点
                    Answer[result]==FindLowestAncestor(Ques2[result],C);
                }

            }
            else if(Ques2[result]==top)
            {
                if(C[Ques1[result]]==Grey)//祖先就是Ques2[result]
                {
                    Answer[result]=Ques1[result];
                }
                else
                {
                    //找Ques2[result]的祖先的灰色结点
                    Answer[result]=FindLowestAncestor(Ques1[result],C);
                }
            }
            result=FindQuestion(Ques1,Ques2,Answer,C,top,result);//再找一遍
        }

        int i;
        for(i=0;i<V[top].size();++i)
        {
            if(C[V[top][i]]==White)//没被访问过
            {
                Stack.push(V[top][i]);
                C[V[top][i]]=Grey;
                break;
            }
        }
        if(i==V[top].size())
        {
            C[top]=Black;
            Stack.pop();
        }
    }

    for(int i=0;i<Answer.size();++i)
        std::cout<<Name[Answer[i]]<<std::endl;
}
int main()
{

    int N;
    std::cin>>N;
    std::string Father, Son;
    int id=0;//给名字编号
    for(int i=0; i<N; ++i)
    {
        std::cin>>Father>>Son;
        if(Map.count(Father)==0)
        {
            Map[Father]=id++;//给名字找ID
            Name.push_back(Father);//存储名字
            std::vector<int> v;
            V.push_back(v);
            Ancestor.push_back(Map[Father]);
        }
        if(Map.count(Son)==0)
        {
            Map[Son]=id++;
            Name.push_back(Son);
            std::vector<int> v;
            V.push_back(v);
            Ancestor.push_back(Map[Son]);
        }
        //添加父子关系
        V[Map[Father]].push_back(Map[Son]);
        Ancestor[Map[Son]]=Map[Father];
    }

    int M;
    std::cin>>M;
    std::string name1, name2;
    std::vector<int> Ques1(M);//存储问题
    std::vector<int> Ques2(M);
    for(int i=0; i<M; ++i)
    {
        std::cin>>name1>>name2;
        Ques1[i]=Map[name1];
        Ques2[i]=Map[name2];
    }

    int mostAncestor;//公共祖先
    for(std::vector<int>::iterator iter=Ancestor.begin(); iter!=Ancestor.end();++iter)
    {
        if(*iter==iter-Ancestor.begin())
        {
            mostAncestor=iter-Ancestor.begin();
            break;
        }
    }

    DFS(mostAncestor,M,Ques1,Ques2);
    return 0;
}


  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值