数据结构 实验报告(二)

一、实验目的:

1、领会二叉链存储结构和掌握二叉树中的各种基本运算算法设计;
2、领会线索二叉树的构造过程以及构造二叉树的算法设计;
3、领会哈夫曼树的构造过程以及哈夫曼编码的生成过程;
4、掌握二叉树遍历算法的应用,熟练使用先序、中序、后序3种递归遍历算法进行二叉树问题的求解;

二、使用仪器、器材

微机一台
操作系统:WinXP
编程软件:C/C++编程软件

三、实验内容及原理

1、教材P247

实验题1:实现二叉树的各种基本运算的算法
编写一个程序btree.cpp,实现二叉树的基本运算,并在此基础上设计一个程序exp7-1.cpp完成以下功能。
(1)由图7.33所示的二叉树创建对应的二叉链存储结构b,该二叉树的括号表示串为“A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,i)))”。
(2)输出二叉树b。
(3)输出‘H’结点的左、右孩子结点值。
(4)输出二叉树b的高度。
(5)释放二叉树b。

#include <iostream>
#include <string>
#include <stack>
#include <queue>
using namespace std;

typedef struct BiTNode
{
    char data;
    BiTNode *lchild;
    BiTNode *rchild;
    // 构造函数
    BiTNode()
    {
        this->lchild = nullptr;
        this->rchild = nullptr;
    }
    BiTNode(char e)
    {
        this->data = e;
        this->lchild = nullptr;
        this->rchild = nullptr;
    }
    BiTNode(char e, BiTNode *l, BiTNode *r)
    {
        this->data = e;
        this->lchild = l;
        this->rchild = r;
    }
} *BiTree;

BiTree CreateTree(string p)
{
    // 栈s 保存的是【会被作为父节点】的点, 例如A(B,C(D,)),那么A、C会被入栈
    stack<BiTNode *> s;
    // 根节点为第一个点,直接入栈即可
    BiTree root = new BiTNode(p[0]);
    s.push(root);
    // tmp指针用于下面for循环使用
    BiTNode *tmp;
    // flag用于标记接下来处理的元素是左孩子(true)还是右孩子(false)
    bool flag = true;

    // 建树
    for (int i = 2; i < p.size() - 1; i++)
    {
        switch (p[i])
        {
        case '(':
            s.push(tmp); // 遇到左括号,说明刚刚左括号前的英文字母【会被作为父节点】,入栈
            flag = true; // 待会要处理的是左孩子
            break;
        case ',':
            flag = false; // 待会要处理的是右孩子
            break;
        case ')':
            s.pop(); // 当遇到右括号时,说明括号外的父节点已经被处理完毕,出栈即可
            break;
        default:                           // 如果以上都不成立,说明是英文字符
            tmp = new BiTNode(char(p[i])); // 新建结点保存到tmp
            if (flag)                      // 栈顶元素是tmp的父结点,根据tmp的值来确定其是左孩子还是右孩子
                s.top()->lchild = tmp;
            else
                s.top()->rchild = tmp;
            break;
        }
    }
    while (!s.empty())
        s.pop(); // 特判:如果只有一个根节点,那么栈s里面还会剩一个根节点,出栈即可
    return root; // 返回根节点
}

// 前序遍历
void preorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    cout << Node->data << " ";
    preorderTraversal(Node->lchild);
    preorderTraversal(Node->rchild);
}

// 中序遍历
void inorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    inorderTraversal(Node->lchild);
    cout << Node->data << " ";
    inorderTraversal(Node->rchild);
}

// 后序遍历
void postorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    postorderTraversal(Node->lchild);
    postorderTraversal(Node->rchild);
    cout << Node->data << " ";
}

// 层次遍历(层序遍历)【BFS】
void levelorderTraversal(BiTNode *Node)
{
    queue<BiTNode *> q;
    q.push(Node);
    while (!q.empty())
    {
        BiTNode *t = q.front();
        q.pop();
        cout << t->data << " ";
        if (t->lchild)
            q.push(t->lchild);
        if (t->rchild)
            q.push(t->rchild);
    }
}

// 找到指定节点【DFS】
BiTNode *FindNode(BiTNode *Node, char c)
{
    // 递归终止条件:已经到空节点 或 找到了该节点
    if (Node == nullptr)
        return nullptr;

    if (Node->data == c)
        return Node;
    
    // 先找左子树
    BiTNode *f = FindNode(Node->lchild, c);
    // 如果左子树就找到了(f不为nullptr),那么返回f即可
    if (f)
        return f;
    // 否则就找右子树
    f = FindNode(Node->rchild, c);
    // 找到了,返回f,没找到,返回nullptr,不需要再判断了
    return f;
}

// 求二叉树的高度
int TreeHeight(BiTNode *T)
{
    if (!T)
        return 0;
    else
    {
        int leftHeight = TreeHeight(T->lchild);
        int rightHeight = TreeHeight(T->rchild);
        return max(leftHeight, rightHeight) + 1;
    }
}

void DestoryTree(BiTNode *p)
{
    if (p == nullptr)
        return; // 已经为nullptr,无需销毁
    DestoryTree(p->lchild);
    DestoryTree(p->rchild);
    p->lchild = nullptr;
    p->rchild = nullptr;
    delete p;
}

int main()
{
    BiTree tree = CreateTree("A(B(D,E(H(J,K(L,M(,N))))),C(F,G(,i)))");

    cout << "(1) 输出二叉树:\n ";
    cout << "先序遍历: ";
    preorderTraversal(tree);
    cout << "\n中序遍历: ";
    inorderTraversal(tree);
    cout << "\n后序遍历: ";
    postorderTraversal(tree);
    cout << "\n层次遍历: ";
    levelorderTraversal(tree);
    cout << "\n";

    BiTNode *p = FindNode(tree, 'H');
    if(p)
        cout << "(2)'H'结点左孩子: " << p->lchild->data << "\n   'H'结点右孩子: " << p->rchild->data << "\n";
    else
        cout << "(2)没找到该节点!\n";
    

    cout << "(3)二叉树的高度: " << TreeHeight(tree) << "\n";

    cout << "(4)释放二叉树\n";
    DestoryTree(tree);

    //system("pause");
    return 0;
}

2、教材P248

实验题3:由遍历序列构造二叉树
编写一个程序exp7-3.cpp,实现由先序序列和中序序列以及由中序序列和后序序列构造一棵二叉树的功能(二叉树种的每个结点值为单个字符),要求以括号表示和凹入表示法输出该二叉树,并用先序遍历序列“ABDEHJKLMNCFGI”和中序遍历序列“DBJHLKMNEAFCGI”以及由中序遍历序列“DBJHLKMNEAFCGI”和后序遍历序列“DJLNMKHEBFIGCA”进行验证。

#include <iostream>
#include <string>
using namespace std;

typedef struct BiTNode
{
    char data;
    BiTNode *lchild;
    BiTNode *rchild;
    // 构造函数
    BiTNode()
    {
        this->lchild = nullptr;
        this->rchild = nullptr;
    }
    BiTNode(char e)
    {
        this->data = e;
        this->lchild = nullptr;
        this->rchild = nullptr;
    }
    BiTNode(char e, BiTNode *l, BiTNode *r)
    {
        this->data = e;
        this->lchild = l;
        this->rchild = r;
    }
} *BiTree;

// 前序遍历
void preorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    cout << Node->data << " ";
    preorderTraversal(Node->lchild);
    preorderTraversal(Node->rchild);
}

// 中序遍历
void inorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    inorderTraversal(Node->lchild);
    cout << Node->data << " ";
    inorderTraversal(Node->rchild);
}

// 后序遍历
void postorderTraversal(BiTNode *Node)
{
    if (Node == nullptr) // 如果节点为空,直接返回即可
        return;
    postorderTraversal(Node->lchild);
    postorderTraversal(Node->rchild);
    cout << Node->data << " ";
}

// 由先序、中序遍历序列创建二叉树
BiTNode *CreateTree_1(string Pre, string In)
{
    if (Pre.size() == 0 || In.size() == 0)
        return nullptr;

    // 确定根节点
    BiTNode *root = new BiTNode(Pre[0]);

    int idx = In.find(Pre[0]); // 返回在In里的下标
    // 分割中序遍历序列
    string left_In = In.substr(0, idx);
    string right_In = In.substr(idx + 1);

    // 分割先序遍历序列
    string left_Pre = Pre.substr(1, left_In.size());
    string right_Pre = Pre.substr(left_In.size() + 1);

    root->lchild = CreateTree_1(left_Pre, left_In);
    root->rchild = CreateTree_1(right_Pre, right_In);

    return root;
}

// 由中序、后序遍历序列创建二叉树
BiTNode *CreateTree_2(string In, string Post)
{
    if (Post.size() == 0 || In.size() == 0)
        return nullptr;

    // 确定根节点
    BiTNode *root = new BiTNode(Post[Post.size() - 1]);

    int idx = In.find(Post[Post.size() - 1]); // 返回在Post里的下标
    // 分割中序遍历序列
    string left_In = In.substr(0, idx);
    string right_In = In.substr(idx + 1);

    // 分割后序遍历序列
    string left_Post = Post.substr(0, idx);
    string right_Post = Post.substr(idx, Post.size() - idx - 1);

    root->lchild = CreateTree_2(left_In, left_Post);
    root->rchild = CreateTree_2(right_In, right_Post);

    return root;
}

// 括号表示法输出二叉树
void DisPlayBiTNode_1(BiTNode *b)
{
    if (b)
    {
        cout << b->data;
        if (b->lchild || b->rchild)
        {
            cout << "(";
            DisPlayBiTNode_1(b->lchild);

            cout << ",";

            DisPlayBiTNode_1(b->rchild);
            cout << ")";
        }
    }
}

// 凹入表示法输出二叉树
void DisPlayBiTNode_2(BiTNode *b, int n, int k)
{
    if (b)
    {
        for (int j = 0; j < k; j++)
            cout << " ";
        
        cout << b->data;

        for (int j = 0; j < n; j++)
            cout << "-";
        
        cout << "\n";
        
        DisPlayBiTNode_2(b->lchild, n - 1, k + 1);
        DisPlayBiTNode_2(b->rchild, n - 1, k + 1);
    }
}

void DestoryTree(BiTNode *p)
{
    if (p == nullptr)
        return; // 已经为nullptr,无需销毁
    DestoryTree(p->lchild);
    DestoryTree(p->rchild);
    p->lchild = nullptr;
    p->rchild = nullptr;
    delete p;
}

int main()
{
    string PreOrderTraversal = "ABDEHJKLMNCFGI";  // 先序
    string InOrderTraversal = "DBJHLKMNEAFCGI";   // 中序
    string PostOrderTraversal = "DJLNMKHEBFIGCA"; // 后序

    cout << "(1)由先序序列和中序序列构造二叉树: \n";
    BiTree tree1 = CreateTree_1(PreOrderTraversal, InOrderTraversal);
    cout << "1.括号表示法: ";
    DisPlayBiTNode_1(tree1);
    cout << "\n2.凹入表示法:\n";
    DisPlayBiTNode_2(tree1, 10, 0);
    cout << "\n验证: \n";
    cout << "1.先序遍历: ";
    preorderTraversal(tree1);
    cout << "\n2.中序遍历: ";
    inorderTraversal(tree1);
    cout << "\n3.后序遍历: ";
    postorderTraversal(tree1);
    
    cout << "\n";

    cout << "\n\n(2)由中序序列和后序序列构造二叉树: \n";
    BiTree tree2 = CreateTree_2(InOrderTraversal, PostOrderTraversal);
    cout << "1.括号表示法: ";
    DisPlayBiTNode_1(tree2);
    cout << "\n2.凹入表示法:\n";
    DisPlayBiTNode_2(tree2, 10, 0);
    cout << "\n验证: \n";
    cout << "1.先序遍历: ";
    preorderTraversal(tree2);
    cout << "\n2.中序遍历: ";
    inorderTraversal(tree2);
    cout << "\n3.后序遍历: ";
    postorderTraversal(tree2);
    
    cout << "\n";

    DestoryTree(tree1);
    DestoryTree(tree2);
    return 0;
}

3、教材P248

实验题5:构造哈夫曼树生成哈夫曼编码
编写一个程序exp7-5.cpp,构造一棵哈夫曼树,输出对应的哈夫曼编码和平均查找长度,并对下表(表7.8)所示的数据进行验证。

单词 The of a to and in that he is at on for His are be
频度 1192 677 541 518 462 450 242 195 190 181 174 157 138 124 123
表7.8 单词及出现的频度

版本1:

#include <iostream>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;

typedef struct HTNode
{
    int weight;
    int parent = 0, lch = 0, rch = 0;
    bool operator<(const HTNode &a) const
    {
        return weight < a.weight;
    }
} *HuffmanTree;

void Select(HuffmanTree HT, int n, int &lnode, int &rnode)
{
    lnode = rnode = 0;
    int min1 = 0x3f3f3f3f, min2 = 0x3f3f3f3f;
    for (int i = 1; i <= n; i++)
    {
        if (HT[i].parent == 0)
        {
            if (HT[i].weight < min1)
            {
                min2 = min1;
                rnode = lnode;
                min1 = HT[i].weight;
                lnode = i;
            }
            else if (HT[i].weight < min2)
            {
                min2 = HT[i].weight;
                rnode = i;
            }
        }
    }
}

void CreatHuffmanTree(HuffmanTree &HT, int n)
{
    int lnode, rnode;
    
    for (int i = n + 1; i < n * 2; i++)
    {
        // 在HT[k](1<=k<=i-1)中选择两个双亲域为0且权值最小的结点,并返回它们在HT中的序号lnode和rnode
        Select(HT, i - 1, lnode, rnode);

        // 从森林中删除s1,s2
        HT[lnode].parent = i;
        HT[rnode].parent = i;

        // s1,s2分别作为i的左右孩子
        HT[i].lch = lnode;
        HT[i].rch = rnode;

        // i的权值为左右孩子权值之和
        HT[i].weight = HT[lnode].weight + HT[rnode].weight;
    }
}

char** CreatHuffmanCode(HuffmanTree &HT, int n)
{
    // 从叶子到根逆向求每个字符的哈夫曼编码,存储在编码表HC中
    char** HC = new char *[n + 1]; // 分配n个字符编码的头指针矢量
    char*  cd = new char[100];       // 分配临时存放编码的动态数组空间
    cd[n - 1] = '\0';        // 编码结束符

    for (int i = 1; i <= n; ++i)
    { // 逐个字符求哈夫曼编码
        int start = n - 1; // 控制字符起始位置,从后开始往前
        int child = i;
        int parent = HT[i].parent;

        // 从叶子结点开始向上回溯,直到根结点(根节点的parent为0)
        while (parent != 0)
        {
            --start; // 回溯一次start向前指一个位置

            if (HT[parent].lch == child)
                cd[start] = '0'; // 结点c是f的左孩子,则生成代码0
            else
                cd[start] = '1'; // 结点c是f的右孩子,则生成代码1
            
            // 继续向上回溯
            child = parent;
            parent = HT[parent].parent;
        }                            // 求出第 i 个字符的编码
        HC[i] = new char[n - start]; // 为第 i 个字符编码分配空间
        strcpy(HC[i], &cd[start]);  // 将求得的编码从临时空间cd复制到HC的当前行中
    }
    delete cd; // 释放临时空间
    return HC;
}

int getWPL(HuffmanTree &HT, int Node, int depth)
{
    if(HT[Node].lch == 0 && HT[Node].rch == 0)
        return HT[Node].weight * depth;
    
    return getWPL(HT, HT[Node].lch,depth+1) + getWPL(HT, HT[Node].rch,depth+1);
}

int main()
{
    int sum_weight = 0; // 存储权值之和
    const char *s[] = {"The", "of", "a", "to", "and", "in", "that", "he", "is", "at", "on", "for", "His", "are", "be"};
    int a[] = {1192, 677, 541, 518, 462, 450, 242, 195, 190, 181, 174, 157, 138, 124, 123};

    HuffmanTree HT = new HTNode[30];

    for (int i = 1; i <= 15; i++)
    {
        HT[i].weight = a[i - 1];
        sum_weight += a[i-1];
    }
        

    CreatHuffmanTree(HT, 15);
    char** HuffmanCode = CreatHuffmanCode(HT, 15);

    for(int i = 1; i <= 15; i++)
        printf("%s 对应的编码为: %s\n", s[i-1], HuffmanCode[i]);
    
    printf("\n平均查找长度为: %lf \n", 1.0 * getWPL(HT, 29, 0) / sum_weight);
    
    delete []HT; // 销毁二叉树
    return 0;
}

版本2:

#include <iostream>
#include <queue>
#include <map>
#include <string>
using namespace std;

struct Node
{
    // 注意不要直接在这里重载Node*的运算符,因为我们需要的是比较 Node*,而不是Node
    string word;
    int freq;
    Node *left = nullptr, *right = nullptr;
    Node()
    {
    }
    Node(string w, int f)
    {
        this->word = w;
        this->freq = f;
    }
    Node(string w, int f, Node *l, Node *r)
    {
        this->word = w;
        this->freq = f;
        this->left = l;
        this->right = r;
    }
};

// 创建一个比较函数对象
struct cmp
{
    bool operator()(Node *a, Node *b)
    {
        // 大根堆(默认情况)就是 <
        // 我们这里需要小根堆,所以是 >
        return a->freq > b->freq;
    }
};

// 创建哈夫曼树
Node *CreateHuffmanTree()
{
    map<string, int> words = {
        {"The", 1192}, {"of", 677}, {"a", 541}, {"to", 518}, {"and", 462}, {"in", 450}, {"that", 242}, {"he", 195}, {"is", 190}, {"at", 181}, {"on", 174}, {"for", 157}, {"His", 138}, {"are", 124}, {"be", 123}};
    
    // 小根堆(自动从小到大排序)
    priority_queue<Node *, vector<Node *>, cmp> q;

    // 【方式1】用auto遍历,把各单词加入到小根堆中
    for (auto iter = words.begin(); iter != words.end(); iter++)
        q.push(new Node(iter->first, iter->second));

    Node *top, *l, *r;

    while (q.size() > 1)
    {
        l = q.top();
        q.pop();

        r = q.top();
        q.pop();

        top = new Node("", l->freq + r->freq, l, r);
        q.push(top);
    }

    // 最后只剩一个,即根节点
    top = q.top();
    q.pop();

    return top; // 返回根节点
}

// 创建哈夫曼编码,同时为了方便,返回各单词频率之和
int CreatHuffmanCode(Node *T, string s, map<string, string> &m)
{
    if (!T)
        return 0;
    if (!T->left && !T->right)
    {
        m[T->word] = s;
        return T->freq;
    }

    return CreatHuffmanCode(T->left, s + "0", m) + CreatHuffmanCode(T->right, s + "1", m);
}

// 计算查找的加权权重
int getWPL(Node *T, int depth)
{
    if (!T)
        return 0;
    if (!T->left && !T->right)
        return T->freq * depth;

    return getWPL(T->left, depth + 1) + getWPL(T->right, depth + 1);
}

int main()
{
    Node *HT = CreateHuffmanTree();

    map<string, string> HTCode;

    // 创建创建哈夫曼编码,同时保存各单词原始权重之和
    int sum_freq = CreatHuffmanCode(HT, "", HTCode);

    cout << "Word\tHuffman Code\n";

    // 【方式2】用auto遍历
    for (auto v : HTCode)
        cout << v.first << "\t" << v.second << "\n";

    // 1.0* 是为了把int强制转换成double
    cout << "Average Length: " << 1.0 * getWPL(HT, 0) / sum_freq << "\n";
    return 0;
}

4、教材P248

实验题8:简单算术表达式二叉树的构建和求值
编写一个程序exp7-8.cpp,先用二叉树来表示一个简单算术表达式,树的每一个结点包括一个运算符或运算数。在简单算术表达式中只包含+、-、、/和一位正整数且格式正确(不包括括号),并且要按照先乘除后加减的原则构造二叉树,图7.34所示为“1+23-4/5”代数表达式对应的二叉树,然后由对应的二叉树计算该表达式的值。

#include <iostream>
#include <stack>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;

struct Node
{
    string val;
    Node *lchild;
    Node *rchild;
    // 构造函数
    Node(string x) : val(x), lchild(nullptr), rchild(nullptr) {}
};

// 运算符优先级
int precedence(string c)
{
    if (c == "*" || c == "/")
        return 2;
    else if (c == "+" || c == "-")
        return 1;
    else // 遇到 左右括号 '( )' 或者 结束符'@'时,返回-1
        return -1;
}

// 将中缀表达式转换成后缀表达式
vector<string> infixToPostfix(string s)
{
    stack<string> st;
    st.push("N");
    int l = s.size();
    vector<string> ns;

    for (int i = 0; i < l; i++)
    {
        // 如果字符是左括号,将其压入栈中
        if (s[i] == '(')
        {
            st.push(string(1, s[i]));
        }
        // 如果字符是右括号,将栈中的运算符弹出并添加到结果向量中,直到遇到左括号
        else if (s[i] == ')')
        {
            while (st.top() != "N" && st.top() != "(")
            {
                string c = st.top();
                st.pop();
                ns.push_back(c);
            }

            // 弹出左括号
            st.pop();
        }
        // 如果字符是数字,需要读入完整的数字
        else if (isdigit(s[i]))
        {
            string num;
            while (i < l && isdigit(s[i]))
            {
                num += s[i];
                i++;
            }
            i--;
            ns.push_back(num);
        }
        // 如果以上都不是,说明遇到了【运算符 + - * /】
        else
        {
            while (st.top() != "N" && st.top() != "(" && precedence(string(1, s[i])) <= precedence(st.top()))
            {
                string c = st.top();
                st.pop();
                ns.push_back(c);
            }
            st.push(string(1, s[i]));
        }
    }

    while (st.top() != "N")
    {
        string c = st.top();
        st.pop();
        ns.push_back(c);
    }

    return ns;
}

// 构建表达式二叉树
Node *constructTree(vector<string> postfix)
{
    stack<Node *> st;
    Node *t, *t1, *t2;

    for (int i = 0; i < postfix.size(); i++)
    {
        if (isdigit(postfix[i][0]))
        {
            t = new Node(postfix[i]);
            st.push(t);
        }
        else
        {
            t = new Node(postfix[i]);

            t1 = st.top();
            st.pop();
            t2 = st.top();
            st.pop();

            t->rchild = t1;
            t->lchild = t2;

            st.push(t);
        }
    }

    t = st.top();
    st.pop();

    return t; // 返回根节点
}

double calculate(Node *root)
{
    // 如果节点是叶子节点(即操作数)
    if (root->lchild == nullptr && root->rchild == nullptr)
        return stod(root->val); // 直接返回其值(注意需要将string类型转换成double类型,方便运算)
    // 如果节点是其他节点(即运算符)
    else
    {
        double left = calculate(root->lchild);   // 递归计算左子树的值
        double right = calculate(root->rchild); // 递归计算右子树的值
        if (root->val == "+")
            return left + right;
        else if (root->val == "-")
            return left - right;
        else if (root->val == "*")
            return left * right;
        else
            return left / right;
    }
}

void DestoryTree(Node *p)
{
    if (p == nullptr)
        return; // 已经为nullptr,无需销毁
    DestoryTree(p->lchild);
    DestoryTree(p->rchild);
    p->lchild = nullptr;
    p->rchild = nullptr;
    delete p;
}

int main()
{
    string infix = "1+2*3-4/5";
    // 先把中缀表达式转换成后缀表达式
    vector<string> postfix = infixToPostfix(infix);
    // 构建表达式二叉树
    Node *r = constructTree(postfix);
    if(r)
    {
        cout << "二叉树构建成功!\n";
        cout << "计算结果为: " << calculate(r) << "\n";
        DestoryTree(r); // 销毁二叉树
    }
    // system("pause");
    return 0;
}

5、教材P249

(备注:这题我记得做得比较匆忙,所以可能写得一般)

实验题9:用二叉树表示家谱关系并实现各种查找功能
编写一个程序exp7-9.cpp,采用一棵二叉树表示一个家谱关系(由若干家谱记录构成,每个家谱记录由父亲、母亲和儿子的姓名构成,其中姓名是关键字),要求程序具有以下功能。
(1) 文件操作功能:家谱记录的输入,家谱记录的输出,清除全部文件记录和将家谱记录存盘。要求在输入家谱记录时按从祖先到子孙的顺序输入,第一个家谱记录的父亲域为所有人的祖先。
(2) 家谱操作功能:用括号表示法输出家谱二叉树,查找某人的所有儿子,查找某人的所有祖先(这里的祖先是指所设计的二叉树结构中某结点的所有祖先结点)。

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

struct Node
{
    string name;
    Node *left;  // 母亲结点
    Node *right; // 子结点
    Node() : name(""), left(nullptr), right(nullptr) {}
    Node(string x) : name(x), left(nullptr), right(nullptr) {}
};

struct FamilyTree
{
    Node *root;
    FamilyTree() : root(nullptr) {}

    // 添加家庭记录
    Node *AddRecord(const string &father, const string &mother, const string &child)
    {
        Node *node = findNode(root, father);
        if (node == nullptr) // 如果没找到这个人,说明是第一次输入
            root = node = new Node(father);
        if (mother != "NULL")
        {
            node->left = new Node(mother);
            if (child != "NULL")
                node->left->right = new Node(child);
        }
        else if (mother == "NULL" && child != "NULL")
            node->right = new Node(child);
        return node;
    }

    // 在家谱树中查找名字为name的节点
    Node *findNode(Node *node, const string &name)
    {
        if (node == nullptr)
            return nullptr;
        if (node->name == name)
            return node;
        Node *foundNode = findNode(node->left, name);
        if(foundNode)
            return foundNode;
        else
            foundNode = findNode(node->right, name);
        return foundNode;
    }

    void printChildren(Node *node)
    {
        if (node == nullptr)
            return;
        cout << node->name << " ";
        printChildren(node->right);
    }

    // 输出某人的所有儿子
    void findAllSons(const string &name)
    {
        Node *node = findNode(root, name);
        if(!node)
        {
            cout << "查无此人!\n";
            return;
        }
        if (node->left != nullptr) // 如果左结点不为空,说明其应该是一个父亲,所以需要用母亲的结点来输出所有儿子
            printChildren(node->left->right);
        else // 不然就说明其本身就是母亲,输出其儿子即可
            printChildren(node->right);
        
        cout << "\n";
    }

    // 在家谱树中查找名字为name的节点的父节点
    Node *findParent(Node *node, const string &name)
    {
        if (node == nullptr)
            return nullptr;
        if (node->left != nullptr && node->left->name == name)
            return node;
        if (node->right != nullptr && node->right->name == name)
            return node;

        Node *f = findParent(node->left, name);
        if (f)
            return f;
        else
            f = findParent(node->right, name);
        return f;
    }

    // 查找某人的所有祖先
    void findAllAncestors(const string &name)
    {
        Node *node = findNode(root, name);
        if(!node)
        {
            cout << "查无此人! \n";
            return;
        }
        node = findParent(root, node->name);
        while (node)
        {
            cout << node->name << " ";
            node = findParent(root, node->name);
        }
        cout << "\n";
    }

    // 用括号表示法输出家谱二叉树【传入根节点】
    void printTree(Node *node)
    {
        if (node)
        {
            cout << node->name;
            if (node->left || node->right)
            {
                cout << "(";
                printTree(node->left);

                cout << ",";

                printTree(node->right);
                cout << ")";
            }
        }
    }

    void SavePerson(Node *node)
    {
        ofstream of("family.txt", ios::app);
        if (!node)
            return;
        of << "\n" << node->name << " " << (node->left ? node->left->name : "NULL") << " " << (node->right ? node->right->name : "NULL");
        of.close();
    }

    void DestoryTree(Node *p)
    {
        if (p == nullptr)
            return; // 已经为nullptr,无需销毁
        DestoryTree(p->left);
        DestoryTree(p->right);
        p->left = nullptr;
        p->right = nullptr;
        delete p;
        p = nullptr;
    }

    void ClearFile(Node *node)
    {
        DestoryTree(root);
        ofstream of("family.txt", ios::trunc);
    }

    void Menu()
    {
        cout << "--------------------------------------------------------------------------------------------------------------------------------------------------------\n";
        cout << "**********************************************                      家谱信息管理系统                      **********************************************\n";
        cout << "--------------------------------------------------------------------------------------------------------------------------------------------------------\n";
        cout << "操作说明 (输入内容均会自动保存):\n";
        cout << "0 - 退出系统; 1 - 家谱记录输入; 2 - 家谱记录输出; 3 - 清除全部文件记录; 4 - 输出家谱二叉树(括号表示法); 5 - 查找某人的所有儿子; 6 - 查找某人的所有祖先;\n";
        cout << "--------------------------------------------------------------------------------------------------------------------------------------------------------\n";
    }

    void Action()
    {
        bool ctl = true;
        ifstream ifs;
        char ch;
        while (ctl)
        {
            cout << "\n请输入操作指令: ";
            int select = 0;
            cin >> select;
            switch (select)
            {
            case 0: // 退出系统
                ctl = false;
                cout << "欢迎再次使用本系统!\n";
                break;
            case 1: // 录入
            {
                cout << "请依次输入父亲、母亲、孩子的名字(若不存在,请输入 NULL):\n";
                string a, b, c;
                cin >> a >> b >> c;
                SavePerson(AddRecord(a, b, c));
                cout << "录入成功!记录已存盘!\n";
                break;
            }
            case 2: // 输出
            {
                cout << "----------------\n";
                cout << "父亲 母亲 孩子\n";
                cout << "----------------\n";
                ifs.open("family.txt", ios::in);
                while ((ch = ifs.get()) != EOF)
                    cout << ch;
                ifs.close();
                cout << "\n";
                break;
            }
            case 3: // 清除全部文件记录
            {
                ClearFile(root);
                ofstream ofs;
                ofs.open("family.txt", ios::trunc);
                ofs.close();
                cout << "清除成功! \n\n";
                break;
            }
            case 4: // 输出家谱二叉树(括号表示法)
                printTree(root);
                cout << "\n";
                break;
            case 5: // 查找某人的所有儿子
            {
                string x;
                cout << "请输入姓名: ";
                cin >> x;
                findAllSons(x);
                break;
            }
            case 6: // 查找某人的所有祖先
            {
                string xx;
                cout << "请输入姓名: ";
                cin >> xx;
                findAllAncestors(xx);
                break;
            }
            default:
                cout << "输入不合法!\n";
                break;
            }
        }
    }
};

int main()
{
    FamilyTree familyTree;

    // 从文件中读取家庭记录并添加到家谱树中
    ifstream file("family.txt");
    string father, mother, child;
    while (file >> father >> mother >> child)
        familyTree.AddRecord(father, mother, child);
    
    familyTree.Menu();
    familyTree.Action();
    system("pause");
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值