家谱管理系统(数据结构课程设计-树形结构程序设计模块)


一、实验目的及要求

1、实验目的

(1)理解家谱管理系统的基本要求和功能。
(2)掌握树这种数据结构的存储结构和基本操作。
(3)学习排序算法并将其应用于家谱成员按出生日期排序的功能。
(4)分析算法的时间复杂度和空间复杂度,掌握算法效率的评估方法。

2、实验要求

家谱管理系统是查询家谱信息必不可少的一部分,利用家谱管理系统可以清楚的了解家族成员信息。本程序实现一个简易的家谱管理系统。

实验要求如下:

(1)该家谱管理系统是通过来实现,定义其存储结构。
(2)需要设置普通用户、管理员两种角色,不同角色登录后的权限各不相同,普通用户可以进行查询;管理员有对成员增加、删除和修改的权限。
(3)家谱中成员的信息中均应包含姓名、出生日期、婚否、地址、健在否、死亡日期(若其已死亡)等,也可附加其它信息。
(4)查询功能。可按照姓名查询,输出成员信息(包括其本人、父亲、孩子的信息、所在辈分等);按照出生日期查询成员名单。
(5)按出生日期对家谱中所有人排序
(6)输入任意两个姓名,能够查询这两个姓名之间的是否具有直系祖先后裔关系
(7)分析算法的时间复杂度和空间复杂度。

二、功能设计

  • 登录
  • 查询成员
  • 增加成员
  • 删除成员
  • 修改成员信息
  • 按出生日期对家谱中所有人排序
  • 查询两人是否有父子关系

三、系统流程图

在这里插入图片描述

四、数据结构及函数功能模块设计:

1、定义个人信息结构体ElemType,包含姓名、出生日期、是否结婚、住址、是否健在以及若死亡的死亡日期各种信息。
2、定义孩子节点ChildPtr,包含自身的标记即数组下标,还有指针域。
3、定义表头结构CTBox,包含父母的数组下标、个人信息还有指向第一个孩子的指针。
4、定义树结构CTree,包含节点数组、根的位置和节点数还有包含删除后的人数。
5、void User_login(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:用户账户密码管理。
6、void main_menu_super(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:管理员菜单。
7、void main_menu_normal(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:普通用户菜单。
8、char get_choice_menu_super()
功能:管理员菜单的选择。
9、char get_choice_menu_normal()
功能:普通用户菜单的选择。
10、char get_choice_Inquire()
功能:查询系统的选择。
11、char get_choice_Modify()
功能:修改菜单的选择。
12、ElemType Input()
功能:录入成员信息。
13、void init(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:初始化家族谱。
14、void show_seniority(CTree *tree, int label)
参数:CTree *tree为传入包含家谱信息的树,label为此人在数组中的位置。
功能:显示查询此人的辈分。
15、Status Exist_Name(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:以姓名查询是否存在此人。
16、Status Exist_BothDay(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:以出生日期判断是否存在此人。
17、void Inquire(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:查询功能。
18、void Add_Child(CTree *tree, int i)
参数:CTree *tree为传入包含家谱信息的树,i为数组中已经存了的人数,接下来就从i继续接着添加。
功能:为数组节点增加孩子。
19、void Add_Parent_Person(CTree *tree, int i)
参数:CTree *tree为传入包含家谱信息的树,i为数组中已经存了的人数,接下来就从i继续接着添加。
功能:增加新成员父亲节点。
20、Status Add_Person(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:增加新成员。
21、Status Remove(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:删除功能。
22、Status Modify(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:修改某人信息。
23、Status Sort_BothDay(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:对家谱里的人按照出生日期进行排序。
24、void Destroy(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:释放内存空间。
25、void Show_Everyone(CTree *tree)
参数:CTree *tree为传入包含家谱信息的树。
功能:显示家谱表。

五、 核心算法设计与实现

1、采用树来存储,因为一个人可以有多个孩子,所以不用二叉树而用树来存储。存储方式结合双亲表示法和孩子表示法,这样可以方便后续功能的实现。

2、进入程序后首先是登录界面,选择是普通用户还是管理员登录,选择后然后输入账号和密码进入各自的功能界面。账号和密码都是内置的,输入时会有提示。

3、进入功能主菜单后选择相应功能调用相应的函数来实现,如果输入的选项不存在则提示输入错误并重新输入直到输入正确。功能主菜单会一直循环来实现各种功能,直到选择退出登录可以回到登录界面切换账号。

4、主菜单和选择功能放到一个函数中,然后返回选项到另一个函数中,在这个函数中去配对相应的函数来实现功能。

5、求辈分的算法为从这个人一直往根节点遍历,直到遍历到根节点停止,根据遍历的分支数可以求出辈分,也就是这个结点在树的深度。

6、查询功能分为三种,在选择完查询功能后再继续选择查询方式,一种是根据姓名查询,遍历数组,如果存在此名且未被删除则输出这个名字的所有人以及他们的父亲和孩子信息与辈分,否则提示查询不到然后选择是继续查询还是返回主菜单;一种是根据出生日期来查询,如果家谱中存在在这一天出生的则输出在这一天出生的所有人以及他们的父亲和孩子信息与辈分,否则提示查询不到然后选择是继续查询还是返回主菜单;一种是输入两个人的姓名,如果输入的两个姓名有一个不存在就直接提示输入名字不存在然后选择是继续查询还是返回主菜单,否则查询是否有父子关系,然后查询完也选择是继续查询还是返回主菜单。

7、添加新成员首先输入所要添加的人数,然后输入个人信息,然后再为其添加孩子信息,采用头插法往其孩子域添加孩子,与图的邻接表操作方式差不多,然后再添加其父亲是谁,然后为父亲的孩子域再用头插法插入这个孩子。

8、删除成员时可以先选择是否显示家谱信息表,这样可以先了解所要删除的成员,防止误删。删除操作先输入所要删除的人的姓名,如果不存在则提示并退出,否则才继续删除。我最终设计的是删除某成员就将其后代也删除,在现实生活中也如此,如果某人移出家谱其后代一般也跟着移出。删除时我采用的是假删除,用了个全局数组来标记每个人是否被删除,一开始我是想删除结点,但是后来发现这样对其他功能的操作会有影响,对树的其他操作不便。

9、修改信息也是可以先选择是否显示家谱信息表,这样可以先了解所要修改的成员的原先信息,提高修改效率,然后选择是要修改什么信息再继续修改。

10、对家谱成员进行按出生日期排序,由于不能在原数组中进行排序,因为如果排序则原来的下标信息会错乱从而导致树的结构错乱,所以额外增加了个数组作为辅助空间,存储排序后的成员在原先数组中的下标,排序方法类似于选择排序,先选择最小的出生日期放在数组第一个位置,然后将其标记为已排序,然后再接着遍历选出次小的也存入排序数组,如此循环最后排序数组存储的就是排序后的成员下标,最后输出按出生日期排序的所有成员信息,被删除的不输出。

11、对于输入有固定范围的都会有判错机制,比如选择选项时如果输入的选项没有对应的功能即超出范围则会循环重新输入,直到输入正确为止。

六、系统实现

1、程序主界面截图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本次程序总共要13种命令,每种命令通过输入相应的单词就能实现。

2、关键部分的实现过程。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

七、结果分析

此家谱管理系统的时间复杂度为O(n2),因为在排序操作时时间复杂度达到了O(n2),即使前面求辈分只需O(logn)以及遍历只需O(n),但平均下来还是O(n2)。
空间复杂度为O(n),需要n个空间来存储n个成员,n个辅助空间来在排序时可以用,总的平均下来为O(n)。

八、实验心得

通过本次课程设计实现了家谱管理系统,存储结构采用的是树,结合双亲表示法和孩子表示法,各种功能的操作也是基于树来实现的。本次课程设计让我学会了关于树的存储方式和相关操作,之前实验都是基于二叉树的没有涉及到树,所以一开始做这个课程设计感到非常陌生,但因此我也学会了许多,对树形结构也有了更深的理解。

本次课程设计的亮点在于采用树来存储而不用二叉树,一个人可以有多个孩子而不止两个。且对于输入都会有判错机制,在输入选项时会判断输入的是否在范围内,如果不在会提示重新输入,直到对为止,在输入删除或编辑哪个成员时也会先判断是否在家谱内,不在会提示并退出。还有在删除或编辑前也可以选择是否先显示家谱表,然后再进行删除或编辑,这样可以先了解,防止删除或编辑出错,然后删除或编辑完可以选择是否继续,实现删除或编辑的连续,不用回到主菜单再重新进入删除或编辑。

目前这个家谱管理系统还存在一定的不足,在输入添加时只能先输入祖先再输入后代,因为在添加成员时需要添加该新成员的父亲是谁,此时会在家谱中搜索,如果搜索不到则会提示找不到,只有已存在了才会将其连接上,如果要解决做这个问题的话就只能不连接,只是将其父亲作为这个新成员结点的一个信息,但这样就无法体现树的应用了,没有实现将祖先和后代连接起来,所以最终我恢复了这个做法,只是输入时会有限制而已。

九、源代码

C语言

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_TREE_SIZE 50
#define LEN 20
#define ERROR 0
#define OK 1

int delete[MAX_TREE_SIZE] = {
   0};

typedef int Status;

typedef struct // 个人信息结构体
{
   
    char name[LEN];     // 姓名
    char bothDay[LEN];  // 出生日期
    int getMatings;     // 是否结婚
    char address[LEN];  // 其住址
    int keepAlive;      // 是否健在
    char deathDay[LEN]; // 若死亡,死亡日期
} ElemType;

typedef struct CTNode // 孩子节点
{
   
    int child;           // 自身的标记,数组下标
    struct CTNode *next; // 指针域
} ChildPtr;

typedef struct // 表头结构
{
   
    int parent;    // 父母的信息,数组下标
    ElemType data; // 个人信息
    ChildPtr *firstchild;
} CTBox;

typedef struct // 树结构
{
   
    CTBox nodes[MAX_TREE_SIZE]; // 节点数组
    int r, n;                   // 根的位置和节点数
    int denum;                  // 包含删除后的人数
} CTree;

void User_login(CTree *tree);               // 用户账户密码管理
void main_menu_super(CTree *tree);          // 管理员菜单
void main_menu_normal(CTree *tree);         // 普通用户菜单
char get_choice_menu_super();               // 超级用户菜单的选择
char get_choice_menu_normal();              // 普通用户菜单的选择
char get_choice_Inquire();                  // 查询系统的选择
char get_choice_Modify();                   // 修改菜单的选择
ElemType Input();                           // 录入成员信息函数
void init(CTree *tree);                     // 初始化家族谱
void show_seniority(CTree *tree, int flag); // 显示查询此人时候的辈分
Status Exist_Name(CTree *tree);             // 以姓名查询是否存在此人
Status Exist_BothDay(CTree *tree);          // 以出生日期判断是否存在此人
void Inquire(CTree *tree);                  // 查询功能
void Add_Child(CTree *tree, int i);         // 为数组节点增加孩子
void Add_Parent_Person(CTree *tree, int i); // 增加新成员时父亲节点的动态变化
Status Add_Person(CTree *tree);             // 增加新成员
Status Remove(CTree *tree);                 // 删除某人
Status Modify(CTree *tree);                 // 修改某人信息
Status Sort_BothDay(CTree *tree);           // 对家谱里的人按照出生日期进行排序
void Destroy(CTree *tree);					// 释放内存空间 
void Show_Everyone(CTree *tree);			//显示家谱表 

void init(CTree *tree)
{
   
    tree->denum = 0;
    tree->n = 0;
    tree->r = 0;
}

void User_login(CTree *tree)
{
   
    char initial_code[LEN] = {
   "123456"};       // 密码
    char normalUser[LEN] = {
   "normal_account"}; // 普通用户的账号
    char superUser[LEN] = {
   "super_account"};   // 超级用户的账号
    char account[LEN];                         // 接收账号输入
    char password[LEN];                        // 接收密码输入
    int login = 1;
    while (login)
    {
   
        printf("***********\t欢迎进入家族管理系统\t***********\n");
        printf("a:普通用户登录\tb:管理员登录\tc:退出程序\n");
        printf("*************************************************\n");
        int flag = 1, tag = 1;
        char ch;
        printf("请输入选项选择功能:");
        while (flag)
        {
   
            fflush(stdin);
            scanf("%c", &ch);
            fflush(stdin);
            if (ch == 'a')
            {
   
                printf("请输入普通用户账号(normal_account):");
                while (tag)
                {
   
                    fflush(stdin);
                    scanf("%s", account);
                    fflush(stdin);
                    if (!strcmp(account, normalUser))
                    {
   
                        tag = 0;
                    }
                    else
                    {
   
                        printf("账号不存在,请重新输入(normal_account):");
                    }
                }
                tag = 1;
                printf("请输入普通用户密码(123456):");
                while (tag)
                {
   
                    fflush(stdin);
                    scanf("%s", password);
                    fflush(stdin);
                    if (!strcmp(password, initial_code))
                    {
   
                        printf("普通用户登录成功!\n");
                        main_menu_normal(tree); // 显示普通用户的菜单
                        tag = 0;
                    }
                    else
                    {
   
                        printf("密码不正确,请重新输入(123456):");
                    }
                }
                flag = 0;
            }
            else if (ch == 'b')
            {
   
                printf("请输入管理员账号(super_account):");
                while (tag)
                {
   
                    fflush(stdin);
                    scanf("%s", account);
                    fflush(stdin);
                    if (!strcmp(account, superUser))
                    {
   
                        tag = 0;
                    }
                    else
                    {
   
                        printf("账号不存在,请重新输入(super_account):");
                    }
                }
                tag = 1;
                printf("请输入管理员密码(123456):");
                while (tag)
                {
   
                    fflush(stdin);
                    scanf("%s", password);
                    fflush(stdin);
                    if (!strcmp(password, initial_code))
                    {
   
                        printf("管理员登录成功!\n");
                        main_menu_super(tree); // 显示管理员的菜单
                        tag = 0;
                    }
                    else
                    {
   
                        printf("密码不正确,请重新输入(123456):");
                    }
                }
                flag = 0;
            }
            else if (ch == 'c')
            {
   
                flag = 0;
                login = 0;
            }
            else
            {
   
                printf("输入有误,请重新输入:");
            }
        }
    }
}

void main_menu_super(CTree *tree) // 超级用户的菜单
{
   
    int status; // 判断选择后执行的成功与否
    int choice; // 根据选择而执行相关功能
    int flag = 1;
    int tag = 1;
    int n, flag1 = 1;
    while (flag)
    {
   
        choice = get_choice_menu_super();
        switch (choice)
        {
   
        case 'a':
            status = Add_Person(tree); // 调用增加成员函数,如果成功添加,则返回OK,反之,返回ERROR
            if (status)
            {
   
                printf("添加新成员成功!\n");
            }
            break;
        case 'b':
            Inquire(tree); // 调用查询功能
            break;
        case 'c':
            flag1 = 1;
            while (flag1)
            {
   
                status = Modify(tree); // 调用修改成员函数,如果成功修改,则返回OK,反之,返回ERROR
                if (status)
                {
   
                    printf("修改此人操作成功!\n");
                }
                else
                {
   
                    printf("家谱中不存在此人!\n");
                }
                tag = 1;
                while (tag)
                {
   
                    printf("请输入1或2选择是否继续修改(1:继续 2:退出):");
                    fflush(stdin);
                    scanf("%d", &n);
                    fflush(stdin);
                    if (n == 
【功能介绍】 1.该族谱管理系统一改以往修谱系统的特点,增加了输出word文档的功能,方便在打印前对出谱资料的二次修改,具有很强的可修改性。另外,可以将族人图形资料输出到word文档,和族人资料一起一打印。可以打印输出族人的图形等可见信息。 2.可以在数据库内保存家族和族人的语音、视频、扫描资料等重要资料,由于保存在数据库内,所以具有保密性。家谱资料在数据库内可以删除、查看,方便整个家族资料的管理和完备性。 3.可以将世系树的结构输出到文本文档,对于家族的分支结构在树上得到很好的诠释。由于文本文档不具有格式性, 所以可以输出无限大的世系图,可以将整个家族的世系图完整输出 。 4.具有输出部分世系图分支的功能,可以分段输出世系图,便于将世系图分段保存和管理。配合整个世系图和分段世系图,对于某一分支可以了解自家分支的世系全貌。 5.可以管理多个配偶信息,配偶信息的输出是附加在族人后面,对于家庭的完整性得到很好的体现。配偶信息资料全,可以了解配偶在自家的排行、兄弟姐妹等情况。 6.自定义家谱输出格式,方便输出古代的五代格式。五代输出按大排行方式输出,符合大家的排行习惯。 7.族人输出按照大排行的顺序输出,例子老大的儿子输出要排在老二儿子的前边, 老大的孙子输出也要排在老二孙子的前边,以此类推。 8.可以管理家族的历史事件,历史人物,可以任意增加历史事件和历史人物,来阐述家族曾经出得辉煌。增加的人物和事件无限制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值