c语言实现家谱(孩子兄弟树)数据结构

一、需求分析

(一)题目

【问题描述】
家谱记载了一个家族的世系繁衍及重要人物事迹。使用树型结构对家谱进行管理,实现查看祖先和子孙个人信息,插入家族成员,删除家族成员的功能
【基本要求】
(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