【C++】算法设计与分析作业:家谱树问题


前言

本人的算法设计与分析老师布置一道题目,关于数据结构与路径搜索算法,在此分享一下个人的解答。


问题描述

一个家谱树被描述在一个文本文件中,格式如下:
第一行指定家谱树中的总人数n。
接下来的n行描述树中的人,每行格式如下:
姓名 性别 年龄
其中:
姓名是字符串,没有空格,最多50个字符。
性别是一个字符,'M’代表男性,'F’代表女性。
年龄表示这个人的寿命。

这n行之后,文件接下来的行,其行数未知,指定了这些人之间的关系。每行三个字符串,
结构如下:
孩子姓名 父亲姓名 母亲姓名
注意,显然生理学上,每个人必然有一父有母,但一对夫妻的孩子数量没有限制。

写一个C++程序实现:
1.将文件内容存储在一个适当的数据结构中。
2.在这个数据结构中找到具有最大相同性别人数(男性或女性)的路径,即按照父母到孩子
  的关系,遇到具有相同性别的人数最多的路径。
3.在这个数据结构中找到寿命总和最大的路径,即按照父母到孩子的关系,使路径上的节点
(人)寿命之和最大。

文件输入示例:

10
A M 75
B F 74
C M 70
D F 70
E M 43
F F 50
G M 47
H F 45
I M 20
J F 24
I G F
J G F
E A B
F A B
G C D
H C D

家谱树解析:
一个简单的家谱树,包含姓名、性别、年龄
输出示例:

男性最多的路径:(共3 名男性)
C       M
G       M
I       M

女性最多的路径:(共3 名女性)
B       F
F       F
J       F

年龄总和:149 岁
A       75
F       50
J       24

解题思路

1.数据结构的选择

在构建数据结构之初,我考虑过很多种情况.如,在Person的结构体中 姓名,性别,年龄,父亲节点,母亲节点,用动态数组存放的孩子节点;或者2个结构体,一个是个人信息的结构体,另一个是家庭成员关系的结构体.
他们确实可以有效存放家谱树中的对应关系,但是在后续搜索路径的操作时,仅仅只有家庭成员的关系来搜索并遍历路径显然不够用,因此结构体中的内容大致如下:

 enum Gender { M, F }; // 定义枚举类型Gender,包含M和F两个性别
 struct Person { // 定义结构体Person
     std::string name; // 姓名
     Gender sex; // 性别
     int age; // 年龄
     Person* next; // 下一个节点
     Person* mother; // 母亲节点
     Person* father; // 父亲节点
     Person* children; // 子节点
     Person* nextBySex; // 按性别排序的下一个节点
     Person* nextByAge; // 按年龄排序的下一个节点
 };

2.读取文件并将数据存储在数据结构中

实现一个简单的家庭关系数据库,其中包含对家庭成员的记录,包括他们的名字、性别、年龄以及他们之间的父母-子女关系。
以下是各函数的解释:

  • searchPerson(std::vector<Person>&, const std::string &): 接受一个Person对象的向量和一个字符串(预期是人的名字),然后在向量中搜索具有这个名字的Person对象。如果找到,它返回那个对象的地址;如果没有找到,它返回nullptr。
  • setParents(Person*, Person*, Person*): 接受一个孩子Person对象,一个母亲Person对象和一个父亲Person对象,然后设置孩子对象的母亲和父亲属性,并返回孩子对象的地址。
    setChild(Person*, Person*): 接受一个父亲/母亲Person对象和一个孩子Person对象,然后把这个孩子添加到父亲/母亲的子节点链表中,并返回父亲/母亲的地址。
  • readFile(const std::string&, int&): 从给定的文件中读取数据。文件首先包含一个整数n_people,表示人的数量。接下来,它读取每个人的姓名、性别和年龄。然后,它循环读取每个人的父母名字,并使用searchPerson函数找到相应的父亲和母亲Person对象。然后使用setParents和setChild函数设置父母和孩子之间的关系。最后,它返回包含所有Person对象的向量。
// 搜索指定姓名的人
Person* searchPerson(std::vector<Person>&rp, const std::string & name) { 

    for (auto& Person : rp) {
       
        if (Person.name == name) {
            return &Person;
        }
    }
    return nullptr;
}
// 分配父母节点
Person* setParents(Person* child, Person* mom, Person* dad) { 
    child->mother = mom;
    child->father = dad;
    return child;
}
// 分配子节点
Person* setChild(Person* parent, Person* child) { 
    child->next = parent->children;
    parent->children = child;
    return parent;
}
// 从文件中读取数据
std::vector<Person> readFile(const std::string& filename, int& n_people) { 
    std::ifstream file(filename); // 打开文件流
    if (!file) { // 如果文件打开失败
        std::cerr << "Error in opening file! Filename:" + filename << std::endl; // 输出错误信息
        exit(1); // 退出程序
    }
    file >> n_people; // 读取人数
    std::vector<Person> pt(n_people); // 创建节点向量
    for (int i = 0; i < n_people; i++) { // 循环读取每个人的信息
        
        file >> pt[i].name ; // 读取姓名

        char sex;
        file >> sex >> pt[i].age; // 读取性别和年龄
        if (sex == 'M') { // 如果性别为男性
            pt[i].sex = M; // 设置性别为M
        }
        else {
            pt[i].sex = F; // 否则设置性别为F
        }
    }
    
    std::string childName, dadName, momName;
    while (file >> childName >> dadName >> momName) { // 循环读取每个人的父母信息
        Person* curr_person = searchPerson(pt, childName); // 搜索当前人的节点
        Person* curr_mom = searchPerson(pt, momName); // 搜索当前人的母亲节点
        Person* curr_dad = searchPerson(pt, dadName); // 搜索当前人的父亲节点

        curr_person = setParents(curr_person, curr_mom, curr_dad); // 分配父母节点
        curr_mom = setChild(curr_mom, curr_person); // 分配子节点
        curr_dad = setChild(curr_dad, curr_person); // 分配子节点
    }
    return pt; // 返回节点向量
}

3.找到具有最大相同性别人数的路径

查找给定性别的人在人员关系树中最大的路径长度。这里的"路径"是指从给定的当前节点开始,沿着同一性别的人员关系线,到达的子节点的集合。而"最大路径长度"则是指这条路径上包含的节点数。
下面详细解释代码的执行过程:

  1. 函数 findPathBySex 接受三个参数:Person* curr(当前节点),int cnt(路径长度),Gender sex(性别)。
  2. pt 指向当前节点 currtmp 指向当前节点的子节点。
  3. 检查当前节点是否为空或性别不匹配。如果是,则返回 cnt路径长度。
  4. 如果当前节点的子节点为空,则返回 cnt + 1,表示到达了一个新的叶子节点,路径长度加1。
  5. 如果当前节点的子节点不为空,那么递增 cnt,表示路径长度加1。
  6. 循环遍历子节点。对于每个子节点,递归调用 findPathBySex 函数查找路径。
  7. 如果找到了一条更长的路径(即返回的路径长度 i 大于 max),则更新 max 为 i,并更新按性别排序的下一个节点 curr->nextBySex 为当前子节点。
  8. 在遍历完一个子节点后,将 pt->children 指向下一个子节点。
  9. 在遍历完所有子节点后,恢复子节点指针 curr->children 为原始值 tmp
  10. 最后返回最大路径长度 max
    主要作用是在一个复杂的人员关系树中,寻找特定性别的人的最长路径。在人员关系树中,每个节点代表一个人,而节点之间的连接则代表人与人之间的关系。通过查找特定性别的人的最长路径,可以了解在人员关系树中,特定性别的人能够到达的最远位置有多少个节点。
// 按性别查找最大路径长度
int findPathBySex(Person* curr, int cnt, Gender sex) { 
    int max = cnt; // 最大路径长度
    Person* pt = curr; // 当前节点
    Person* tmp = curr->children; // 子节点
    if (pt == nullptr || pt->sex != sex) { // 如果当前节点为空或性别不匹配
        return cnt; // 返回路径长度
    }
    if (pt->children == nullptr) {
        return cnt + 1;
    }
    ++cnt; // 路径长度加1
    while (pt->children != nullptr) { // 循环遍历子节点
        int i = findPathBySex(pt->children, cnt, sex); // 递归查找路径
        if (i > max) { // 如果找到更长的路径
            max = i; // 更新最大路径长度
            curr->nextBySex = pt->children; // 更新按性别排序的下一个节点
        }
        pt->children = pt->children->next; // 遍历下一个子节点
    }
    curr->children = tmp; // 恢复子节点
    return max; // 返回最大路径长度
}

4.找到寿命总和最大的路径

算法与查找最大相同性别人数的路径相似,查找给定年龄的人在人员关系树中最大的路径长度。这里的"路径"是指从给定的当前节点开始,沿着年龄关系线,到达的子节点的集合。而"最大路径长度"则是指这条路径上包含的节点数。

// 按年龄查找最大路径长度
int findPathByAge(Person* curr, int age) {
    int max = age; // 最大路径长度
    Person* pt = curr; // 当前节点
    Person* tmp = curr->children; // 子节点
    if (pt == nullptr) { // 如果当前节点为空
        return age; // 返回路径长度
    }
    if (pt->children == nullptr) { // 如果当前节点没有子节点
        return age + curr->age; // 返回路径长度加上当前节点的年龄
    }
    while (pt->children != nullptr) { // 循环遍历子节点
        int i = findPathByAge(pt->children, age + pt->age); // 递归查找路径
        if (i > max) { // 如果找到更长的路径
            max = i; // 更新最大路径长度
            curr->nextByAge = pt->children; // 更新按年龄排序的下一个节点
        }
        pt->children = pt->children->next; // 遍历下一个子节点
    }
    pt->children = tmp; // 恢复子节点
    return max; // 返回最大路径长度
}

5.打印节点信息

用于处理人员关系树的,这两个函数都是遍历树结构并找出特定性别或年龄的最长路径,然后打印出这条路径上的所有人员信息。
简单描述一下这两个函数是如何工作的:

  • printBySex函数:遍历整个家谱树,找到具有特定性别的最长路径,并打印出这条路径上的人员信息。findPathBySex函数用于查找给定性别的人的最长路径。
  • printByAge函数:遍历整个家谱树,找到年龄之和最大的路径,并打印出这条路径上的人员信息。findPathByAge函数用于查找给定年龄的人的最长路径。
// 按性别打印节点信息
void printBySex(std::vector<Person> pt, Gender sex,int n_people) {
    int max = 0; // 最大路径长度
    Person* pathbySex = nullptr; // 按性别排序的路径
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        pt[i].nextBySex = nullptr; // 初始化按性别排序的下一个节点为空指针
    }
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        if (pt[i].sex == sex) { 
            int j = findPathBySex(&pt[i], 0, sex); // 按性别查找路径
            if (j > max) { // 如果找到更长的路径
                max = j; // 更新最大路径长度
                pathbySex = &pt[i]; // 更新按性别排序的路径
            }
        }
    }
    if (sex == M) {
        std::cout << "男性最多的路径:(共" << max << " 名男性)" << std::endl; // 打印按男性排序的路径长度和性别
    }
    else {
        std::cout << "女性最多的路径:(共" << max << " 名女性)" << std::endl; // 打印按女性排序的路径长度和性别
    }
    while (pathbySex != nullptr) { // 循环遍历节点
        std::cout << pathbySex->name << "\t" << (sex == M ? 'M' : 'F') << std::endl; // 打印节点信息

        pathbySex = pathbySex->nextBySex; // 遍历下一个节点
    }
    std::cout << std::endl; 
}
// 按年龄打印节点信息
void printByAge(std::vector<Person> pt,int n_people) {
    //查找年龄最大的路径
    int max = 0;
    Person* pathbyAge = nullptr; // 按年龄排序的路径
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        int j = findPathByAge(&pt[i], 0); // 按年龄查找路径
        if (j > max) { // 如果找到更长的路径
            max = j; // 更新最大路径长度
            pathbyAge = &pt[i]; // 更新按年龄排序的路径
        }
    }
    std::cout << "年龄总和:" << max << " 岁" << std::endl; // 打印按年龄排序的路径长度和年龄
    while (pathbyAge != nullptr) { // 循环遍历节点
        std::cout << pathbyAge->name << "\t" << pathbyAge->age << std::endl; // 打印节点信息

        pathbyAge = pathbyAge->nextByAge; // 遍历下一个节点
    }
    std::cout << std::endl; 
}

完整代码

FamilyTree.h

#ifndef FAMILYTREEOPT_H
#define FAMILYTREEOPT_H
#include <string>
#include <vector>
#include <iostream>
#include <fstream> 

 enum Gender { M, F }; // 定义枚举类型Gender,包含M和F两个值
 struct Person { // 定义结构体Person
     std::string name; // 姓名
     Gender sex; // 性别
     int age; // 年龄
     Person* next; // 下一个节点
     Person* mother; // 母亲节点
     Person* father; // 父亲节点
     Person* children; // 子节点
     Person* nextBySex; // 按性别排序的下一个节点
     Person* nextByAge; // 按年龄排序的下一个节点
 };

Person* searchPerson(std::vector<Person>& rp, const std::string& name);
Person* setParents(Person* child, Person* mom, Person* dad);
Person* setChild(Person* parent, Person* child);
std::vector<Person> readFile(const std::string& filename, int& n_people);
int findPathBySex(Person* curr, int cnt, Gender sex);
int findPathByAge(Person* curr, int age);
void printBySex(std::vector<Person> pt, Gender sex, int n_people);
void printByAge(std::vector<Person> pt, int n_people);
void displayFamilyTreeAnswer(std::string filename);

#endif

FamilyTree.cpp

#include "FamilyTreeOpt.h"

// 搜索指定姓名的人
Person* searchPerson(std::vector<Person>&rp, const std::string & name) { 

    for (auto& Person : rp) {
       
        if (Person.name == name) {
            return &Person;
        }
    }
    return nullptr;
}
// 分配父母节点
Person* setParents(Person* child, Person* mom, Person* dad) { 
    child->mother = mom;
    child->father = dad;
    return child;
}
// 分配子节点
Person* setChild(Person* parent, Person* child) { 
    child->next = parent->children;
    parent->children = child;
    return parent;
}
// 从文件中读取数据
std::vector<Person> readFile(const std::string& filename, int& n_people) { 
    std::ifstream file(filename); // 打开文件流
    if (!file) { // 如果文件打开失败
        std::cerr << "Error in opening file! Filename:" + filename << std::endl; // 输出错误信息
        exit(1); // 退出程序
    }
    file >> n_people; // 读取人数
    std::vector<Person> pt(n_people); // 创建节点向量
    for (int i = 0; i < n_people; i++) { // 循环读取每个人的信息
        
        file >> pt[i].name ; // 读取姓名

        char sex;
        file >> sex >> pt[i].age; // 读取性别和年龄
        if (sex == 'M') { // 如果性别为男性
            pt[i].sex = M; // 设置性别为M
        }
        else {
            pt[i].sex = F; // 否则设置性别为F
        }
    }
    
    std::string childName, dadName, momName;
    while (file >> childName >> dadName >> momName) { // 循环读取每个人的父母信息
        Person* curr_person = searchPerson(pt, childName); // 搜索当前人的节点
        Person* curr_mom = searchPerson(pt, momName); // 搜索当前人的母亲节点
        Person* curr_dad = searchPerson(pt, dadName); // 搜索当前人的父亲节点

        curr_person = setParents(curr_person, curr_mom, curr_dad); // 分配父母节点
        curr_mom = setChild(curr_mom, curr_person); // 分配子节点
        curr_dad = setChild(curr_dad, curr_person); // 分配子节点
    }
    return pt; // 返回节点向量
}
// 按性别查找最大路径长度
int findPathBySex(Person* curr, int cnt, Gender sex) { 
    int max = cnt; // 最大路径长度
    Person* pt = curr; // 当前节点
    Person* tmp = curr->children; // 子节点
    if (pt == nullptr || pt->sex != sex) { // 如果当前节点为空或性别不匹配
        return cnt; // 返回路径长度
    }
    if (pt->children == nullptr) {
        return cnt + 1;
    }
    ++cnt; // 路径长度加1
    while (pt->children != nullptr) { // 循环遍历子节点
        int i = findPathBySex(pt->children, cnt, sex); // 递归查找路径
        if (i > max) { // 如果找到更长的路径
            max = i; // 更新最大路径长度
            curr->nextBySex = pt->children; // 更新按性别排序的下一个节点
        }
        pt->children = pt->children->next; // 遍历下一个子节点
    }
    curr->children = tmp; // 恢复子节点
    return max; // 返回最大路径长度
}
// 按年龄查找最大路径长度
int findPathByAge(Person* curr, int age) {
    int max = age; // 最大路径长度
    Person* pt = curr; // 当前节点
    Person* tmp = curr->children; // 子节点
    if (pt == nullptr) { // 如果当前节点为空
        return age; // 返回路径长度
    }
    if (pt->children == nullptr) { // 如果当前节点没有子节点
        return age + curr->age; // 返回路径长度加上当前节点的年龄
    }
    while (pt->children != nullptr) { // 循环遍历子节点
        int i = findPathByAge(pt->children, age + pt->age); // 递归查找路径
        if (i > max) { // 如果找到更长的路径
            max = i; // 更新最大路径长度
            curr->nextByAge = pt->children; // 更新按年龄排序的下一个节点
        }
        pt->children = pt->children->next; // 遍历下一个子节点
    }
    pt->children = tmp; // 恢复子节点
    return max; // 返回最大路径长度
}
// 按性别打印节点信息
void printBySex(std::vector<Person> pt, Gender sex,int n_people) {
    int max = 0; // 最大路径长度
    Person* pathbySex = nullptr; // 按性别排序的路径
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        pt[i].nextBySex = nullptr; // 初始化按性别排序的下一个节点为空指针
    }
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        if (pt[i].sex == sex) { 
            int j = findPathBySex(&pt[i], 0, sex); // 按性别查找路径
            if (j > max) { // 如果找到更长的路径
                max = j; // 更新最大路径长度
                pathbySex = &pt[i]; // 更新按性别排序的路径
            }
        }
    }
    if (sex == M) {
        std::cout << "男性最多的路径:(共" << max << " 名男性)" << std::endl; // 打印按男性排序的路径长度和性别
    }
    else {
        std::cout << "女性最多的路径:(共" << max << " 名女性)" << std::endl; // 打印按女性排序的路径长度和性别
    }
    while (pathbySex != nullptr) { // 循环遍历节点
        std::cout << pathbySex->name << "\t" << (sex == M ? 'M' : 'F') << std::endl; // 打印节点信息

        pathbySex = pathbySex->nextBySex; // 遍历下一个节点
    }
    std::cout << std::endl; 
}
// 按年龄打印节点信息
void printByAge(std::vector<Person> pt,int n_people) {
    //查找年龄最大的路径
    int max = 0;
    Person* pathbyAge = nullptr; // 按年龄排序的路径
    for (int i = 0; i < n_people; i++) { // 循环遍历每个人的节点
        int j = findPathByAge(&pt[i], 0); // 按年龄查找路径
        if (j > max) { // 如果找到更长的路径
            max = j; // 更新最大路径长度
            pathbyAge = &pt[i]; // 更新按年龄排序的路径
        }
    }
    std::cout << "年龄总和:" << max << " 岁" << std::endl; // 打印按年龄排序的路径长度和年龄
    while (pathbyAge != nullptr) { // 循环遍历节点
        std::cout << pathbyAge->name << "\t" << pathbyAge->age << std::endl; // 打印节点信息

        pathbyAge = pathbyAge->nextByAge; // 遍历下一个节点
    }
    std::cout << std::endl; 
}
//显示答案函数接口
void displayFamilyTreeAnswer(std::string filename) {
    int n_people = 0; // 人数
    std::vector<Person> pt = readFile(filename, n_people); // 从文件中读取数据

    printBySex(pt, M, n_people); // 按男性打印路径

    printBySex(pt, F, n_people); // 按女性打印路径

    printByAge(pt, n_people); // 按年龄打印路径
}

main.cpp

#include "FamilyTreeOpt.h"
#include <iostream>


int main() { 
    /*程序使用方法:1.displayFamilyTreeAnswer()函数,将文件地址传入即可运行整个答案*/
    displayFamilyTreeAnswer(".\\res\\family_tree.txt");
    return 0;
}

后记

本人资历尚浅,有问题还请大家指出。谢谢观看,欢迎留言交流。

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值