一、需求分析
(一)题目
【问题描述】
家谱记载了一个家族的世系繁衍及重要人物事迹。使用树型结构对家谱进行管理,实现查看祖先和子孙个人信息,插入家族成员,删除家族成员的功能
【基本要求】
(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;
但是由于本程序要实现的是家谱,所以添加指向父亲节点的指针是非常有必要的,可以让程序变得更加简便。
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;
2.兄弟孩子树节点设计
typedef struct CSTNode
{
MSG data;
struct CSTNode *firstChild,*nextSibling,*father,*mother;
//mother指针闲置了,后续用不到
}CSTNode,*CSTree,*CSForest;
其他问题
关于孩子的问题
很多人的孩子不止一个,那么我们就会将二儿子/三儿子添加到对应的孩子的brother指针,并且将parent指针指向自己哥哥的父母;
3.返回值设计
typedef enum status{
TRUE,
FALSE,
OK,
ERROR,
SUCCESS,
OVERFLOW,
EMPTY
}Status;//枚举类型返回值
4.队列节点设计
typedef struct LNode{
//链表和链表结点类型
CSTree data; //数据域
struct LNode *next; //指针域
}LNode, *LinkList;
(二)基本操作模块代码实现(CSTree.c)
1.创建节点
void createCSTNode(CSTree*A){
//没问题
(*A)=(CSTree)malloc(sizeof(CSTNode));
if((*A)==NULL)return;
initCSTNode(&(*A));
}
初始化节点数据避免出现野指针等情况
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;
}
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));
}
销毁函数
思路:和二叉树的销毁一模一样,因为兄弟孩子树就是二叉树
void destroy(CSTree*A){
if((*A)==NULL)return;
destroy(&((*A)->firstChild));
destroy(&((*A)->nextSibling));
free(*A);
}
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;
}
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));
}
5.修改个人信息函数
void changeMsg(CSTree*B,CSForest T){
if((*B)==NULL)return;
if((*B)->data.seniority==0){
printf