人类学研究对于家族很感兴趣,于是研究人员搜集了一些家族的家谱进行研究。实验中,使用计算机处理家谱。为了实现这个目的,研究人员将家谱转换为文本文件。下面为家谱文本文件的实例:
家谱文本文件中,每一行包含一个人的名字。第一行中的名字是这个家族最早的祖先。家谱仅包含最早祖先的后代,而他们的丈夫或妻子不出现在家谱中。每个人的子女比父母多缩进2个空格。以上述家谱文本文件为例,John
这个家族最早的祖先,他有两个子女Robert
和Nancy
,Robert
有两个子女Frank
和Andrew
,Nancy
只有一个子女David
。
在实验中,研究人员还收集了家庭文件,并提取了家谱中有关两个人关系的陈述语句。下面为家谱中关系的陈述语句实例:
研究人员需要判断每个陈述语句是真还是假,请编写程序帮助研究人员判断。
输入格式:
输入首先给出2个正整数N(2≤N≤100)和M(≤100),其中N为家谱中名字的数量,M为家谱中陈述语句的数量,输入的每行不超过70个字符。
名字的字符串由不超过10个英文字母组成。在家谱中的第一行给出的名字前没有缩进空格。家谱中的其他名字至少缩进2个空格,即他们是家谱中最早祖先(第一行给出的名字)的后代,且如果家谱中一个名字前缩进k个空格,则下一行中名字至多缩进k+2个空格。
在一个家谱中同样的名字不会出现两次,且家谱中没有出现的名字不会出现在陈述语句中。每句陈述语句格式如下,其中X
和Y
为家谱中的不同名字:
输出格式:
对于测试用例中的每句陈述语句,在一行中输出True
,如果陈述为真,或False
,如果陈述为假。
输入样例:
输出样例:
这个题我身边的朋友有用多叉树做的,还有用map映射的,我还是喜欢用我的老朋友左孩子右兄弟的二叉链表来做。
这个题的类型跟我上一篇介绍的目录树这个题考察的知识点时类似的,但是我觉得这个题作为一个进阶实验比目录树那个基础实验简单多了。思路就是用借助一个堆栈用前序遍历建树,写一个查询树中某个结点位置的函数,再写一个Judge函数判断关系是否正确就可以了。而判断逻辑也十分简单。话不多说,直接亮代码,代码上写了详细的注释,应该很容易就能明白思路。当然其实可以把Judge函数进行优化,陈姥姥说可以优化到两种情况,但是我懒得优化了,直接分五种情况更直白。但是陈姥姥还提出了一种优化这个代码的方法,即为人名建立二叉搜索树,这样的查询效率更高,我先把这个坑放在这里过段时间再来填,有兴趣的朋友如果实现了可以跟我交流交流。
注意一点,我们要进入一个循环来判断读入的结点的层数和上一个读入的结点层数之间的关系,所以第一个结点要特殊处理,因为第一个结点没有上一个结点,所以我们在进入循环之前先建立一颗有一个结点的二叉树并且把最早的祖先读到这个结点里面并且把让preNode指向这最早的祖先结点。之后我们再进入循环就ok了。
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include<iostream>
#include<stack>
using namespace std;
int N,M;
struct TreeNode
{
string name;
int layer;
TreeNode* child;
TreeNode* sibling;
};
typedef TreeNode* PtrToTreeNode;
typedef PtrToTreeNode BinTree;
BinTree CreateNode(string name, int layer)
{
PtrToTreeNode Node = new TreeNode;
Node->name = name;
Node->layer = layer;
Node->child = NULL;
Node->sibling = NULL;
return Node;
}
BinTree CreateFamilyTree()
{
int i,j,cnt,layer;//cnt用来记录空格的个数
PtrToTreeNode Node,preNode,tmpNode;
string s;
stack<BinTree> sta;
getline(cin,s);//不要用cin,cin会忽略掉单词前面的空格
PtrToTreeNode root = CreateNode(s,0);//创建一颗只有根结点的二叉树并读入最早的祖先
preNode = root;//令preNode指向根结点
for(i=1; i<N; i++)//开始逐行读入人名并插入二叉树
{
cnt = 0;
getline(cin,s);
for(j=0; j<s.length(); j++)//计算空格的个数
{
string ss = s.substr(j,1);
if(ss==" ")
cnt++;
}
Node = CreateNode(s.substr(cnt,s.length()-cnt),cnt/2);
if(Node->layer>preNode->layer)//如果层数大于前一个结点就插入它的左孩子
{
Node->sibling = preNode->child;
preNode->child = Node;
preNode = Node;
sta.push(Node);//压入栈中
}
else if(Node->layer==preNode->layer)//层数相等说明是兄弟,插入其右兄弟的位置
{
Node->sibling = preNode->sibling;
preNode->sibling = Node;
preNode = Node;
sta.push(Node);//压入栈中
}
else//层数大于前一个结点,说明这个结点应该是前一个结点的长辈,逐个弹出栈中的结点并检查
{ //直到碰到某个结点的层数与之相同,然后插入到这个结点右兄弟位置
while(true)
{
tmpNode = sta.top(); sta.pop();
if(tmpNode->layer==Node->layer)
{
Node->sibling = tmpNode->sibling;
tmpNode->sibling = Node;
preNode = Node;
sta.push(Node);//压入栈中
break;
}
}
}
}
return root;
}
PtrToTreeNode search(BinTree BT, string name)//递归的查找函数
{
BinTree ans1,ans2;
if(!BT) return NULL;//BT为NULL,说明查找失败,返回NULL
if(BT->name == name)//找到了,return 地址
return BT;
else//未找到,分别从左孩子和右兄弟处继续递归的查找
{
ans1 = search(BT->child,name);
ans2 = search(BT->sibling,name);
}
if(ans1) return ans1;//如果在左孩子的子树找到了就直接return地址
else
return ans2;//左孩子子树没找到,那就直接return右兄弟的查询结果即可
}
void Judge(BinTree root)//判断函数
{
int i;BinTree x,y,ptr;
string str[6];
for(i=0; i<6; i++)
cin>>str[i];
if(str[3]=="child")
{
y = search(root,str[5]);//先找到父亲的位置
if(y->child == NULL) { printf("False\n"); return; }//如果父亲没孩子,说明错误
if(y->child->name == str[0]) { printf("True\n"); return; }//有孩子且名字也一样,说明正确
ptr = y->child->sibling;//第一个孩子不是,那么从第一个孩子的兄弟开始找
while(ptr)//遍历他的所有兄弟
{
if(ptr->name == str[0]) { printf("True\n"); return; }
ptr = ptr->sibling;
}
//都没找到,那就说明错误了
printf("False\n"); return;
}
if(str[3]=="parent")//同上,把str[0]和str[5]换一下就可以了
{
y = search(root,str[0]);
if(y->child == NULL) { printf("False\n"); return; }
if(y->child->name == str[5]) { printf("True\n"); return; }
ptr = y->child->sibling;
while(ptr)
{
if(ptr->name == str[5]) { printf("True\n"); return; }
ptr = ptr->sibling;
}
printf("False\n"); return;
}
if(str[3]=="ancestor")
{
x = search(root,str[0]);//先定位祖先的位置
y = search(x->child,str[5]);//在其孩子为根的子树中查找str[5],注意不要在以x为根的子树中去寻找,因为x的右兄弟不是x的子孙后代
if(y)//找到了
{
printf("True\n"); return;
}
else//没找到
{
printf("False\n"); return;
}
}
if(str[3]=="descendant")//思路同上
{
x = search(root,str[5]);
y = search(x->child,str[0]);
if(y)
{
printf("True\n"); return;
}
else
{
printf("False\n"); return;
}
}
if(str[3]=="sibling")//判断兄弟关系
{
x = search(root,str[0]);//先定位兄弟str[0]
ptr = x->sibling;
while(ptr)//一直往右去找他的兄弟str[5],如果找到了就return true,没找到也不要紧
{
if(ptr->name == str[5])
{
printf("True\n"); return;
}
ptr=ptr->sibling;
}
x = search(root,str[5]);//定位兄弟str[5]
ptr = x->sibling;
while(ptr)//一直往右去找他的兄弟str[5],如果找到了就return true,没找到那就直接可以return false了
{
if(ptr->name == str[0])
{
printf("True\n"); return;
}
ptr=ptr->sibling;
}
printf("False\n"); return;
}
}
int main()
{
int i,j;
string s;
scanf("%d%d\n",&N,&M);
BinTree root = CreateFamilyTree();//建树
for(i=0; i<M; i++)//判断并输出
{
Judge(root);
}
return 0;
}