一、需求分析
(一)题目
【问题描述】
家谱记载了一个家族的世系繁衍及重要人物事迹。使用树型结构对家谱进行管理,实现查看祖先和子孙个人信息,插入家族成员,删除家族成员的功能
【基本要求】
(1)采用树形结构完成对家谱成员信息的建立,可利用孩子兄弟表示方法表示树型结构
(2)完成家谱成员信息查找、插入、修改、删除功能
(3)判断两个人的家族关系
(4)进行子孙、祖先、堂兄弟关系的查询
【拓展要求】
(1)实现树的层次遍历,显示家族每一代的成员
(2)打印家谱的树型结构操作
(3)判断两个成员是否属于直系或旁系三代关系
(4)自行设计家谱的其他操作
(二)程序所能达到的功能;
1—插入新人物
1.输入的形式:
例:姓名 性别(1/0) 配偶姓名 生日 生存状况(1/0) 父亲姓名
%s %d %s %d %d %s
姓名,父亲姓名,配偶姓名无输入值范围 性别和生存状况输入值范围为1/0 ,生日输入值范围为0~2021
2. 输出的形式:控制台打印函数的操作结果
3.测试数据:
正确输入:刘星 1 无 1999 1 贾政
正确输出:插入成功
错误输入1:刘星 2 无 1999 1 贾政
错误输出1:性别有误
错误输入2:刘星 1 无 2022 1 贾政
错误输出2:生日有误
错误输入3:刘星 1 无 1999 2 贾政
错误输出3:生存状况有误
错误输入4:刘星 1 无 1999 1 刘六
错误输出4:你的父亲不在家谱里
错误输入5:刘星 1 无 1 1 贾政
错误输出5:你的生日不能比父亲早
2—删除人物
1.输入的形式:
姓名,%s,输入无限制
2.输出的形式;控制台打印函数的操作结果
测试数据:贾政
测试结果:贾政和贾政的后代都从家谱中消失了
3—修改人物信息
1.输入的形式和输入值的范围:无
2. 输出的形式;把函数执行结果打印在控制台上
3.测试数据:贾宝玉
A.修改姓名为贾王 成功
b修改生存状况为0/1成功 2失败
c修改生日 0~2021为有效输入
d修改配偶名:无限制输入值的类型
E 修改性别
4—查找人物
输入:贾宝玉
输出:查找成功
5–人物关系查询
输入:贾宝玉
6—判断两人关系
输入:贾宝玉 贾政
输出:父子
7—凹入表方式打印树状家谱
1.输入的形式和输入值的范围:无
2. 输出的形式;凹入表方式打印家谱
8—层次遍历家谱
1.输入的形式和输入值的范围:无
2. 输出的形式;层次遍历家谱
二、概要设计
(一)数据类型的定义
决定采用孩子兄弟二叉树来表示家谱。
家谱可以看作是一颗树。许多家庭看作一片森林。每个森林都有唯一对应的二叉树。且二叉树非常方便操作和理解。下图演示了树转换为二叉树的过程。
所以需要定义以下几种数据类型:
1.CSTree 节点。传统一般定义为
typedef struct CSTNode
{
Elemtype data;
struct CSTNode *firstChild,*nextSibling;
}CSTNode;
- 1
- 2
- 3
- 4
- 5
但是由于本程序要实现的是家谱,所以添加指向父亲节点的指针是非常有必要的,可以让程序变得更加简便。
2.CSTree节点的数据域
需要包括人物的一系列信息。配偶,生日,姓名,性别,辈分,生存情况等
3.队列节点
在层次遍历家谱的时候会用到
(二)主程序的流程
打开程序,首先初始化,读取文件内容在程序里自动生成一个孩子兄弟树。在主菜单中,可以选择需要的操作模块,每个模块执行完后又回到菜单界面,直到用户选择退出。如图所示。
(三)各程序模块之间的调用关系。
本程序只含有两个模块。一个是主程序main.C,一个是孩子兄弟树的基本操作实现CSTree。主程序模块调用基本操作模块。
三、详细设计
(一)数据类型定义实现
族谱规则
族谱只会跟踪记录男性以及男性的后代,对于女性我们只会记录她在何时出嫁,并不记录她的后代,或者说族谱中的人员向上追溯的时候默认追溯的是父亲一支的关系;
逻辑分析
族谱与数据结构中树的概念相结合,每一个节点就是族谱中的个人,于是我们就需要知道每个人最基本的特性,于是就可以抽象化变成树中的属性;
父母 (parent) --人
兄弟 (brother) --人
孩子 (children) --人
姓名 (name) --字符串
生辰八字 (birthday) --日期
性别 (gender) --性别
那么我们对应的树的结构就出来了
1.数据域设计
typedef struct MSG
{
char name[100];//姓名
int sex;//性别 1为男性 0为女性
char fed[100];//配偶姓名
int seniority;//辈分
int birth;
int alive;//1为在世,0为已过世
}MSG;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2.兄弟孩子树节点设计
typedef struct CSTNode
{
MSG data;
struct CSTNode *firstChild,*nextSibling,*father,*mother;
//mother指针闲置了,后续用不到
}CSTNode,*CSTree,*CSForest;
- 1
- 2
- 3
- 4
- 5
- 6
其他问题
关于孩子的问题
很多人的孩子不止一个,那么我们就会将二儿子/三儿子添加到对应的孩子的brother指针,并且将parent指针指向自己哥哥的父母;
3.返回值设计
typedef enum status{
TRUE,
FALSE,
OK,
ERROR,
SUCCESS,
OVERFLOW,
EMPTY
}Status;//枚举类型返回值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4.队列节点设计
typedef struct LNode{ //链表和链表结点类型
CSTree data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
- 1
- 2
- 3
- 4
(二)基本操作模块代码实现(CSTree.c)
1.创建节点
void createCSTNode(CSTree*A){//没问题
(*A)=(CSTree)malloc(sizeof(CSTNode));
if((*A)==NULL)return;
initCSTNode(&(*A));
}
- 1
- 2
- 3
- 4
- 5
初始化节点数据避免出现野指针等情况
void initCSTNode(CSTree*A){// 没问题
if((*A)==NULL)return;
(*A)->father=NULL;
(*A)->mother=NULL;
(*A)->firstChild=NULL;
(*A)->nextSibling=NULL;
(*A)->data.birth=0;
(*A)->data.seniority=0;
(*A)->data.sex=0;
(*A)->data.alive=0;
strcpy((*A)->data.name,"无");
strcpy((*A)->data.fed,"无");
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
2.删除节点函数
思路:
1.如果A是父亲节点的第一个孩子,则让A的兄弟成为父亲节点的第一个孩子
2.否则,找到A的前驱节点指向A的后驱
然后在释放A的空间之前将A的后代全部移除,此处调用Destroy函数
void deleteNode(CSTree *A){
CSTree bro1;
if((*A)->father->firstChild==(*A)){
(*A)->father->firstChild=(*A)->nextSibling;
}
else
{
for(bro1=(*A)->father->firstChild;bro1->nextSibling!=(*A);){
bro1=bro1->nextSibling;
}
bro1->nextSibling=(*A)->nextSibling;
}
(*A)->nextSibling=NULL;
(*A)->father=NULL;
system("pause");
destroy(&(*A));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
销毁函数
思路:和二叉树的销毁一模一样,因为兄弟孩子树就是二叉树
void destroy(CSTree*A){
if((*A)==NULL)return;
destroy(&((*A)->firstChild));
destroy(&((*A)->nextSibling));
free(*A);
}
- 1
- 2
- 3
- 4
- 5
- 6
3.插入函数
参数:父亲节点指针和孩子节点指针
思路:此处要考虑年龄因素,即生日较小的人年龄较大,年龄最大的孩子应该为父亲节点的firstchild。
情况:1.如果father不存在firstchild,直接插入child即可
2如果firstchild年龄比child小,则child成为新的firstchild
3.如果firstchild年龄比child大,则遍历兄弟链表直到找到插入位置为止
void insertNode(CSTree*father,CSTree*child){
(*child)->data.seniority=(*father)->data.seniority+1;
(*child)->father=(*father);
int birth=(*child)->data.birth;
CSTree bro1=(*father)->firstChild;
if(bro1==NULL){
(*father)->firstChild=(*child);
return;
}
if(bro1->data.birth>=birth){
(*child)->nextSibling=bro1;
(*father)->firstChild=(*child);
return;
}
CSTree bro2=(*father)->firstChild;
bro1=bro2->nextSibling;
for(;bro1!=NULL&&birth>bro1->data.birth;){
bro2=bro2->nextSibling;
bro1=bro2->nextSibling;
}
(*child)->nextSibling=bro1;
bro2->nextSibling=(*child);
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
4.查找函数
参数:T为树的结点,name为需要找的人,B储存找到的结点位置
思路:先序遍历
void searchNode(CSForest T,char name[100],CSTree*B){
if(NULL==T)return;
if(strcmp(T->data.name,name)==0){
(*B)=T;
return;
}
searchNode(T->firstChild,name,&(*B));
if((*B)!=NULL)return;
searchNode(T->nextSibling,name,&(*B));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
5.修改个人信息函数
void changeMsg(CSTree*B,CSForest T){
if((*B)==NULL)return;
if((*B)->data.seniority==0){
printf("该节点为森林根结点,禁止操作!\n");
system("pause");return;
}
int choice;
A:system("cls");
showMsg(&(*B));
printf("\n");
printf("which msg do you want to change?\n");
printf("\n");
printf("********************************\n");
printf("\n");
printf(" 1.name 2.sex\n");
printf("\n");
printf(" 3.fedname 4.birth\n");
printf("\n");
printf(" 5.father 6.alive\n");
printf("\n");
printf(" 7.break\n");
printf("\n");
printf("********************************\n");
printf("\n");
printf("请输入序号:\n");
fflush(stdin);
scanf("%d",&choice);
switch(choice)
{
case 1:
printf("please enter the new name:");
char name[100];
fflush(stdin);
scanf("%s",name);
strcpy((*B)->data.name,name);
system("pause"); goto A;
case 2:
printf("你的性别为:(男性请输入1,女性请输入2)");
int sex;
fflush(stdin);
scanf("%d",&sex);
if(sex!=0||sex!=1){
printf("输入有误\n");
system("pause"); goto A;
}
(*B)->data.sex=sex;
system("pause"); goto A;
case 3:
printf("please enter the fed new name:");
char fed[100];
fflush(stdin);
scanf("%s",fed);
strcpy((*B)->data.fed,fed);
system("pause"); goto A;
case 4:
changeBirth(&(*B));
system("pause"); goto A;
case 5:
changeFather(&(*B),T);
system("pause"); goto A;
case 6:
if((*B)->data.alive)(*B)->data.alive=0;
else (*B)->data.alive=1;
system("pause"); goto A;
case 7:break;
default:goto A;
}
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
修改生日函数
思路:因为修改生日涉及到排序问题,所以为了方便我们可以把这个结点分离出来,再调用含自动排序功能的insert函数插入回二叉树内。
void changeBirth(CSTree *A){
printf("生日修改为:(请输入0~2021之间的整数)\n");
int birth;
fflush(stdin);
scanf("%d",&birth);
if(birth<0||birth>2021){
printf("输入有误\n");
system("pause");
return;
}
if((*A)->father->data.seniority!=0&&(*A)->father->data.birth>=birth){
printf("生日不能比父亲早\n");
return;
}
(*A)->data.birth=birth;
CSTree B;
CSTree C=(*A)->father;
if((*A)->father->firstChild==(*A)){
(*A)->father->firstChild=(*A)->nextSibling;
(*A)->nextSibling=NULL;
(*A)->father=NULL;
}
else{
for(B=(*A)->father->firstChild;B->nextSibling!=(*A);B=B->nextSibling);
B->nextSibling=(*A)->nextSibling;
(*A)->nextSibling=NULL;
(*A)->father=NULL;
}
insertNode(&C,&(*A));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
修改父亲函数
思路:先调用search函数找到新的父亲结点,从原来的地方将这个结点分离出来,再插入到新的结点。最后调用refreshseniority函数将该节点的孩子的辈分刷新。
void changeFather(CSTree *A,CSForest T){
printf("父亲修改为:\n");
int birth=(*A)->data.birth;
char name[100];
fflush(stdin);
scanf("%s",name);
CSTree newFather=NULL;
searchNode(T,name,&newFather);
showMsg(&newFather);
if(newFather==NULL){
printf("不存在这个人\n");
system("pause");
return;
}system("pause");
if(newFather->data.seniority!=0&&newFather->data.birth>=birth){
printf("生日不能比父亲早\n");
return;
}
CSTree B;
CSTree C=(*A)->father;
if((*A)->father->firstChild==(*A)){
(*A)->father->firstChild=(*A)->nextSibling;
(*A)->nextSibling=NULL;
(*A)->father=NULL;
}
else
{
B=(*A)->father->firstChild;
for(;B->nextSibling!=(*A);B=B->nextSibling);
B->nextSibling=(*A)->nextSibling;
(*A)->nextSibling=NULL;
(*A)->father=NULL;
}
insertNode(&newFather,&(*A));
refreshSeniority(&T);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
辈分刷新函数
思路:先序遍历
void refreshSeniority(CSForest*T){
if(NULL==(*T))return;
if((*T)->father==NULL)
(*T)->data.seniority=0;
else{
(*T)->data.seniority=(*T)->father->data.seniority+1;
}
refreshSeniority(&((*T)->firstChild));
refreshSeniority(&((*T)->nextSibling));
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
6.层次遍历输出函数
Status PrintTree(CSTree t){
LinkList L;
if(t==NULL){
printf(" 森林不存在\n");
return OK;
}
InitQueue(&L);//初始化队列
Traverse(t->firstChild,L);//利用队列输出
DestroyQueue(L);//销毁队列
return OK;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
用队列遍历输出CS树
Status Traverse(CSTree T,LinkList L){
CSTree P=T;
CSTree K;
int i=0;
int j=1;
while (P)//p指向森林里的每棵树
{
printf("第%d个家族\n",j);
K=P;//利用k来遍历以p为根节点子树中的节点
Enqueue(L,K);//根节点入队
while (IfEmpty(L)==FALSE)//只要队列不为空就依次出队直到队空
{
Dequeue(L,&K);
if(i!=K->data.seniority){
printf("\n");
i=K->data.seniority;
}
printf("[%d|%d|%s]",K->data.seniority,K->data.birth,K->data.name);
if(K->firstChild){//如果该节点不是森林中的叶节点,则进入下一层
K=K->firstChild;
Enqueue(L,K);
while (K->nextSibling)
{//入队同层兄弟节点
K=K->nextSibling;
Enqueue(L,K);
}
}
}
P=P->nextSibling;
printf("\n");
j++;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
其他队列相关操作
//初始化队列
Status InitQueue(LinkList *L){
(*L)=(LNode*)malloc(sizeof(LNode));//分配结点空间
if(*L==NULL) //分配失败
return OVERFLOW;
(*L)->next=NULL;
return OK;
}
//新建结点
LNode* CreateNode(BTNode *p){
LNode *q;
q=(LNode*)malloc(sizeof(LNode));//分配结点空间
if(q!=NULL){ //分配成功
q->data=p;
q->next=NULL;
}
return q;
}
//元素q入队列
Status Enqueue(LNode *p,BTNode *q){
if(p==NULL)
return ERROR;
while(p->next!=NULL) //调至队列最后
p=p->next;
p->next=CreateNode(q);//生成结点让q进入队列
return OK;
}
//出队列,并以q返回值
Status Dequeue(LNode *p,BTree *q){
LNode *aq;
if(p==NULL||p->next==NULL) //删除位置不合理
return ERROR;
aq=p->next;//修改被删结点aq的指针域
p->next=aq->next;
(*q)=aq->data;
free(aq); //释放结点aq
return OK;
}
//队列判空
Status IfEmpty(LinkList L){
if(L==NULL) //队列不存在
return ERROR;
if(L->next==NULL)//队列为空
return TRUE;
return FALSE;//队列非空
}
//销毁队列
void DestroyQueue(LinkList L){
LinkList p;
if(L!=NULL){
p=L;
L=L->next;
free(p); //逐一释放
DestroyQueue(L);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
7.输出所有堂兄堂弟
void inorderCousin(CSTree T,CSTree A){
if(NULL==T)return;
if(T->data.seniority>=3)
if(T->father->father==A->father->father&&T!=A)
printf("[%d|%d|%s]\n",T->data.seniority,T->data.birth,T->data.name);
inorderCousin(T->firstChild,A);
inorderCousin(T->nextSibling,A);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
8.凹入表输出家谱
思路:此为树的先根遍历–>对应为二叉树存储的先序遍历
void PrintAsTree(CSTree T) {
int cnt;
if (T) {
//输出空格
for (cnt=1; cnt<T->data.seniority; cnt++) printf(" ");
//输出字符
printf("[%d|%d|%s]\n",T->data.seniority,T->data.birth,T->data.name);
printf("\n");
PrintAsTree(T->firstChild);
PrintAsTree(T->nextSibling);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(三)主程序代码实现
此处设定了全局变量,
CSForest T;
CSTree A,B;
T为根节点,A,B记录search函数返回值
1.初始化函数
功能:从文件中读取信息自动生成孩子兄弟二叉树
void init(){
createCSTNode(&T);
FILE*r;
r= fopen("familytree.txt","r");
if(r==NULL)
{
printf("打开文件失败!");
}
A=NULL;
B=NULL;
char fatherName[100];
createCSTNode(&A);
while(fscanf(r,"%s %d %s %d %d %s",&A->data.name,&A->data.sex,&A->data.fed,&A->data.birth,&A->data.alive,fatherName)!=EOF)
{
printf("%s %d %s %d %d %s",A->data.name,A->data.sex,A->data.fed,A->data.birth,A->data.alive,fatherName);
printf("search\n");
searchNode(T,fatherName,&B);
if(B==NULL){
printf("此人的父亲不在家谱里!\n");
free(A);
}
if(B->data.seniority!=0&&B->data.birth>=A->data.birth){
printf("生日不能比父亲早\n");
free(A);
}
else {
printf("insert\n");
insertNode(&B,&A);
}
B=NULL;
A=NULL;
createCSTNode(&A);
}
free(A);
A=NULL;
fclose(r);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
2.主菜单函数
void welcome()
{
int choice;
A:system("cls");
printf(" 主菜单\n");
printf(" -----------------------------------------------------\n");
printf("******************************************************\n");
printf("\n");
printf(" 1---插入新人物 2---删除人物\n");
printf("\n");
printf(" 3---修改人物信息 4---查找人物 \n");
printf("\n");
printf(" 5---人物关系查询 6---判断两人关系\n");
printf("\n");
printf(" 7---凹入表方式打印树状家谱 8---层次遍历家谱\n");
printf("\n");
printf(" 9---退出菜单\n");
printf("\n");
printf("****************************************************\n");
printf(" --------------------------------------------------\n");
printf("\n");
printf("请输入序号:\n");
fflush(stdin);
scanf("%d",&choice);
switch(choice)
{
case 1:
insertNew();
system("pause"); goto A;
case 2:
delete();
system("pause"); goto A;
case 3:
change();
system("pause"); goto A;
case 4:
search();
system("pause"); goto A;
case 5:
relationship();
system("pause"); goto A;
case 6:
judge();
system("pause"); goto A;
case 7:
PrintAsTree(T);
system("pause"); goto A;
case 8:
PrintTree(T);
system("pause"); goto A;
case 9:break;
default:goto A;
}
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
3.插入新人物
void insertNew(){
char fatherName[100];
A=NULL;
B=NULL;
createCSTNode(&A);
printf("请输入:姓名 性别(1/0) 配偶姓名 生日 生存状况(1/0) 父亲姓名\n ");
printf(" 例如: 刘星 1 无 19 1 刘六\n ");
scanf("%s %d %s %d %d %s",&A->data.name,&A->data.sex,&A->data.fed,&A->data.birth,&A->data.alive,fatherName);
printf("%s %d %s %d %d %s",A->data.name,A->data.sex,A->data.fed,A->data.birth,A->data.alive,fatherName);
if(A->data.sex!=1&&A->data.sex!=0){
printf("性别输入有误!\n");
free(A);
A=NULL;
return;
}
if(A->data.alive!=1&&A->data.alive!=0){
printf("生存状况输入有误!\n");
free(A);
A=NULL;
return;
}
searchNode(T,fatherName,&B);
if(B==NULL){
printf("此人的父亲不在家谱里!\n");
free(A);
A=NULL;
return;
}
if(B->data.seniority!=0&&B->data.birth>=A->data.birth){
printf("生日不能比父亲早\n");
free(A);
A=NULL;
B=NULL;
return;
}
insertNode(&B,&A);
A=NULL;
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
4.判断两者关系的函数
void judge(){
A=NULL;
B=NULL;
char name[100];
printf("请输入姓名1:");
scanf("%s",name);
searchNode(T,name,&A);
if(A==NULL){
printf("不存在名为%s的人\n",name);
return;
}
printf("请输入姓名2:");
scanf("%s",name);
searchNode(T,name,&B);
if(B==NULL){
printf("不存在名为%s的人\n",name);
A=NULL;
return;
}
CSTree temp=NULL;
if(A->data.birth>B->data.birth){
temp=B;
B=A;
A=temp;
}
if((A!=T&&B!=T)&&A==B->father){
printf("%s是%s的父亲\n",A->data.name,B->data.name);
if(B->data.sex){
printf("%s是%s的儿子\n",B->data.name,A->data.name);
}
else
{
printf("%s是%s的女儿\n",B->data.name,A->data.name);
}
printf("关系:三代内直系\n");
}
else if((A!=T&&B!=T)&&A==B->father->father){
printf("%s是%s的爷爷\n",A->data.name,B->data.name);
if(B->data.sex){
printf("%s是%s的孙子\n",B->data.name,A->data.name);
}
else
{
printf("%s是%s的孙女\n",B->data.name,A->data.name);
}
printf("关系:三代内直系\n");
}
else if((A->data.seniority>1&&B->data.seniority>1)&&A->father==B->father){
if(A->data.sex){
printf("%s是%s的堂兄\n",A->data.name,B->data.name);
}
else
{
printf("%s是%s的堂姐\n",A->data.name,B->data.name);
}
if(B->data.sex){
printf("%s是%s的堂弟\n",B->data.name,A->data.name);
}
else
{
printf("%s是%s的堂妹\n",B->data.name,A->data.name);
}
printf("关系:三代内旁系\n");
}
else if((A->data.seniority>1&&B->data.seniority>1)&&A->father==B->father->father){
if(A->data.sex){
printf("%s是%s的叔叔\n",A->data.name,B->data.name);
}
else
{
printf("%s是%s的姑妈\n",A->data.name,B->data.name);
}
if(B->data.sex){
printf("%s是%s的侄子\n",B->data.name,A->data.name);
}
else
{
printf("%s是%s的侄女\n",B->data.name,A->data.name);
}
printf("关系:三代内旁系\n");
}
else
{
printf("关系:%s,%s两者非三代内旁系或直系\n",A->data.name,B->data.name);
}
A=NULL;
B=NULL;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
5.查找与自己相关的人的函数
void relationship(){
B=NULL;
A=NULL;
char name[100];
printf("请输入姓名:");
scanf("%s",name);
searchNode(T,name,&B);
if(B==NULL){
printf("不存在名为%s的人\n",name);
return;
}
int choice;
C:system("cls");
showMsg(&B);
printf("\n");
printf("which relationship do you want to choose?\n");
printf("\n");
printf("********************************\n");
printf("\n");
printf(" 1.ancestor 2.posterity\n");
printf("\n");
printf(" 3.cousin 4.break\n");
printf("\n");
printf("********************************\n");
printf("\n");
printf("请输入序号:\n");
fflush(stdin);
scanf("%d",&choice);
switch(choice)
{
case 1:
A=B->father;
for(;A->data.seniority!=0;A=A->father){
printf("[%d|%d|%s]\n",A->data.seniority,A->data.birth,A->data.name);
}
system("pause"); goto C;
case 2:
PrintTree(B);
system("pause"); goto C;
case 3:
inorderCousin(T,B);
system("pause"); goto C;
case 4:break;
default:goto C;
}
B=NULL;
A=NULL;
return;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
6.删除函数
void delete(){
A=NULL;
char name[100];
printf("请输入姓名:");
scanf("%s",name);
searchNode(T,name,&A);
if(A==NULL){
printf("不存在名为%s的人\n",name);
return;
}
deleteNode(&A);
A=NULL;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
7.修改个人信息函数
void change(){
A=NULL;
char name[100];
printf("请输入姓名:");
scanf("%s",name);
searchNode(T,name,&A);
if(A==NULL){
printf("不存在名为%s的人\n",name);
return;
}
changeMsg(&A,T);
A=NULL;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
灰色的为基本操作函数。
四、调试分析
1)调试过程中遇到的问题;在运行过程中,进入查找功能,输入一个错误的姓名后,程序进入死循环没有反应与输出,遇到这个情况一开始以为是程序错误,还重新编译几次,发现还是有问题。后面回头进入程序里面进行差错,发现在搜索的时候没有考虑到全部的地址都遍历了以后将程序退出,导致程序一直循环,没有返回错误。后面加了一个计数标志,才把问题解决。
(2)算法改进思想:可对程序代码进行代码量减少,运用函数把多次使用的代码模块来调用,减少代码的冗余。 在进行插入和删除操作前可先进行查找操作,进而给予用户适当的提示,避免用户进行不必要重复的操作。
(3)经验与体会①在做一个较大的程序过程中,应该学会编写程序边运行,即完成了一个功能,也要对其调试,这样有利于我们高效地完成项目,并在调试BUG的过程可以大大减小难度。②必须要有良好的编程习惯。首先编码风格要统一规范,这样不仅有利于代码的阅读,更有利于代码的维护。其次在一些代码方面要细心谨慎,减少BUG出现的机率。③写的程序需人性化,虽C语言没有什么图形界面,但也要考虑人性化的问题,在操作时要给予适当的提示。
五、 用户使用说明
说明如何使用你编写的程序,详细列出每一步的操作步骤:
1.打开程序,程序自动读取familytree.TXT文件,自动生成兄弟孩子树。
2.可以看到主面板上的每一个操作都有标序号。只需要按照序号输入即可。
3.输入1插入新人物。需要输入正确的格式和值才能够插入成功
4.输入2删除某个家谱中查询的到的人物(包括他的后代)
5.输入3修改人物信息。进入另一个界面,可以修改六个性质,输入7则返回主菜单
6.输入4可以查找人物信息
7.输入5跳转到新界面,可以选择查看与自己有亲戚关系的人
8.选择6,输入两人名字可以得知他们是什么关系
9.选则7,8则分别用不同方式打印家谱
10.选择9则自动退出程序
六、 测试结果
1—插入新人物
正确输入:
错误输入:
2—删除人物
删除前:
删除后:
3—修改人物信息
4—查找人物
输入:贾宝玉
输出:不存在(因为这时候贾宝玉改名叫贾王了)
5–人物关系查询
6—判断两人关系
7—输出凹入表
8—层次遍历输出家谱
七、附录
八、可运行文件下载地址
https://download.csdn.net/download/weixin_45849757/18420359?spm=1001.2014.3001.5501