当前位置:首页 > 其他 > 手搓尾插法树类代码详解:从实现到优化的全流程指南

手搓尾插法树类代码详解:从实现到优化的全流程指南

5小时前

一、简介和特点

尾插法树是一种基于链表实现树结构的手动编码方式,通过将节点的孩子节点以尾插方式插入链表,实现树形数据的存储与操作。本文代码示例采用C++模板类,允许泛型数据类型,核心特点包括:

1. 使用listnode作为子节点链表单元,treenode存储数据及子节点链表头指针

2. tree类管理节点数组,支持动态分配内存,提供根节点设置、添加子节点、数据赋值及打印功能;

3. 尾插法插入子节点时,从链表末尾添加,避免头插法导致的遍历顺序颠倒,逻辑更直观。

二、优点

1. 逻辑清晰:尾插法按节点自然顺序添加,符合“父-子”层级关系,减少遍历时的反向调整;

2. 内存管理可控:通过节点数组预分配(如tree(int maxnodes)),避免频繁动态分配内存碎片;

3. 灵活性:模板类支持任意数据类型,适用于不同场景(如整数树、字符串树等);

4. 简洁性:相比递归构建或复杂指针操作,代码结构更易理解,适合新手学习树结构基础。

三、实现步骤

1. 定义节点结构

    listnode:链表节点,存储指向treenode的指针,通过next指向下一个节点;

    treenode:树节点,包含数据data和子节点链表头指针childrenhead。

2. 构建树类tree

    初始化节点数组:nodes为treenode指针数组,通过构造函数传入最大节点数(默认10000);

    根节点设置:通过setroot(int rootid)将指定索引的节点设为根;

    子节点添加:addchild(int parentid, int sonid)通过父/子ID定位节点,调用addchild(treenode* node)尾插子节点到父节点链表。

3. 数据操作与遍历

    assigndata(int id, T data):为指定ID节点赋值;

    print():递归打印根节点及其所有子节点,通过链表遍历子树。

四、代码及注释

#include <iostream>
using namespace std;

// 链表节点模板,存储指向T类型节点的指针
template <typename T>
struct listnode {
    T* data;         // 指向树节点的地址
    listnode* next = nullptr;  // 指向下一个节点

    // 构造:传入节点指针初始化data
    listnode(T* d) {
        data = d;
    }
    // 空构造(可能用于初始化头节点)
    listnode() {}
};

// 树节点模板,存储数据和子节点链表头指针
template <typename T>
struct treenode {
    T data;                      // 节点数据
    listnode<treenode<T>*>* childrenhead = nullptr;  // 子节点链表头

    // 添加子节点:尾插法
    void addchild(treenode<T>* node) {
        // 创建新子节点链表节点,指向传入的node
        listnode<treenode<T>*>* newchild = new listnode<treenode<T>*>(node);
        if (childrenhead == nullptr) {  // 若链表为空,直接作为头节点
            childrenhead = newchild;
        } else {                       // 否则从末尾插入
            listnode<treenode<T>*>* tmp = childrenhead;
            while (tmp->next) {        // 遍历到末尾
                tmp = tmp->next;
            }
            tmp->next = newchild;      // 尾插新节点
        }
    }
};

// 树类模板,管理节点数组和树操作
template <typename T>
class tree {
    treenode<T>* nodes;             // 节点数组指针
    treenode<T>* root = nullptr;     // 根节点

public:
    // 构造:默认分配10000个节点
    tree() {
        nodes = new treenode<T>[10000];
    }
    // 构造:传入最大节点数
    tree(int maxnodes) {
        nodes = new treenode<T>[maxnodes];
    }
    // 析构:释放节点数组
    ~tree() {
        delete[] nodes;
    }

    // 设置根节点(通过ID索引)
    void setroot(int rootid) {
        root = &nodes[rootid];
    }

    // 添加子节点(通过父/子ID)
    void addchild(int parentid, int sonid) {
        treenode<T>* parent = &nodes[parentid];
        treenode<T>* son = &nodes[sonid];
        parent->addchild(son);
    }

    // 为节点赋值
    void assigndata(int id, T data) {
        treenode<T>* node = &nodes[id];
        node->data = data;
    }

    // 递归打印节点及其子节点
    void print(treenode<T>* node) {
        cout << node->data;          // 打印当前数据
        listnode<treenode<T>*>* tmp = node->childrenhead;
        while (tmp) {               // 遍历子节点链表
            print(tmp->data);        // 递归打印子节点
            tmp = tmp->next;
        }
    }

    // 打印整棵树(从根开始)
    void print() {
        print(root);
    }
};

五、总结

本文通过尾插法树类的代码示例,展示了手动实现树结构的基础逻辑:利用链表维护子节点顺序,通过节点数组简化内存管理。该实现适合学习树结构原理,尤其适合新手理解“尾插”操作如何避免头插法的遍历问题。在实际应用中,可根据需求扩展(如添加删除节点、搜索等功能),但需注意内存释放细节(如节点删除时链表清理)。通过注释与步骤解析,读者可快速掌握从零构建树类的方法,为更复杂的算法设计打下基础。

原创内容 转载请注明出处

分享给朋友:

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。