算法笔记——【并查集】家谱

算法笔记——【并查集】家谱

题目链接

现代的人对于本家族血统越来越感兴趣,现在给出充足的父子关系,请你编写程序找到某个人的最早的祖先。

输入格式

由多行组成,首先是一系列有关父子关系的描述,其中每一组父子关系由二行组成,用#name的形式描写一组父子关系中的父亲的名字,用+name的形式描写一组父子关系中的儿子的名字;接下来用?name的形式表示要求该人的最早的祖先;最后用单独的一个$表示文件结束。

规定每个人的名字都有且只有6个字符,而且首字母大写,且没有任意两个人的名字相同。

输出格式

按照输入的要求顺序,求出每一个要找祖先的人的祖先,格式:本人的名字+一个空格+祖先的名字+回车。

数据范围

最多可能有10000组父子关系。
输入数据不超过20010行。
数据中涉及到的人数不超过10000。

输入样例:
#George
+Rodney
#Arthur
+Gareth
+Walter
#Gareth
+Edward
?Edward
?Walter
?Rodney
?Arthur
$
输出样例:
Edward Arthur
Walter Arthur
Rodney George
Arthur Arthur

算法思路:

这一题与大部分并查集问题不同,其他题都是直接使用数字做索引抛出问题,而这一题是字符串做索引,那么我们首先需要将字符串与数字建立起索引关系,那么在C++中建立其这种关系最好的方式就是使用字典,有两种选择:map和unordered_map,更具下方补充,可以选择使用unordered_map会更好一些。另外该题还有一个巧妙的点是,对于输入#开头(父亲)和+开头(儿子)的这两种字符串,可以分别创建一个接受数据的变量,这样在每次读取完父亲的名字之后,对应使用的变量还会一直存在(直至读取下一个父亲的名字),那么对于接下来读取儿子的名字时,就可以很方便的利用这个还存在的变量建立起父亲和儿子之间的关系。剩下的自然就是并查集的常规操作就好了。

补充:

map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。适用处:对于那些有顺序要求的问题,用map会更高效一些

unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。适用处:对于查找问题,unordered_map会更加高效一些。

算法描述:

  1. 建立两个字典,一个索引为string型,值为int型,记为字典name2idx,另一个相反,记为name2idx。建立father[]数组用于存放各点的父亲结点;
  2. 每次读取一行字符串进来,然后开始判断首字符的符号是哪一种;
  3. 如果是’#’,那就截取出字符串后面的名字,检查其是否存在于字典中,若不存在则将其录入两个字典中,并且将其对应father[i]初始化为i;
  4. 如果是’+’,截取出后面名字,检查时候存在于字典中,若不存在则录入。然后不管刚刚是否存在与字典,都将其father[j]设置为当前还保存在变量中的父亲名字的索引;
  5. 如果是’?’,截取出之后的名字,并且查找其对应的祖宗节点,然后将两个按题目要求输出。

源代码(已AC):

#include<iostream>
#include<string>
#include<unordered_map>

using namespace std;

int father[10010];
string Data;
string fat_f,son_f;
unordered_map<string,int> name2idx;
unordered_map<int,string> idx2name; 

int findFather(int x)
{
    if(x==father[x])   //查找到根结点
        return x;
        else
        {
               int F=findFather(father[x]);  //F为根结点
               father[x]=F;  //路径压缩,将查找路径上的每一个结点的父亲结点都设为根结点
               return F;  //返回根结点
         }
    
}

int main()
{
   
	int q=1;
	while(true)
	{
		cin>>Data;
		if(Data[0]=='#')
			{
				fat_f=string(Data.begin()+1,Data.end()); //截取字符串中除首元素的名字
				if(name2idx.count(fat_f)==false)
				{
					name2idx[fat_f]=q;
					idx2name[q]=fat_f;
					father[q]=q;  //设自己为根结点
					q++;
				 }	
			}
		if(Data[0]=='+')
			{
				son_f=string(Data.begin()+1,Data.end());
				if(name2idx.count(son_f)==false)
				{
					name2idx[son_f]=q;
					idx2name[q]=son_f;
					q++;
				 }
				 	father[name2idx[son_f]]=name2idx[fat_f];
			}
		if(Data[0]=='?')
			{
				string f=string(Data.begin()+1,Data.end());
				cout<<f<<" ";
				int w=findFather(name2idx[f]);
				cout<<idx2name[w]<<endl;
			 }
		if(Data[0]=='$')
			return 0;
	}
	return 0;
 } 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值