# C語言使用樹設計族譜

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] :展示武雲的堂兄弟  
武雲的堂兄弟姐妹有:  武龍  
  武麗  

後記

本問題並不考慮算法問題,因此效率/空間/時間複雜度在這裏不做考慮;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章