BUAA(2021春)查家谱(士谔书院16级期末)——找最近公共祖先(已上传测试数据和代码)

48 篇文章 154 订阅

看前须知

要点介绍和简要声明.

考试回顾

格式控制输入输出——期中考试模拟题(简单的分类讨论).

标识符的识别(期中考试题)——题目说的不清不楚但其实不难.

空闲空间申请模拟(期中考试题)——注意读题,难度其实一般.

网络打印机选择(北京学院数据结构18级期末压轴题)——伪树状数组(这题杀我)(ಥ_ಥ).

查家谱(士谔书院16级期末)——找最近公共祖先.

图的直径(士谔书院15级期末)——求有向图的最大路径:Floyd的妙用.

题目内容

问题描述

同姓氏中国人见面常说的一句话是“我们五百年前可能是一家”。从当前目录下的文件in.txt中读入一家谱,从标准输入读入两个人的名字(两人的名字肯定会在家谱中出现),编程查找判断这两个人相差几辈,若同辈,还要查找两个人共同的最近祖先以及与他(她)们的关系远近。假设输入的家谱中每人最多有两个孩子,例如下图是根据输入形成的一个简单家谱:

在这里插入图片描述
通过该家谱,可以看到wangliang、wangguoping和wangguoan都有两个孩子,wangtian、wangxiang和wangsong有一个孩子,wangguang、wangqinian、wangping和wanglong还没有孩子。若要查找的两个人是wangqinian和wangguoan,从家谱中可以看出两人相差两辈;若要查找的两个人是wangping和wanglong,可以看出两人共同的最近祖先是wangguoan,和两人相差两辈。

输入形式

当前目录下的in.txt中读入家谱。文件中第一行是家谱中有孩子的人数,后面每行内容是每个人的名字和其孩子的名字,名字都由1到20个英文字母构成,各名字间以一个空格分隔,整个家谱中的人员都不会重名;若只有一个孩子,则第二个孩子的名字为NULL;若没有孩子,则不需输入;输入的顺序是按照辈份从高到低依次输入,若孩子A出现在孩子B之前,则A的孩子应在B的孩子之前输入。假设以该形式读入的家谱肯定能够形成类似上图所示的一棵二叉树形式的家谱,家谱中任何两人相差的辈份不会超过100

从标准输入读入要查找的两个人的名字,两名字间也以一个空格分隔。

输出形式

所有信息输出到标准输出上。

若要查找的两人不同辈,则先输出辈份低的名字,再输出辈份高的名字,然后输出相差几辈,都以一个空格分隔;

若两人同辈,按照两人名字从标准输入读取的先后顺序,分行输出两人的最近祖先名字、两人姓名以及相差几辈,各数据间以一个空格分隔。

样例

假设当前目录下in.txt文件内容为:

6

wangliang wangguoping wangguoan

wangguoping wangtian wangguang

wangguoan wangxiang wangsong

wangtian wangqinian NULL

wangxiang wangping NULL

wangsong wanglong NULL

从标准输入读取:

wangqinian wangliang

【样例1输出】

wangqinian wangliang 3

【样例2输入】

假设当前目录下in.txt文件内容为:

6

wangliang wangguoping wangguoan

wangguoping wangtian wangguang

wangguoan wangxiang wangsong

wangtian wangqinian NULL

wangxiang wangping NULL

wangsong wanglong NULL

从标准输入读取:

wangping wanglong

【样例2输出】

wangguoan wangping 2

wangguoan wanglong 2

样例说明

【样例1说明】

家谱中输入了六个人名及其孩子的人名,形成了“问题描述”中的家谱,要查找的两人是wangqinian和wangliang,wangliang比wangqinian高3辈。

【样例2说明】

和样例1同样输入了一家谱,wangping和wanglong共同的最近祖先是wangguoan,该祖先与两人相差两辈。

题解

思考和详解

看了一下往年的期末考试题目,最后一题基本都是以树作为压轴题。2016年的查家谱和2018年的网络打印机选择都涉及到的同一个算法——找最近公共祖先,所以必须掌握这个经典算法。在这里还是要说一下的是,这两个题的找最近公共祖先都是简化版的都是给你了每个节点的父亲节点(根节点除外),所以要好好利用好这个条件,让两个节点同时回退到根节点,记录沿路的的信息,然后比对找出第一个相同信息的节点即为最近公共祖先,当然我们必须要思考的是,如果不是这种给了父亲节点的伪线索树,我们又该如何找最近公共祖先呢?

还有一个难点是如何构造树,用递归即可,但是要注意初始化完备,不然会受到野指针的影响!

参考代码

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct TreeNode {	//树的结构体 
    char name[200];
    int depth;					//新增了层数 
    struct TreeNode *left;
    struct TreeNode *right;
    struct TreeNode *parent;	//新增了父亲节点 
}Tree,*Treep;											 //top是栈顶 
int HaveChild_parentNum,depth,chidDepth_1,chidDepth_2,flag,top,i,j;
//	有孩子的父母的个数	深度 第一个孩子的深度 第二个孩子的深度 
char parName[200],chidName_1[200],chidName_2[200];
Treep root=NULL,chidPtr_1,chidPtr_2,ancPtr,ptr,path_1[200],path_2[200];
//		第一个孩子的指针 第二个孩子的指针,祖先指针(ancPtr) 
void initTree(Treep t, int depth);	//构建树 
Treep SearchTree(Treep t,char* s);	//搜索该孩子的位置 
Treep FindSameAncestor(Treep p,Treep q);//搜索公共祖先
int main()
{	
	FILE *fp = fopen("in.txt","r");
	fscanf(fp,"%d",&HaveChild_parentNum);	//读入有孩子的父母的个数
	for(i=0;i<HaveChild_parentNum;i++)
	{
		depth=1;		//起始层数是1,不是0 
		fscanf(fp,"%s %s %s",parName,chidName_1,chidName_2);//录入信息 
		if(root==NULL)
		{
			root=(Treep)malloc(sizeof(Tree));			//申请空间 
			root->left=(Treep)malloc(sizeof(Tree));		//申请空间 
			root->right=(Treep)malloc(sizeof(Tree));	//申请空间  
			strcpy(root->name,parName);				
			strcpy(root->left->name,chidName_1);		//录入信息 
			root->left->left=root->left->right=NULL;	//一定要初始化好 
			root->depth=depth,root->left->depth=depth+1;	//录入信息 
			root->left->parent=root;
			if(strcmp(chidName_2,"NULL")!=0)	//有没有第二个孩子 
			{
				strcpy(root->right->name,chidName_2);	//录入信息 
				root->right->left=root->right->right=NULL;	//初始化 
				root->right->parent=root;	//录入信息 
				root->right->depth=depth+1;	//录入信息 
			}	
			else	root->right=NULL;	//初始化 
		}
		else
		{
			initTree(root->left,depth+1);	//递归插入 
			initTree(root->right,depth+1);	//递归插入 
		}	
	}
	scanf("%s %s",chidName_1,chidName_2);
	flag=0,ptr=NULL;
	chidPtr_1=SearchTree(root,chidName_1);	//找到第一个人的位置 
	chidDepth_1=chidPtr_1->depth;
	flag=0,ptr=NULL;
	chidPtr_2=SearchTree(root,chidName_2);	//找到第二个人的位置 
	chidDepth_2=chidPtr_2->depth;
	if(chidDepth_1 != chidDepth_2)		//层数不同 
	{
		if(chidDepth_1 < chidDepth_2)
			printf("%s %s %d\n",chidName_2,chidName_1,chidDepth_2-chidDepth_1);
		else
			printf("%s %s %d\n",chidName_1,chidName_2,chidDepth_1-chidDepth_2);
	}
	else								//层数相同 
	{
		ancPtr=FindSameAncestor(chidPtr_1,chidPtr_2);	//找到公共祖先 
		printf("%s %s %d\n",ancPtr->name,chidName_1,chidDepth_1-ancPtr->depth);
		printf("%s %s %d\n",ancPtr->name,chidName_2,chidDepth_2-ancPtr->depth);
	}
	return 0;
}
Treep FindSameAncestor(Treep p,Treep q)
{
	flag=0;
	ptr=chidPtr_1,top=0;
	while(ptr!=root)	//回退祖先,记录沿路的指针地址 
	{
		path_1[top]=ptr;	//用栈存下 
		ptr=ptr->parent,
		top++;
	}
	path_1[top]=root;
	ptr=chidPtr_2,top=0;
	while(ptr!=root)	//回退祖先,记录沿路的指针地址 
	{
		path_2[top]=ptr;	//用栈存下
		ptr=ptr->parent,
		top++;
	}
	path_2[top]=root;
	for(i=0;path_1[i];i++)		//找第一个相同的指针地址即为最近公共祖先的地址 
	{
		for(j=0;path_2[j];j++)
		{
			if(path_1[i]==path_2[j])	//找到 
				flag=1;
			if(flag==1)
				break;
		}
		if(flag==1)
			break;
	}
	return path_1[i];	//返回最近公共祖先的指针 
}
void initTree(Treep t, int depth)	//插入树,跟之前一样 
{
	if(t==NULL)	return;
	if(strcmp(t->name,parName)==0)	
	{
		t->left=(Treep)malloc(sizeof(Tree));
		t->right=(Treep)malloc(sizeof(Tree));
		strcpy(t->left->name,chidName_1);
		t->left->left=t->left->right=NULL;
		t->left->depth=depth+1;
		t->left->parent=t;
		if(strcmp(chidName_2,"NULL")!=0)
		{
			strcpy(t->right->name,chidName_2);
			t->right->left=t->right->right=NULL;
			t->right->parent=t;
			t->right->depth=depth+1;
		}	
		else	t->right=NULL;
	}
	else
	{
		initTree(t->left,depth+1);	//递归时层数+1 
		initTree(t->right,depth+1);	//递归时层数+1 
	}
}
Treep SearchTree(Treep t,char* s)
{
	if(t==NULL && flag==0)	return NULL;	//还没找着所以递归回退NULL 
	else if (t==NULL && flag==1)	return ptr;	//找着了所以返回指针 
	if(strcmp(t->name,s)==0)	//找到了 
	{
		flag=1;
		ptr=t;
		return ptr;			//返回该姓名的指针 
	}	
	else
	{
		SearchTree(t->left,s); //左子树找 
		SearchTree(t->right,s);//右子树找 
	}
}

测试数据

假设当前目录下in.txt文件内容为:

21
a b c
b d NULL
d f NULL
f h l
h j NULL
j p NULL
p q NULL
l m NULL
m n NULL
n o NULL
c e NULL
e g NULL
g i NULL
i k NULL
k r s
r t NULL
t u v
u w NULL
w x NULL
x y NULL
y z NULL

【测试点1的输入输出】

a z
z a 12

【测试点2的输入输出】

v e
v e 6

【测试点3的输入输出】

o q
f o 4
f q 4

【测试点3的输入输出】

q t
a q 7
a t 7

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值