# C语言使用树设计族谱

3 篇文章 0 订阅
1 篇文章 0 订阅

C语言使用树设计族谱

完整代码地址: Github

本项目可使用vscode 打开并编译,详情请参考环境搭建教程:教程链接

问题描述

家属关系查询系统

  1. 问题描述:用树结构表示你所在家族的家谱关系,实现家族信息的管理与查询。
  2. 具体要求:此系统应实现如下功能。
    1. 建立(打开)家族关系树。
    2. 添加(删除)家属成员。
    3. 家属关系查询,例如查找某人的祖先、双亲、兄弟、孩子、堂兄弟、后代子孙、查询某入居于家族中的第几代。

族谱规则

族谱只会跟踪记录男性以及男性的后代,对于女性我们只会记录她在何时出嫁,并不记录她的后代,或者说族谱中的人员向上追溯的时候默认追溯的是父亲一支的关系;

逻辑分析

族谱与数据结构中树的概念相结合,每一个节点就是族谱中的个人,于是我们就需要知道每个人最基本的特性,于是就可以抽象化变成树中的属性;

  • 父母 (parent) --人
  • 兄弟 (brother) --人
  • 伴侣 (soulmate) --人
  • 孩子 (children) --人
  • 姓名 (name) --字符串
  • 生辰八字 (birthday) --日期
  • 性别 (gender) --性别

那么我们对应的树的结构就出来了

// def Tree node 定义节点
typedef struct _FamilyNode
{
    struct _FamilyNode* parent;
    struct _FamilyNode* brother;
    struct _FamilyNode* soulmate;
    struct _FamilyNode* children;
    char name[30];
    char birthday[20];
    char gender[6];
} FamilyNode;

// def Family Tree 定义族谱
typedef struct _FamilyTree
{
    FamilyNode* root;
    int deep;
} FamilyTree;

其他问题

  • 关于孩子的问题
    很多人的孩子不止一个,那么我们就会将二儿子/三儿子添加到对应的孩子的brother指针,并且将parent指针指向自己哥哥的父母;

  • 关于离婚的问题
    我们都是好孩子,不考虑离婚再婚.

  • 关于变性问题

    • 不考虑—不要问—问了就是不考虑

  • 简单示例
    这是小明家的族谱
    小明族谱.png

    小明->parent = NULL;
    小明->brother = NULL;
    小明->soulmate = NULL;
    小明->children = 武大郎;
    
    武大郎->parent = 小明;
    武大郎->brother = 武松;
    武大郎->soulmate = 潘金莲;
    武大郎->children = 小红;
    
    武松->parent = 小明;
    武松->brother = NULL;
    武松->soulmate = 玉兰;
    武松->children = 龙龙;
    
    ....
    
    

代码实现部分

头文件定义

// headfile familytree.h

#ifndef _familytree_h
#define _familytree_h
#include <stdio.h>

typedef struct
{
    char name[30];
    char birthday[20];
    char gender[6];
} Human;

typedef struct _FamilyNode
{
    struct _FamilyNode *parent;
    struct _FamilyNode *brother;
    struct _FamilyNode *soulmate;
    struct _FamilyNode *children;
    Human man;
    int deep;
} FamilyNode;

// def Family Tree 定义族谱
typedef struct _FamilyTree
{
    FamilyNode *root;
    char introduce[200]; ///< 对于该族谱的简单介绍
} FamilyTree;

FamilyTree *newFamilyTree(Human origin);
void *deleteFamilyTree(FamilyTree *tree);

int insertNewBaby(FamilyNode *parent, Human baby);
int marryPeople(FamilyNode *wifi, FamilyNode *husband);
int editFamilyNode(FamilyNode *old, FamilyNode *newone);
void removeFamilyNode(FamilyTree *tree, FamilyNode *node);

FamilyNode *findFamilyNodee(Human man);
FamilyNode *findOrigin(FamilyNode *node, int generation);
FamilyNode *findParent(FamilyNode *node);

void printBrother(FamilyNode *node);
void printCousin(FamilyNode *node);

extern int
compareFamilyNode(FamilyNode *a, FamilyNode *b);

#endif // _familytree_h

增删改查基础操作

1. new 一个家族的崛起

为了方便后续的管理,我重新自定义了一个FamilyTree,实际上来说任何一个FamilyNode都是一个familytree,但是不能人人都是先祖把所以咱们得要有一个根
于是乎:

FamilyTree *newFamilyTree(Human origin, char *introduce)
{
    FamilyTree *ret = (FamilyTree *)malloc(sizeof(FamilyTree));
    if (introduce != NULL)
        strcpy(ret->introduce, introduce);
    ret->root = (FamilyNode *)malloc(sizeof(FamilyNode));
    memset(ret->root, 0, sizeof(FamilyNode));
    ret->root->man = origin;
    ret->root->deep = 1;
    return ret;
}
2. delete 这个家族覆灭了

有生就有死,不可能我只New 没有Delete,这样不符合计算机的要求,也不符合自然的规律,当这个家族不复存在时(俗称满门抄斩),我们就会将其删除.

void deleteFamilyTree(FamilyTree *tree)
{
    if (tree != NULL)
    {
        if (tree->root != NULL)
            removeFamilyNode(tree->root);
        free(tree);
    }
} 
3. insert 我要当爸爸了

当新生命出现,我们的家族进一步壮大

int insertNewBaby(FamilyNode *parent, Human baby)
{
    if (parent == NULL)
        return 0;
    if (parent->soulmate == NULL)
    {
        K_ERROR("%s没有爱人\n", parent->man.name);
        return 0;
    }
    FamilyNode *chld = (FamilyNode *)malloc(sizeof (FamilyNode));
    memset(chld, 0, sizeof(FamilyNode));

    chld->man = baby;
    chld->parent = parent;
    if (parent->children == NULL)
    {
        parent->children = chld;
        chld->deep = parent->deep + 1;
    }
    else
    {
        FamilyNode *tmp = parent->children;
        while (tmp->brother != NULL)
            tmp = tmp->brother;
        tmp->brother = chld;
        chld->deep = tmp->deep;
    }
    K_INFOMATION("%s和%s的结合在%s诞生了一名叫%s的小%s孩 \n",
                 parent->man.name,
                 parent->soulmate->man.name,
                 chld->man.birthday,
                 chld->man.name,
                 (chld->man.gender == Gender_Man) ? ("男 ") : ("女"));
    return 1;
}
4. edit 我把我儿子改名叫"狗子"
   Human son;
   strcpy(son.name."二狗子");
   FamilyNode* myson = findFamilyNode(myFamilyTree,son);
   strcpy( myson->man.name,"狗子");
5. remove 我与某某人断绝父子关系

断绝关系之后,你的孙子就不是你的孙子,你的儿媳也不是你的儿媳,你就是你,既不妖艳,也不清高;

///< 递归删除当前节点下的所有子节点下的所有元素
void removeFamilyNode(FamilyNode *node)
{
    if (node == NULL)
        return;
    node->deep = 0;    //深度置0 ,表示当前节点节点需要删除
    removeFamilyNode(node->children);
    if (node->parent == NULL || node->parent->deep == 0) //如果 父母都要被删除,那么他的兄弟也要被删除
        removeFamilyNode(node->brother);
    else
    {
        FamilyNode *tmp = node->parent->children;
        if (tmp == node)
        {
            node->parent->children = NULL;
        }
        else
        {
            while (tmp->brother != node && tmp->brother  != NULL)
                tmp = tmp->brother;
            tmp->brother = node->brother;
        }
    }
    free(node);
}
6. find 皇上派我来查查你的底细

3分钟我要这个人的全部资料,这个女人朕要定了!
皇上~~(颤抖),武大郎是个男的~(哭了)

FamilyNode *findFamilyNode(FamilyTree *tree, Human man)
{
    if (tree == NULL || tree->root == NULL)
        return NULL;
    return findFamilyNodeByNode(tree->root, &man);
}

解决上述题目中的几个问题

  1. 找祖先
    你找第几代祖先~?
    再族谱里,你想找几代就找几代~
//使用方式: findOrigin(tigerKiller,1);  //往上找武松一代祖先
FamilyNode *findOrigin(FamilyNode *node, int generation)
{
    if (node == NULL)
        return NULL;
    FamilyNode *ret = node;
    while (ret->parent != NULL && generation != 0)
    {
        ret = ret->parent;
        generation--;
    }
    return ret;
}
  1. 找双亲
FamilyNode *findParent(FamilyNode *node)
{
    if (node == NULL)
        return NULL;
    return node->parent;
}
  1. 找兄弟
void printBrother(FamilyNode *node)
{
    if (node == NULL)
        return;
    FamilyNode *tmp = NULL;
    K_PRINT("%s的兄弟姐妹有:", node->man.name);
    if (node->parent != NULL)
        tmp = node->parent->children;
    else
        tmp = node->brother;
    while (tmp != NULL)
    {
        if (tmp != node)
            K_PRINT("  %s\n", tmp->man.name);
        tmp = tmp->brother;
    }
}
  1. 找孩子
// someone->children 后遍历 孩子的 brother 即可
  1. 找堂兄弟
void printCousin(FamilyNode *node)
{
    if (node == NULL || node->parent == NULL)
        return;
    FamilyNode *tmpuncle = NULL;
    FamilyNode *tmpbro = NULL;
    K_PRINT("%s的堂兄弟姐妹有:", node->man.name);
    if (node->parent->parent == NULL)
        tmpuncle = node->parent->brother;
    else
        tmpuncle = node->parent->parent->children;
    while (tmpuncle != NULL)
    {
        tmpbro = tmpuncle->children;
        while (tmpbro != NULL)
        {
            if (tmpbro != node)
                K_PRINT("  %s\n", tmpbro->man.name);
            tmpbro = tmpbro->brother;
        }
        tmpuncle = tmpuncle->brother;
    }
}
  1. 找后代子孙
void printFamily(FamilyNode *node)
{
    if (node == NULL)
    {
        return;
    }

    for (int i = 0; i < node->deep; i++)
    {
        char *c = " ";
        if (i == node->deep - 2)
            c = "└";
        else if (i == node->deep - 1)
            c = "--";
        else
            c = "  ";

        K_PRINT("%s", c);
    }
    K_PRINT("%s:%s生,性别 %s", node->man.name,
            node->man.birthday, GENDER_TO_STR   (node->man.gender));
    if (node->soulmate != NULL)
    {
        K_PRINT(",伴侣: %s, 性别: %s",
                node->soulmate->man.name,
                GENDER_TO_STR(node->soulmate->man.gender)   );
    }
    K_PRINT("%s", "\n");
    printFamily(node->children);
    printFamily(node->brother);
}
  1. 查询某人居于家族中的第几代
someone->deep;

运行结果

>[Congratulation] :小明和蛙蛙喜结连理,祝他们百年好合  
  >[Info] :小明和蛙蛙的结合在1969-11-11诞生了一名叫武大郎的小男孩  
  >[Info] :小明和蛙蛙的结合在1970-11-11诞生了一名叫武松的小男孩  
  >[Congratulation] :武大郎和潘金莲喜结连理,祝他们百年好合  
  >[Congratulation] :武松和玉兰喜结连理,祝他们百年好合  
  >[Info] :武大郎和潘金莲的结合在1990-11-11诞生了一名叫西红沁的小女孩  
  >[Info] :武大郎和潘金莲的结合在1993-11-11诞生了一名叫武云的小男孩  
  >[Info] :武松和玉兰的结合在1995-11-11诞生了一名叫武龙的小男孩  
  >[Info] :武松和玉兰的结合在1996-11-11诞生了一名叫武丽的小女孩  
  >[Congratulation] :武云和静香喜结连理,祝他们百年好合  
  >[Warning] :潘勇是上门女婿  
  >[Congratulation] :西红沁和潘勇喜结连理,祝他们百年好合  
  >[Congratulation] :武龙和李娜喜结连理,祝他们百年好合  
  >[Info] :武云和静香的结合在2020-01-03诞生了一名叫武毛的小女孩  
  >[Info] :武云和静香的结合在2020-01-03诞生了一名叫武重的小男孩  
--小明:1949-11-11生,性别 男,伴侣: 蛙蛙, 性别: 女  
└--武大郎:1969-11-11生,性别 男,伴侣: 潘金莲, 性别: 女  
  └--西红沁:1990-11-11生,性别 女,伴侣: 潘勇, 性别: 男  
  └--武云:1993-11-11生,性别 男,伴侣: 静香, 性别: 女  
    └--武毛:2020-01-03生,性别 女  
    └--武重:2020-01-03生,性别 男  
└--武松:1970-11-11生,性别 男,伴侣: 玉兰, 性别: 女  
  └--武龙:1995-11-11生,性别 男,伴侣: 李娜, 性别: 女  
  └--武丽:1996-11-11生,性别 女  
  >[Info] :发现武大郎的老婆潘金莲搞外遇,他的女儿不是他亲生的~  好惨啊~  
  >[Info] :将 西红沁 踢出族谱~!!!  
  >[Info] :最终的族谱为  
--小明:1949-11-11生,性别 男,伴侣: 蛙蛙, 性别: 女  
└--武大郎:1969-11-11生,性别 男,伴侣: 潘金莲, 性别: 女  
  └--武云:1993-11-11生,性别 男,伴侣: 静香, 性别: 女  
    └--武毛:2020-01-03生,性别 女  
    └--武重:2020-01-03生,性别 男  
└--武松:1970-11-11生,性别 男,伴侣: 玉兰, 性别: 女  
  └--武龙:1995-11-11生,性别 男,伴侣: 李娜, 性别: 女  
  └--武丽:1996-11-11生,性别 女  
  >[Info] :展示武大郎的兄弟  
武大郎的兄弟姐妹有:  武松  
  >[Info] :展示武云的堂兄弟  
武云的堂兄弟姐妹有:  武龙  
  武丽  

后记

本问题并不考虑算法问题,因此效率/空间/时间复杂度在这里不做考虑;

  • 11
    点赞
  • 90
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值