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

本文档详述了使用C语言实现家谱管理的程序,通过孩子兄弟树结构,实现家谱成员信息的查找、插入、修改、删除等功能,以及查询家族关系和家谱的层次遍历。程序包括需求分析、概要设计、详细设计、调试分析和用户使用说明,涵盖了家谱数据结构的定义、基本操作和主程序流程。
摘要由CSDN通过智能技术生成

一、需求分析

(一)题目

【问题描述】
家谱记载了一个家族的世系繁衍及重要人物事迹。使用树型结构对家谱进行管理,实现查看祖先和子孙个人信息,插入家族成员,删除家族成员的功能
【基本要求】
(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
家谱管理系统可以使用形结构进行存储和管理家谱信息。在C语言中,可以使用结构体来定义家谱节点,结构体的成员可以包括该节点的姓名、性别、出生日期、父亲节点和子节点等信息。具体实现可以参考以下代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_NAME_LEN 20 // 家谱节点结构体 typedef struct family_tree_node { char name[MAX_NAME_LEN]; // 姓名 char gender; // 性别 char birthday[11]; // 出生日期,格式为yyyy-mm-dd struct family_tree_node *father; // 父亲节点 struct family_tree_node *child; // 子节点 struct family_tree_node *sibling; // 兄弟节点 } FamilyTreeNode; // 创建家谱节点 FamilyTreeNode *createFamilyTreeNode(char *name, char gender, char *birthday) { FamilyTreeNode *node = (FamilyTreeNode *)malloc(sizeof(FamilyTreeNode)); if (node == NULL) { printf("Error: createFamilyTreeNode failed, out of memory.\n"); return NULL; } strcpy(node->name, name); node->gender = gender; strcpy(node->birthday, birthday); node->father = NULL; node->child = NULL; node->sibling = NULL; return node; } // 添加子节点 void addChild(FamilyTreeNode *parent, FamilyTreeNode *child) { if (parent == NULL || child == NULL) { printf("Error: addChild failed, invalid arguments.\n"); return; } if (parent->child == NULL) { parent->child = child; } else { FamilyTreeNode *sibling = parent->child; while (sibling->sibling != NULL) { sibling = sibling->sibling; } sibling->sibling = child; } child->father = parent; } // 输出家谱信息 void printFamilyTree(FamilyTreeNode *root) { if (root == NULL) { return; } printf("%s %c %s\n", root->name, root->gender, root->birthday); FamilyTreeNode *child = root->child; while (child != NULL) { printFamilyTree(child); child = child->sibling; } } int main() { // 创建家谱 FamilyTreeNode *root = createFamilyTreeNode("张三", 'M', "1980-01-01"); FamilyTreeNode *child1 = createFamilyTreeNode("张四", 'M', "2000-01-01"); FamilyTreeNode *child2 = createFamilyTreeNode("张五", 'F', "2002-01-01"); addChild(root, child1); addChild(root, child2); FamilyTreeNode *grandChild1 = createFamilyTreeNode("张六", 'M', "2020-01-01"); addChild(child1, grandChild1); FamilyTreeNode *grandChild2 = createFamilyTreeNode("张七", 'F', "2022-01-01"); addChild(child1, grandChild2); // 输出家谱信息 printFamilyTree(root); // 释放内存 free(grandChild2); free(grandChild1); free(child2); free(child1); free(root); return 0; } ``` 在上面的代码中,我们定义了一个FamilyTreeNode结构体来表示家谱节点,其中包含姓名、性别、出生日期、父亲节点和子节点等信息。使用createFamilyTreeNode函数可以创建一个家谱节点,使用addChild函数可以将一个节点添加到另一个节点的子节点列表中。最后,使用printFamilyTree函数可以输出整个家谱的信息。需要注意的是,释放内存的操作也需要在程序结束时进行,以避免内存泄漏。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值