树(多叉树)简单介绍

树(多叉树)是一种层次结构的数据结构,由节点组成,每个节点可以有多个子节点。树的最顶层节点称为根节点,没有子节点的节点称为叶子节点

树的常见特点:

  • 父节点:每个节点的直接上层节点。
  • 子节点:每个节点的直接下层节点。
  • 深度:从根节点到某节点的路径长度。
  • 高度:从某节点到叶子节点的最长路径。

树在文件系统、组织结构图、数据库索引等场景广泛使用。常见操作包括遍历(前序、后序、层序遍历)和插入、删除节点等。

实验题目

多叉树设计(家谱)

实验内容

任务目标

在教材合适的代码基础上,实现关于树的存储、显示、操作等功能,并选用你熟悉的历史人物及其之间关系作为测试数据,国内国外、中文英文都可以,可以理解类似是建立一个简单的家谱管理系统。

任务描述

基本功能

  • 1)自建测试数据,根结点是你自己的名字,初始树的高度至少为4,结点数量至少为10,每个结点的值不要重复;(10分)
  • 2)系统提供菜单,显示以及可以选择相应的功能;(8分)
  • 3)初始树的数据从文件载入,文件格式自定;(10分)
  • 4)按树的显示方式显示树,同时显示其高度、结点数;(12分)
  • 5)新增结点功能,操作逻辑自行实现;(10分)
  • 6)删除结点功能,操作逻辑自行实现;(10分)
  • 7)修改结点功能,操作逻辑自行实现;(10分)
  • 8)树被修改后,也就是经过上述三种操作后,立即重新显示;(9分)
  • 9)给出先根、后根以及层序遍历的三种顺序;(9分)
  • 10)系统退出后,修改的数据自动存储到文件。(12分)

进阶功能

  • 1)图形界面显示树;(10分)
  • 2)动画方式实现遍历过程。(15分)

本文实现全部功能,难点&创新点:

  • 1)动画实现三种遍历顺序,树形或文字的展示;
  • 2)树形展示,具有颜色的cmd界面,清晰直观;
  • 3)从文件输入随意信息即可建立初始树;
  • 4)插入结点时,若已存在相同节点名,则将两者相关联,且子结点不可能通过操作成为其父节点的父节点,具有一定的现实意义,健壮性高。

解决方案

算法设计总述

在本实验中使用C++语言,实现了多叉树的各种操作。整体思路包括通过节点类 TreeNode 定义了多叉树的节点结构,包括数据元素和子节点列表。通过 MultiTree 类定义了多叉树的操作,包括获取根节点、从文件读取创建树、以先根、后根或层序遍历方式输出树的结果至文件、查找父节点、查找兄弟节点、查找子节点、插入节点、删除节点及子树、计算树的高度、计算节点数、查找特定节点、找到节点所在层次、移动和替换节点、动画演示输出等功能。在代码中还强调了错误处理和异常情况的处理,对文件的读取和写入进行了充分的检查和处理,以确保程序的稳定性和可靠性。

类和结构体 的数据成员设计

class TreeNode { //树的节点类
public:
    string data;
    vector<TreeNode*> children; //为符合多叉树一个父节点可能会有多个孩子节点的情况
    TreeNode(string val){
        data = val;
    }
    void addChild(TreeNode* child){  //通过指针实现增加孩子(子树一并移动)
        children.push_back(child);
    }
};
class MultiTree {
public:
    TreeNode* root;// 成员变量
    MultiTree(){  //构造函数
        root = nullptr;
    }
    MultiTree(const string& e){
        root = new TreeNode(e);
    }
    TreeNode* getRoot(){ // 获取根节点
        return root;
}
    ///////////////其他函数见下表
};

此外,为了更好的显示二叉树,设计了一个SetColor函数,更清晰美观的展示输出内容。

SetColor函数设计

void SetColor(int fore = 7, int back = 0) {// 改变控制台中输出的颜色
    unsigned char m_color = fore;
    m_color += (back << 4);
    SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), m_color);
return;
}

MultiTree类中的函数设计

#ifndef __MULTTREE_H__
#define __MULTTREE_H__

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <utility>
#include <fstream>
#include <iomanip>
#include <algorithm>
#include <chrono> // 用于时间延迟
#include <thread> // 用于时间延迟
#include "SetColor.h"
using namespace std;

class TreeNode {
public:
    string data;
    vector<TreeNode*> children;

    TreeNode(string val) 
    {
        data = val;
    }

    void addChild(TreeNode* child) 
    {
        children.push_back(child);
    }
};

class MultiTree {
public:
    // 成员变量
    TreeNode* root;

    //构造函数
    MultiTree() 
    {
        root = nullptr;
    }
    MultiTree(const string& e)
    {
        root = new TreeNode(e);
    }

    // 获取根节点
    TreeNode* getRoot()
    {
        return root;
    }

    // 从文件读取
    void createTreeFromFile(const string& filename) 
    {
        ifstream inputFile(filename);
        string line;

        while (getline(inputFile, line)) {
            istringstream iss(line);
            string nodeName;
            string parentNodeName;
            iss >> nodeName >> parentNodeName;

            if (parentNodeName == "NULL") {
                insertChild(nullptr, nodeName);
            } else {
                TreeNode* parentNode = findNode(root, parentNodeName); // 实现 findNode
                insertChild(parentNode, nodeName);
            }
        }
    }

    // 将先根遍历结果输出到给定的文件输出流
    void preOrderTraversal(TreeNode* root, ofstream& outputFile) 
    {
        if (root == nullptr) 
        {
            return;
        }
        outputFile << root->data << " "; 
        for (TreeNode* child : root->children) 
        {
            preOrderTraversal(child, outputFile); 
        }
    }
    // 将后根遍历结果输出到给定的文件输出流
    void postOrderTraversal(TreeNode* root, ofstream& outputFile) 
    {
        if (root == nullptr) 
        {
            return;
        }
        for (TreeNode* child : root->children) 
        {
            postOrderTraversal(child, outputFile); 
        }
        outputFile << root->data << " "; 
    }
    // 保存到文件
    void saveMultiTreeToFile(TreeNode* root, const string& filename) 
    {
        ofstream outputFile(filename);
        if (outputFile.is_open()) 
        {
            // 输出树的先序遍历结果到文件
            outputFile << "先根遍历结果: ";
            preOrderTraversal(root, outputFile);
            outputFile << endl;

            // 输出树的中序遍历结果到文件
            outputFile << "后根遍历结果: ";
            postOrderTraversal(root, outputFile);
            outputFile << endl;

            outputFile.close();
            SetColor(3, 0);
            cout << "多叉树已成功写入到文件: " << filename << endl;
            SetColor();
        } 
        else 
        {
            SetColor(3, 0);
            cout << "无法打开文件:" << filename << ",树写入失败" << endl;
            SetColor();
        }
    }

    // 找到父节点
    TreeNode* findParentNode(TreeNode* node, const string& value, TreeNode* parent = nullptr) 
    {
        if (node == nullptr) {
            return nullptr;
        }
        if (node->data == value) {
            return parent;
        }
        for (auto child : node->children) {
            TreeNode* result = findParentNode(child, value, node);
            if (result != nullptr) {
                return result;
            }
        }
        return nullptr;
    }

    // 找兄弟节点
    vector<TreeNode*> findBrotherNodes(TreeNode* root, const string& value) 
    {
        TreeNode* parent = findParentNode(root, value);
        vector<TreeNode*> brothers;
        if (parent != nullptr) 
        {
            for (auto child : parent->children) 
            {
                if (child->data != value) 
                {
                    brothers.push_back(child);
                }
            }
        }
        return brothers;
    }

    vector<TreeNode*> findChildrenNodes(TreeNode* node) 
    {
        vector<TreeNode*> childrenList;
        if (node == nullptr) 
        {
            return childrenList;
        }
        for (auto child : node->children) 
        {
            childrenList.push_back(child);
        }
        return childrenList;
    }

    // 插入一个节点作为上一个节点的子节点
    void insertChild(TreeNode* parent, const string& value) 
    {
        if (parent == nullptr) 
        {
            root = new TreeNode(value);
        } 
        else 
        {
            TreeNode* newChild = new TreeNode(value);
            parent->children.push_back(newChild);
        }
    }

    // 删除结点
    void deleteSubtree(TreeNode* node) 
    {
        if (node == nullptr)
        {
            return;
        }
        for (auto child : node->children) 
        {
            deleteSubtree(child);  // 递归删除子节点
        }
        delete node;  // 删除当前节点
    }
    bool deleteNodeAndSubtree(TreeNode* root, const string& value) 
    {
        if (root == nullptr) 
        {
            return false;
        }
        if (root->data == value) 
        {
            deleteSubtree(root);  // 删除当前节点及其所有子节点
            return true;
        }
        // 在当前节点的子节点中递归查找
        for (size_t i = 0; i < root->children.size(); i++) 
        {
            if (root->children[i]->data == value) 
            {
                deleteSubtree(root->children[i]);  // 删除子树
                root->children.erase(root->children.begin() + i);  // 移除当前子节点
                return true;
            }
        }
        // 如果在子节点中没有找到,继续向下搜索
        for (size_t i = 0; i < root->children.size(); i++) 
        {
            if (deleteNodeAndSubtree(root->children[i], value)) 
            {
                return true;  // 如果子树中已完成删除操作,返回true
            }
        }
        return false;
    }

    // 计算树(结点的子树)的高度
    int calculateHeight(TreeNode* node) 
    {
        if (node == nullptr) {
            return 0;
        }
        int maxHeight = 0;
        for (auto child : node->children) {
            maxHeight = max(maxHeight, calculateHeight(child));
        }
        return maxHeight + 1;
    }

    // 计算节点数
    int calculateNodeCount(TreeNode* node)
    {
        if (node == nullptr) {
            return 0;
        }
        int count = 1;
        for (auto child : node->children) {
            count += calculateNodeCount(child);
        }
        return count;
    }

    // 找到结点
    TreeNode* findNode(TreeNode* current, const std::string& nodeName) 
    {
        if (current == nullptr) 
        {
            return nullptr;
        }
        if (current->data == nodeName) 
        {
            return current;
        }
        for (TreeNode* child : current->children)
        {
            TreeNode* found = findNode(child, nodeName);
            if (found != nullptr) 
            {
                return found;
            }
        }
        return nullptr;
    }

    // 在第几层
    int findNodeLevel(TreeNode* root, const string& value) 
    {
        if (root == nullptr) 
        {
            return -1;  // 树为空,返回-1
        }
        queue<pair<TreeNode*, int>> nodesQueue;
        nodesQueue.push(make_pair(root, 1));  // 将根节点加入队列
        while (!nodesQueue.empty()) 
        {
            TreeNode* current = nodesQueue.front().first;
            int level = nodesQueue.front().second;
            nodesQueue.pop();
            if (current->data == value) 
            {
                return level;
            }
            // 遍历当前节点的每个子节点,将它们加入队列并设置正确的层级
            for (TreeNode* child : current->children)
            {
                nodesQueue.push(make_pair(child, level + 1));
            }
        }
        return -1;  // 未找到节点,返回-1
    }

    // 移动结点
    bool moveAndReplaceNode(TreeNode* root, const string& targetValue, const string& destinationValue) 
    {
        int targetLevel = findNodeLevel(root, targetValue);
        int destinationLevel = findNodeLevel(root, destinationValue);
        if (targetLevel < 0 || destinationLevel < 0) 
        {// 错误处理:如果目标值或者目标位置不存在,返回 false
            return false;
        }

        if (targetLevel < destinationLevel) 
        {// 错误处理:如果目标值在目标位置之下,无法移动,返回 false
            return false;
        }
        TreeNode* targetParent = findParentNode(root, targetValue);
        TreeNode* destinationParent = findParentNode(root, destinationValue);
        if (targetParent == nullptr || destinationParent == nullptr) 
        {// 错误处理:如果找不到目标值或者目标位置的父节点,返回 false
            return false;
        }
        // 寻找目标值在其父节点中的位置,然后移除
        for (size_t i = 0; i < targetParent->children.size(); i++) 
        {
            if (targetParent->children[i]->data == targetValue) 
            {
                TreeNode* toMove = targetParent->children[i];
                targetParent->children.erase(targetParent->children.begin() + i);
                // 将目标值移动到目标位置的子节点中
                destinationParent->children.push_back(toMove);
                return true;  // 移动成功
            }
        }
        return false;  // 未能成功移动
    }

    // 动画演示输出顺序
    void printaniMultiTree(TreeNode* root, TreeNode* cur, string prefix = "", bool isLast = true) 
    {
        if (root) {
            SetColor(6, 0);
            cout << prefix;
            SetColor();
            SetColor(6, 0);
            if(root->data!="(朱俊枫)")
            {
                cout << (isLast ? "└───" : "├───");
            }
            SetColor();
            if(root==cur)
            {
                SetColor(0, 5);
                cout << root->data ;
                SetColor();
                cout << endl;
            }
            else if(root->data=="(朱俊枫)")
            {
                SetColor(0, 6);
                cout << root->data ;
                SetColor();
                cout << endl;
            }
            else if(root->data!="(朱俊枫)")
            {
                SetColor(2, 0);
                cout << root->data << endl;
                SetColor();
            }
            for (size_t i = 0; i < root->children.size(); ++i) 
            {
                if(i == root->children.size() - 1) 
                {
                    printaniMultiTree(root->children[i], cur, prefix + (isLast ? "     " : "│    "), true);
                } 
                else 
                {
                    printaniMultiTree(root->children[i], cur, prefix + (isLast ? "     " : "│    "), false);
                }
            }
        }
    }
    void preOrderprint(TreeNode* node) 
    {
        if (node == nullptr) 
        {
            return;
        }
        TreeNode *current = node;
        SetColor(3, 0);
        cout << "先根遍历结果: ";
        SetColor();
        preOrderTraversal(root, current);
        cout << endl;
        printaniMultiTree(root, current);
        Sleep(1000);
        system("cls");
        for (TreeNode* child : node->children) {
            preOrderprint(child); // 递归处理每个子节点
        }
    }
    void postOrderprint(TreeNode* node)
    {
        if (node == nullptr) 
        {
            return;
        }
        TreeNode *current = node;
        for (TreeNode* child : node->children) 
        {
            postOrderprint(child); // 递归处理每个子节点
        }
        SetColor(3, 0);
        cout << "后根遍历结果: ";
        SetColor();
        postOrderTraversal(root, current);
        cout << endl;
        printaniMultiTree(root, current);
        Sleep(1000);
        system("cls");
    }
    void levelOrderprint(TreeNode* root) 
    {
        if (root == nullptr) 
        {
            return;
        }
        queue<TreeNode*> nodesQueue;
        nodesQueue.push(root);
        while (!nodesQueue.empty()) 
        {
            TreeNode* current = nodesQueue.front();
            SetColor(3, 0);
            cout << "层序遍历结果: ";
            SetColor();
            levelOrderTraversal(root, current);
            cout << endl;
            printaniMultiTree(root, current);
            Sleep(1000);
            system("cls");
            nodesQueue.pop();
            for (TreeNode* child : current->children) 
            {
                nodesQueue.push(child); // 将子节点加入队列
            }
        }
    }

    // 先根遍历(前序遍历)
    void preOrderTraversal(TreeNode* root, TreeNode* cur) 
    {
        if (root == nullptr) 
        {
            return;
        }
        if(root==cur)
        {
            SetColor(0, 5);
            cout << root->data << " ";
            SetColor();
        }
        else
        {
            cout << root->data << " "; // 处理当前节点
        }
        for (TreeNode* child : root->children) {
            preOrderTraversal(child, cur); // 递归处理每个子节点
        }
    }
    // 后根遍历(后序遍历)
    void postOrderTraversal(TreeNode* root, TreeNode* cur)
     {
        if (root == nullptr) 
        {
            return;
        }
        for (TreeNode* child : root->children) 
        {
            postOrderTraversal(child, cur); // 递归处理每个子节点
        }
        if(root==cur)
        {
            SetColor(0, 5);
            cout << root->data << " ";
            SetColor();
        }
        else
        {
            cout << root->data << " "; // 处理当前节点
        }
    }
    // 层序遍历
    void levelOrderTraversal(TreeNode* root, TreeNode* cur) 
    {
        if (root == nullptr) 
        {
            return;
        }
        queue<TreeNode*> nodesQueue;
        nodesQueue.push(root);
        while (!nodesQueue.empty()) 
        {
            TreeNode* current = nodesQueue.front();
            nodesQueue.pop();
            if(current==cur)
            {
                SetColor(0, 5);
                cout << current->data << " ";
                SetColor();
            }
            else
            {
                cout << current->data << " "; // 处理当前节点
            }
            for (TreeNode* child : current->children) 
            {
                nodesQueue.push(child); // 将子节点加入队列
            }
        }
    }

    void animateTraversal(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        // 显示先根遍历动画
        SetColor(3, 0);
        cout << "先根遍历" << endl;
        SetColor();
        animatedPreOrderTraversal(root);
        cout << endl;
        // 显示后根遍历动画
        SetColor(3, 0);
        cout << "后根遍历" << endl;
        SetColor();
        animatedPostOrderTraversal(root);
        cout << endl;
        // 显示层序遍历动画
        SetColor(3, 0);
        cout << "层序遍历" << endl;
        SetColor();
        animatedLevelOrderTraversal(root);
        cout << endl;
    }

    void animatedPreOrderTraversal(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        cout << root->data << " "; // 处理当前节点
        this_thread::sleep_for(chrono::milliseconds(70)); // 添加延迟
        for (TreeNode* child : root->children) {
            animatedPreOrderTraversal(child); // 递归处理每个子节点
        }
    }

    void animatedPostOrderTraversal(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        for (TreeNode* child : root->children) {
            animatedPostOrderTraversal(child); // 递归处理每个子节点
        }
        this_thread::sleep_for(chrono::milliseconds(70)); // 添加延迟
        cout << root->data << " "; // 处理当前节点
    }

    void animatedLevelOrderTraversal(TreeNode* root) {
        if (root == nullptr) {
            return;
        }
        queue<TreeNode*> nodesQueue;
        nodesQueue.push(root);
        while (!nodesQueue.empty()) {
            TreeNode* current = nodesQueue.front();
            nodesQueue.pop();
            cout << current->data << " "; // 处理当前节点
            this_thread::sleep_for(chrono::milliseconds(70)); // 添加延迟
            for (TreeNode* child : current->children) {
                nodesQueue.push(child); // 将子节点加入队列
            }
        }
    }

    // 树形输出
    void printMultiTree(TreeNode* root, string prefix = "", bool isLast = true) 
    {
        if (root) {
            SetColor(6, 0);
            cout << prefix;
            SetColor();
            SetColor(6, 0);
            if(root->data!="(朱俊枫)")
            {
                cout << (isLast ? "└───" : "├───");
            }
            SetColor();
            if(root->data=="(朱俊枫)")
            {
                SetColor(0, 6);
                cout << root->data ;
                SetColor();
                cout << endl;
            }
            if(root->data!="(朱俊枫)")
            {
                SetColor(2, 0);
                cout << root->data << endl;
                SetColor();
            }

            // 添加了对root->children是否为空的检查
            for (size_t i = 0; i < root->children.size(); ++i) 
            {
                // 添加了对i是否越界的检查
                if(i == root->children.size() - 1) 
                {
                    printMultiTree(root->children[i], prefix + (isLast ? "     " : "│    "), true);
                } 
                else 
                {
                    printMultiTree(root->children[i], prefix + (isLast ? "     " : "│    "), false);
                }
            }
        }
    }

    // 输出模板
    void coutmodel()
    {
        SetColor(8, 0);
        cout << "《红楼梦》贾家人物关系表 (左根右子)" << endl << endl;
        SetColor();
        printMultiTree(root);
        cout << endl;
        animateTraversal(root);
        SetColor(3, 0);
        cout << "高度是: ";
        SetColor();
        cout << calculateHeight(root);
        cout << endl;
        SetColor(3, 0);
        cout << "节点数: ";
        SetColor();
        cout << calculateNodeCount(root) << endl <<endl;
    }
};

#endif

// 从文件中输入节点数据和位置(可随意插入任意个数节点)

打开指定的文件,并逐行读取文件内容。 对于每行内容,解析节点名称和其父节点名称。

如果父节点名称为"NULL",将其作为根节点或子节点。

如果父节点名称不为"NULL",在树中查找对应的节点,然后将当前节点插入到父节点的子节点列表中。

// 输出到文件

1. preOrderTraversal函数:递归地对树进行先根(前序)遍历,将遍历结果输出到给定的文件输出流中。如果当前节点为空,直接返回;否则,首先将当前节点的数据写入文件,然后对当前节点的每个子节点进行先根遍历。

2. postOrderTraversal函数:递归地对树进行后根(后序)遍历,将遍历结果输出到给定的文件输出流中。如果当前节点为空,直接返回;否则,首先对当前节点的每个子节点进行后根遍历,然后将当前节点的数据写入文件。

3. saveMultiTreeToFile函数:通过先根遍历和后根遍历将树的遍历结果写入到指定的文件中。先打开指定文件,然后在文件流上执行先根遍历和后根遍历操作,将遍历结果写入文件,最后关闭文件流。如果文件成功打开,打印成功写入信息;如果无法打开文件,打印写入失败信息。

通过这种设计,实现了将多叉树的先根遍历和后根遍历结果保存到指定文件的操作,并提供了针对文件操作的错误处理。

// 找父节点

1. 该函数接受三个参数:当前节点指针node、目标值value、node父节点指针parent,初始调用时parent设为null。

2. 首先检查当前节点是否为空,如果为空,则直接返回nullptr,表示未找到父节点。

3. 接着检查当前节点的数据是否等于目标值,如果相等,说明当前节点即为目标节点,返回其父节点指针parent。

4. 对当前节点每个子节点递归调用该函数,将当前节点设为其父节点,继续在子节点中查找目标值的父节点。

5. 如果递归查找的结果不为空,说明在子节点中找到了目标节点,则将结果直接返回。

6. 如果当前节点及其子节点中都未找到目标节点,则最终返回nullptr,表示未找到父节点。

通过这种设计,实现了对多叉树中任意节点的父节点进行查找的功能,并且处理了节点为空、节点为目标节点、以及在子节点中查找的情况。

// 找兄弟节点

这段代码用于找到多叉树中指定节点的兄弟节点,其设计思路如下:

1. 首先通过调用findParentNode函数找到指定节点的父节点。

2. 创建一个存储兄弟节点的vector<TreeNode*>容器brothers。

3. 如果父节点存在(即不为nullptr),则遍历父节点的每个子节点,将不等于目标值的子节点加入brothers容器中,即除了目标节点自身外的其他子节点。

4. 返回brothers容器,其中包含了指定节点的所有兄弟节点。

通过这种设计,实现了查找多叉树中指定节点的兄弟节点的功能,并且处理了节点无父节点的情况。

// 找孩子节点

1. 创建一个空的 vector<TreeNode*> 类型的容器 childrenList,用于存储节点的子节点。

2. 首先检查输入的节点是否为空,如果为空,则直接返回空的 childrenList,表示当前节点没有子节点。

3. 对于非空节点,遍历当前节点的 children 列表,将每个子节点加入 childrenList 容器中。

4. 返回包含当前节点所有子节点的 childrenList 容器,完成节点的子节点收集操作。

通过这种设计,实现了查找和收集多叉树中指定节点的子节点,并在节点为空时能够正确处理并返回空的子节点列表。

// 插入一个节点作为上一个节点的子节点

1. 首先检查父节点是否为空。如果父节点为空,则表示树为空,此时将新建一个值为value的节点作为根节点。

2. 如果父节点不为空,新建一个值为value的节点作为要插入的子节点。然后将该子节点加入到父节点的children容器中,即作为其子节点。

通过这种设计,实现了在多叉树中根据父节点插入新的子节点的功能。

// 根据值删除节点  // 判断是否存在这个节点(是否是可删除的)  // 删除结点

1. deleteSubtree函数:用于递归删除子树。首先检查当前节点是否为空,如果为空则直接返回;然后遍历当前节点的所有子节点,对每个子节点调用自己,递归删除所有子树;最后删除当前节点。

2. deleteNodeAndSubtree函数:用于删除指定值节点及其子树。先检查根节点是否为空,若为空返回false;若根节点为目标节点,调用deleteSubtree直接删除根节点及其所有子节点并返回true;若不是目标节点,则在根节点的子节点列表中查找目标节点:若找到目标节点,删除子树并移除当前子节点;若未找到,递归调用自己在子树中继续查找。

通过这种设计,实现了在多叉树中删除目标节点及其子树的功能,并且对节点为空、节点为目标节点、以及在子节点中查找的情况都进行了正确的处理。

// 修改结点

较为简单,故未编写函数,直接体现在main函数case(3)

1. 利用findNode函数查找并定位到对应的节点。

2. 如果查找到的节点正好是根节点,需要给出相应提示并结束修改操作。

3. 如果找不到对应的节点,也向用户给出相应提示并结束修改操作。

4. 如果找到了目标节点,再次通过输入获取用户期望的新值,并将节点的值更新为新值。

通过这样的设计,用户能够根据输入来指定要修改的节点并进行相应的值更新操作,同时也给出了对应的错误提示。

// 计算高度  

1. 若当前节点为空,则返回高度0。

2. 遍历当前节点的所有子节点,对每个子节点进行递归调用calculateHeight函数,以计算每棵子树的高度。

3. 取得当前节点的所有子节点中最大的子树高度,即maxHeight。

4. 最终的子树高度为maxHeight加上当前节点自身的高度1,即maxHeight + 1。

通过这种设计,实现了对多叉树或者节点的高度进行计算,递归地考虑了每个子树的高度,最终得到整棵子树的高度。

// 计算节点数

1. 若当前节点为空,则返回节点数0。

2. 初始化计数值为1,表示当前节点本身。

3. 遍历当前节点每个子节点,对每个子节点调用calculateNodeCount函数,将返回的子树节点数量累加到计数值中。

4. 返回累加后的计数值,即为整个子树的节点数量。

通过这种设计,实现对多叉树或节点的子树节点数量进行计算,递归考虑每个子树节点数量,最终得整棵子树的节点数。

// 找到结点

1. 若当前节点为空,则返回nullptr,表示未找到目标节点。

2. 首先检查当前节点的数据是否等于目标名称,如果相等,说明当前节点即为目标节点,返回当前节点指针。

3. 对当前节点的每个子节点进行递归调用findNode函数,将子节点作为当前节点,继续在子节点中查找目标名称节点。

4. 如果在当前节点的子节点或子节点的子节点中找到了目标节点,则将找到的节点指针直接返回。

5. 若当前节点及其子节点中都未找到目标节点名称,则最终返回nullptr,表示未找到目标节点。

通过这种设计,实现在多叉树中查找指定名称的节点,并处理节点为空、节点为目标节点、以及在子节点中查找的情况。

// 找到指定结点在第几层

1. 若根节点为空,则返回-1,表示树为空,无法找到层级。

2. 创建一个队列nodesQueue,用于进行层序遍历并记录节点的层级。  queue<pair<TreeNode*, int>> nodesQueue;

3. 将根节点和其层级1作为pair加入到队列中。                      nodesQueue.push(make_pair(root, 1));

4. 在队列不为空的情况下,循环执行以下步骤: TreeNode* current = nodesQueue.front().first;

   - 取出队列中的第一个节点和该节点的层级。    int level = nodesQueue.front().second;

   - 检查当前节点的数据是否等于目标值,如果相等,则表示找到了目标节点,直接返回其所在的层级。

   - 遍历当前节点的每个子节点,将子节点以及其对应的层级加入到队列中,层级加1

5. 若遍历完整棵树后都未找到目标节点,最终返回-1,表示未找到节点所在的层级。

通过这种设计,实现了在多叉树中查找指定节点所在的层级,并且使用层序遍历的方式逐层搜索目标节点,根据结果返回节点所在的层级。

// 移动结点

1. 首先,根据目标值和目标位置值分别获取它们在树中的层级,如果其中有一个值不存在,或者目标值在目标位置之下,即目标值的层级小于目标位置的层级,则无法进行移动,直接返回false。

2. 接着,查找目标值和目标位置值的父节点,如果找不到他们,则无法进行移动,直接返回false。

3. 在目标值的父节点中查找目标值在其子节点中的位置,然后将其移除。

4. 最后,将目标值移动到目标位置的子节点中,并返回true,表示移动操作成功;如果未执行成功的话,返回false。

通过这种设计,实现了在多叉树中移动节点到另一个位置并替换节点的功能,并对错误情况进行了相应的处理。

//先根、后根遍历

1. 先根遍历函数 preOrderTraversal:

    - 若当前节点为空,则直接返回。。

    - 遍历当前节点的每个子节点,对每个子节点进行递归调用preOrderTraversal,处理每个子节点。

2. 后根遍历函数 postOrderTraversal

    - 若当前节点为空,则直接返回。

- 对当前节点的每个子节点进行递归调用postOrderTraversal,处理每个子节点。

//层序遍历

1. 首先检查根节点是否为空,如果为空则直接返回。

2. 创建一个队列 nodesQueue,并将根节点加入队列。

3. 在队列不为空的情况下,循环执行以下步骤:

   - 取出队列中的第一个节点 current

   - 以打印节点数据。

   - 将当前节点的每个子节点加入队列中,以便后续继续遍历子节点。

// 树形输出

1. 首先检查当前节点是否为空。如果为空则直接返回,不进行任何打印操作。

2. 如果当前节点不为空,首先根据节点是不是最后一个子节点,打印对应的前缀,用以展示树的分支结构。

3. 根据节点的数据是否为特定值,设置节点的打印颜色,以突出显示特定节点。

4. 对当前节点的每个子节点进行递归调用 printMultiTree 函数,打印子节点的数据和对应的分支结构。

通过这种设计和递归调用,实现了对整个多叉树结构清晰打印,并能够通过特定颜色和分支结构直观展示树的层次结构。
// 动画演示输出顺序

1. printaniMultiTree函数:

   - 与前面所描述的树形输出函数类似,但增加了一个参数cur,用以表示当前处理的节点。

   - 在输出时,如果当前节点是目标节点cur,则使用特定颜色输出,以突出显示该节点。

2. preOrderprint函数: 对树进行先根遍历,并在每个节点遍历完毕后输出遍历结果,并调用printaniMultiTree函数展示树的结构,并通过延时操作(Sleep)和清屏操作(system("cls"))产生动画效果。

3. postOrderprint函数: 对树进行后根遍历,并在每个节点遍历完毕后输出遍历结果,并调用printaniMultiTree函数展示树的结构,并通过延时操作(Sleep)和清屏操作(system("cls"))产生动画效果。

4. levelOrderprint函数: 对树进行层序遍历,并在每个节点遍历完毕后输出遍历结果,并调用printaniMultiTree函数展示树的结构,并通过延时操作(Sleep)和清屏操作(system("cls"))产生动画效果。

通过这种设计,实现了对树的各种遍历操作,并以动画形式展示了遍历顺序和树的结构,增加了程序的可视化效果。

 源程序代码 main.cpp

int main()
{
    MultiTree tree;  tree.createTreeFromFile("input.txt");
    SetColor(3, 0);  cout << "第一次打开从input.txt中读取数据" << endl;
    tree.coutmodel();  SetColor(0, 15);
    cout << "1:删除某个节点  2:增加一个节点  3:修改节点内容  4:移动某个节点  5:查询某个节点  6:动画展示遍历  7:退出并保存至文件";
    SetColor();  cout << endl;
    while (1) {
        int swi = 0;  bool flag = 0;  cin >> swi;
        switch (swi){
        case(1):{
            SetColor(0, 15);  cout << "谁被逐出家门了:"; SetColor();  cout << endl;
            string edit;  cin >> edit;
            if (edit == tree.root->data){
                SetColor(11, 0); cout << "无法删除该根结点";  SetColor();  cout << endl; break;}
            bool flag1 = (tree.deleteNodeAndSubtree(tree.root, edit));
            if (flag1 == 0){
                SetColor(11, 0);  cout << "该人不在其中";  SetColor();  cout << endl;  break; }
            tree.coutmodel();  break;}
        case(2):{
            int choose = 0; SetColor(0, 15);
            cout << "1:找到了先人(在树中根据孩子节点插入父亲节点)  2:找到了后代(在树中根据父亲节点插入孩子节点)"; SetColor();  cout << endl;  cin >> choose;
            if (choose == 1){
                SetColor(0, 15);  cout << "谁找到了先人";  SetColor(); cout << endl;
                string add;  cin >> add;
                TreeNode* parent = tree.findParentNode(tree.root, add);
                TreeNode* cur = tree.findNode(tree.root, add);
                if (cur == tree.root){
                    SetColor(11, 0);  cout << "无法修改该根结点" << endl;  SetColor();   break;}
                if (parent == nullptr){
                    SetColor(11, 0);  cout << "该人不在其中" << endl;  SetColor();  break;}
                if (parent != nullptr){
                    SetColor(0, 15);  cout << "他的祖先叫?";  SetColor();  cout << endl;
                    string teacher;  cin >> teacher;
                    TreeNode* child = tree.findNode(tree.root, add);
TreeNode *teacherptr = tree.findNode(tree.root, teacher);
                    if(teacherptr!=nullptr && child != nullptr){teacherptr->addChild(child);}
                    if (teacherptr==nullptr && child != nullptr){
                        for (auto grandchild : parent->children){
                            if (grandchild->data == add){
                                tree.insertChild(parent, teacher);
                                TreeNode* tmp = tree.findNode(tree.root, teacher);
                                tmp->addChild(child);}}}
                    parent->children.erase(remove(parent->children.begin(), parent->children.end(), child), parent->children.end()); }}
            if (choose == 2){
                SetColor(0, 15);  cout << "谁找到了后代";  SetColor();  cout << endl;
                string add;  cin >> add;
                TreeNode* parent = tree.findNode(tree.root, add);
                if (parent == nullptr){
                    SetColor(11, 0);  cout << "该人不在其中" << endl;  SetColor();  break;}
                SetColor(0, 15);  cout << "他的孩子叫?";  SetColor();  cout << endl;
                string student;  cin >> student;
                TreeNode* past = tree.findNode(tree.root, student);
                TreeNode* pastpar = tree.findParentNode(tree.root, student);
if (tree.findNode(past, parent->data)){
                SetColor(11, 0);  cout << "这违反常理" << endl;  SetColor();  break; }
                if(past == nullptr){ tree.insertChild(parent, student);}
                if(past != nullptr){ parent->addChild(past);
                   pastpar->children.erase(remove(pastpar->children.begin(), pastpar->children.end(), past), pastpar->children.end());}}
            tree.coutmodel();  break;}
        case(3):{
            SetColor(0, 15);  cout << "想要修改哪个节点的值";  SetColor();  cout << endl;
            string findstr;  cin >> findstr;
            TreeNode* find = tree.findNode(tree.root, findstr);
            if (find == tree.root){
                SetColor(11, 0);  cout << "无法修改该根结点" << endl;  SetColor();  break;}
            if (find == nullptr) {
                SetColor(11, 0);  cout << "该人不在其中" << endl;  SetColor();  break;}
            SetColor(0, 15);  cout << "想要修改为?";  SetColor();  cout << endl;
            string setstr;  cin >> setstr;
            find->data = setstr;  tree.coutmodel();  break;}
        case(4):{
            SetColor(0, 15);  cout << "想要移动哪个节点";  SetColor();  cout << endl;
            string move;  cin >> move;
            TreeNode* moveptr = tree.findNode(tree.root, move);
            if (moveptr == tree.root){
                SetColor(11, 0);  cout << "无法修改该根结点" << endl;  SetColor();  break;}
            if (moveptr == nullptr){
                SetColor(11, 0);  cout << "该人不在其中" << endl;  SetColor();  break; }
            SetColor(0, 15);  cout << "移动至哪个节点的位置";  SetColor();  cout << endl;
            string end;  cin >> end;
            TreeNode* endptr = tree.findNode(tree.root, end);
            if (endptr == nullptr){
                SetColor(11, 0);  cout << "该人不在其中" << endl;  SetColor();  break;}
            TreeNode* parent = tree.findParentNode(tree.root, end);
            TreeNode* child = tree.findNode(tree.root, end);
            if ((tree.moveAndReplaceNode(tree.root, move, end)) == 1){
                parent->children.erase(remove(parent->children.begin(), parent->children.end(), child), parent->children.end());
                if (tree.findNodeLevel(tree.root, move) >= tree.findNodeLevel(tree.root, end)){tree.coutmodel();}}
            if (tree.findNodeLevel(tree.root, move) < tree.findNodeLevel(tree.root, end)){
                SetColor(11, 0);  cout << "无法向后移动 ";  SetColor();  cout << endl;}  break;}
        case(5):{
            SetColor(0, 15);  cout << "想要查找哪个节点";  SetColor();  cout << endl;
            string value;  cin >> value;
            TreeNode* find = tree.findNode(tree.root, value);
            int level = tree.findNodeLevel(tree.root, value);
            if (find == nullptr) {
                SetColor(11, 0);  cout << "该人不在其中";  SetColor();  cout << endl;}
            else {  cout << "在第";  SetColor(11, 0);  cout << level;  SetColor();  cout << "层";
                if (level == 1) {
                    SetColor(11, 0);  cout << ",已经是root了";  SetColor();  cout << endl;}
                if (level != 1) {
                    cout << ",他的先人为";  SetColor(11, 0);
                    cout << tree.findParentNode(tree.root, value)->data;
                    SetColor();  cout << ",他的兄弟为";  SetColor(11, 0);
                    vector<TreeNode*> brotherList;
                    brotherList = tree.findBrotherNodes(tree.root, value);
                    if (brotherList.size() != 0) {
                        for (int i = 0; i < (int)brotherList.size(); i++) {
                            cout << brotherList[i]->data << " ";}}
                    if ((int)brotherList.size() == 0) { cout << "空";}
                    SetColor();  cout << ",他的孩子为";  SetColor(11, 0);
                    vector<TreeNode*> childrenList;
                    childrenList = tree.findChildrenNodes(find);
                    if (childrenList.size() != 0) {
                        for (int i = 0; i < (int)childrenList.size(); i++){
                            cout << childrenList[i]->data << " ";}}
                    if ((int)childrenList.size() == 0) { cout << "空";}
                    SetColor(); cout << endl;}} break;}
        case(6):{
            int c; SetColor(0, 15);  cout << "先根:1   后跟:2   层序:3";  SetColor();
            cout << endl;  cin >> c;
            if (c == 1){ tree.preOrderprint(tree.root); tree.coutmodel();}
            if (c == 2){ tree.postOrderprint(tree.root); tree.coutmodel(); }
            if (c == 3){ tree.levelOrderprint(tree.root); tree.coutmodel();}
            if (c != 1 && c != 2 && c != 3){
                SetColor(0, 4);  cout << "输入有误";  SetColor();  cout << endl;} break;}
        case(7):{ flag = 1; break;}
        default:{ SetColor(0, 4);  cout << "输入有误";  SetColor(); cout << endl; }}
        if (flag == 1){ break;}
        SetColor(0, 15);
        cout << "1:删除某个节点  2:增加一个节点  3:修改节点内容  4:移动某个节点  5:查询某个节点  6:动画展示遍历  7:退出并保存至文件";
        SetColor();  cout << endl;}
    tree.saveMultiTreeToFile(tree.root, "output.txt");
}

实验结果

测试用例(input.txt)

运行结果截图

功能1:树形显示,菜单,计算并显示其高度、结点数及三种遍历顺序,从文件中读取数据

功能2删除和分别在前、后新增结点,修改后立即显示二叉树

功能3:修改节点内容,修改后立即显示二叉树

功能4:移动结点,修改后立即显示二叉树

功能5:查询节点信息

功能6:动画展示遍历

功能7:退出并保存至output.txt文件中

功能8:异常输入检查

算法分析

  • createTreeFromFile:遍历文件内容并根据节点信息构建多叉树,时间复杂度为O(n),其中n是节点的个数。
  • preOrderTraversal和postOrderTraversal:递归地遍历树的所有节点,时间复杂度为O(n),其中n是节点的个数。
  • saveMultiTreeToFile:内部包含先根遍历和后根遍历,因此时间复杂度也为O(n),其中n是节点的个数。
  • findParentNode和findNode:在多叉树中查找父节点和指定节点,最坏情况下的时间复杂度为O(n),其中n是节点的个数。
  • findBrotherNodes和findChildrenNodes:查找兄弟节点和子节点,时间复杂度为O(k),其中k是节点的子节点个数。
  • insertChild和moveAndReplaceNode:在树中插入和移动节点,时间复杂度取决于查找父节点的时间复杂度,最坏情况下为O(n),其中n是节点的个数。
  • deleteSubtree和deleteNodeAndSubtree:删除节点及其子树的操作,时间复杂度也为O(n),其中n是节点的个数。
  • calculateHeight和calculateNodeCount:计算树的高度和节点数,递归遍历每个节点,时间复杂度为O(n),其中n是节点的个数。
  • findNodeLevel:在树中查找节点所在的层级,最坏情况下的时间复杂度为O(n),其中n是节点的个数。
  • printaniMultiTree、preOrderprint、postOrderprint和levelOrderprint:这些函数会递归遍历节点并输出,所以时间复杂度与树的节点数成正比,最差情况下为O(n),其中n是节点的个数。
  • animatedPreOrderTraversal、 animatedPostOrderTraversal、 animatedLevelOrderTraversal:这些函数在树上进行遍历并输出,时间复杂度与树的节点数成正比,最差情况下为O(n),其中n是节点的个数。

综上所述,大多数操作的时间复杂度都与树的节点数量成正比,主要受树的结构和深度影哨。因此,在最坏情况下,这些操作的时间复杂度为O(n),其中n是节点的个数。

在解决“以动画形式输出遍历过程”这一问题时,我原本想法是对于先根和后根遍历的情况,自上而下或自下而上输出即可,但在层序遍历时这一算法行不通了,于是便想到使用一个指针current来表示当前遍历到的结点,在print函数中加上if(root==current)的判断条件来突出显示,并通过延时再清屏以完成动画的效果。本来还想创建多线程使文字和树形同时输出,但难以实现。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐