C++ 数据结构之-二叉树(一个普通二叉树实现)

目录

二叉树

相关概念

C++实现

        代码

        实现分析

        插入和删除

        先/中/后序遍历

        分层打印二叉树


二叉树

        二叉树是一种树形数据结构,其中每个节点最多有两个子节点,一个左子节点和一个右子节点。二叉树通常使用递归方式定义,并将其定义为一个空节点或者是一个具有两个子节点的节点。(如相关概念中的图,就是一颗二叉树)。

        二叉树有一些重要的性质,包括:

        1. 深度限制:一个二叉树中,根节点的深度为0,子节点的深度为父节点的深度+1。在一个二叉树中,任何节点的深度不会超过树的高度。

        2. 节点数量限制:有n个节点的二叉树,它的叶子节点数量是n+1的二次幂。

        3. 遍历中序:二叉树的节点中序遍历结果是一个递增的有序序列。

        4. 遍历前序与后序:树的前序遍历遍历根节点,然后遍历左子树和右子树。后序遍历遍历左子树和右子树,然后遍历根节点。

        二叉树是一种常见的数据结构,在计算机科学中有广泛的应用。例如,它们可以用于表示文件系统,解析二叉表达式或搜索和排序算法。

相关概念

        

  1. 根节点(Root):二叉树中的第一个节点称为根节点,它没有父节点。(上图中的0就是这棵二叉树的根节点。)

  2. 父节点(Parent):除了根节点外,每个节点都有一个父节点。

    1. 上图中的0是4和1的父节点。同理4和1是0的两个子节点

  3. 子节点(Child):每个节点可能有 0、1 或 2 个子节点。

    1. 上图中的9、8、6、7有0个子节点,1和5只有一个子节点(右孩子7),0,4,2都有左右两个子节点(孩子)。

  4. 叶子节点(Leaf):没有子节点的节点称为叶子节点、终端节点或外部节点。

    1. 也就是子节点个数为0的节点:9、8、6、7

  5. 内部节点(Internal):至少有一个子节点的节点称为内部节点.

  6. 左子树(Left Subtree):一个节点下的左边所有节点组成的子树。

    1. 上图二叉树的左子树:

  7. 右子树(Right Subtree):一个节点下的右边所有节点组成的子树.

    1. 上图二叉树的右子树:

  8. 深度(Depth):从根节点到某一个节点的唯一路径上的边数。

  9. 高度(Height):从某一个节点到它下面的所有叶子节点所经过的路径上的边数,也就是从这个节点开始的子树的高度。

C++实现

        代码

#ifndef TEST_RANDOM_TREE_H
#define TEST_RANDOM_TREE_H


#include <iostream>
#include <map>
#include <cmath>
#include <string>
#include <random>

/**
 * 生成1-10的随机数
 * @return
 */
static int genRandomValue(){
    static std::random_device rd; // 随机设备,用于获取随机数种子
    static std::mt19937 gen(rd()); // 使用 Mersenne Twister 算法的伪随机数生成器
    static std::uniform_int_distribution<> dist(1, 10);  // 定义随机数分布和范围

    return dist(gen);
}

template<typename T>
class TreeNode {
public:
    explicit TreeNode(T val) : val(val), left(nullptr), right(nullptr) {}

    T val;
    TreeNode<T> *left;
    TreeNode<T> *right;
};


template<typename T>
class RandomTree {
public:

    /**
     * 添加元素
     * @param val
     */
    void insert(T val) {
        if (this->root == nullptr) {
            this->root = new TreeNode<T>(val);
        } else {
            this->insertNode(this->root, val);
        }
    }

    /**
     * 指定节点添加元素
     * 如果val值大于当前节点的值,插入左子树
     * 否则插入右子树
     * @param node
     * @param val
     */
    void insertNode(TreeNode<T> *node, T val) {
        if (genRandomValue() % 2 == 0) {
            if (node->left == nullptr) {
                node->left = new TreeNode<T>(val);
            } else {
                this->insertNode(node->left, val);
            }
        } else {
            if (node->right == nullptr) {
                node->right = new TreeNode<T>(val);
            } else {
                this->insertNode(node->right, val);
            }
        }
    }


    void preorderTraversal() {
        this->preorderTraversal(this->root);
    }

    void inorderTraversal() {
        this->inorderTraversal(this->root);
    }

    void postorderTraversal() {
        this->postorderTraversal(this->root);
    }


    void printTree() {
        this->printTree(this->root);
    }


    void deleteNode(T val){
        this->deleteNode(this->root, val);
    }

private:
    void preorderTraversal(TreeNode<T> *node) {
        if (node == nullptr) {
            return;
        }
        std::cout << node->val << std::endl;
        preorderTraversal(node->left);
        preorderTraversal(node->right);
    }


    void inorderTraversal(TreeNode<T> *node) {
        if (node == nullptr) {
            return;
        }
        postorderTraversal(node->left);
        std::cout << node->val << std::endl;
        postorderTraversal(node->right);
    }

    void postorderTraversal(TreeNode<T> *node) {
        if (node == nullptr) {
            return;
        }
        postorderTraversal(node->left);
        postorderTraversal(node->right);
        std::cout << node->val << std::endl;
    }


    /**
     * 从上到下打印二叉树
     * @param node
     */
    void printTree(TreeNode<T> *node) {
        if (node == nullptr) {
            return;
        }
        // map<元素在二叉树中的序号,元素值> nodes ,从上到下从左到右:
        // 例如:(数字为序号)
        //        1
        //       / \
        //      2   3
        //     / \   \
        //    4   5   7
        //           /
        //          14
        std::map<int, T> nodes{};
        if (node == this->root) {
            nodes.insert({1, node->val}); // 根节点的序号为1
            this->markNodes(node, nodes);
        }

        int len = nodes.size();
        // width = 二叉树宽度*2
        int width = static_cast<int>(pow(2, this->depth + 1));

        std::cout << "//" << std::string(width, '-') << "BinaryTree" << std::string(width, '-') << "//" << std::endl;

        // 分层遍历 二叉树 并输出节点值
        for (int i = 0; i <= this->depth; i++) {
            std::map<int, T> lineMap;
            // 遍历nodes,获取第i层的所有节点数据
            for (auto it = nodes.begin(); it != nodes.end(); ++it) {
                // 使用log2(节点编号)获取当前节点的层数
                if (static_cast<int>(log2(it->first)) == i) {
                    lineMap.insert({it->first, it->second});
                }
            }

            // 打印第i层数据
            // 第0层数据居中
            // 计算每层的元素平均间距int step = width / pow(2,i+1);
            // 以及每个元素在当前层的偏移量offset = pair.first - pow(2,i);
            int preElemPos = static_cast<int>(pow(2, i)); // 每行第一个元素的位置编号
            int x = 0;
            for (auto &pair: lineMap) {
                int offset = pair.first - preElemPos;
                int step = width / static_cast<int>(pow(2, i + 1));
                std::cout << std::string(offset * 2 * step, ' ');
                if (x == 0) {
                    std::cout << std::string(step, ' '); // 每行第一个元素的(控制台输出)位置
                }

                std::cout << pair.second; // 当前节点元素值
                x++;
                preElemPos = pair.first;
            }
            lineMap.clear();
            std::cout << std::endl; // 换行
        }
        std::cout << "//" << std::string(width, '-') << "BinaryTree" << std::string(width, '-') << "//" << std::endl;
    }

    /**
     * 先序遍历二叉树,记录{nodes}信息,并计算二叉树深度{this.depth}
     * root的序号{nodeNo}为1,左子树序号为2*{nodeNo},右子树的序号为2*root.{nodeNo} + 1,以此类推
     * @param node
     * @param nodes
     * @param nodeNo
     */
    void markNodes(TreeNode<T> *node, std::map<T, int> &nodes, int nodeNo = 1) {
        if (node == nullptr) {
            return;
        }

        if (node->left != nullptr) {
            nodes.insert({nodeNo * 2, node->left->val});
            this->depth = std::max(this->depth, static_cast<int>(log2(nodeNo * 2)));
            this->markNodes(node->left, nodes, nodeNo * 2);
        }
        if (node->right != nullptr) {
            nodes.insert({nodeNo * 2 + 1, node->right->val});
            this->depth = std::max(this->depth, static_cast<int>(log2(nodeNo * 2 + 1)));
            this->markNodes(node->right, nodes, nodeNo * 2 + 1);
        }
    }

    /**
     * 从左子树中选择一个叶子节点作为新的根节点
     * @param node
     * @return
     */
    TreeNode<T> *findLeafNodeFromLeft(TreeNode<T> *node) {
        while (node->left != nullptr || node->right != nullptr) {
            if(node->left != nullptr){
                node = node->left;
            }else if(node->right != nullptr){
                node = node->right;
            }
        }

        return node;
    }

    TreeNode<T> *deleteNode(TreeNode<T> *node, T val) {
        if (node == nullptr) return node;

        if(node->val == val) {
            // 左子树为空
            if (node->left == nullptr) {
                TreeNode<T> *temp = node->right;
                delete node;
                return temp;
            } else if (node->right == nullptr) { // 右子树为空
                TreeNode<T> *temp = node->left;
                delete node;
                return temp;
            }
            // 左右子树均不为空
            TreeNode<T> *tempNode = this->findLeafNodeFromLeft(node);
            node->val = tempNode->val;
            node->left = this->deleteNode(node->left,tempNode->val);
        }else{
            node->left = this->deleteNode(node->left, val);
            node->right = this->deleteNode(node->right, val);
        }

        return node;
    }

    TreeNode<T> *root = nullptr;
    int depth{}; // 二叉树 深度

};

#endif //TEST_RANDOM_TREE_H

        实现分析

        插入和删除

        这段代码定义了一个模板类 `RandomTree`,使用泛型 `T` 表示节点的值类型。

        代码实现了一个随机二叉树(Random Tree),包括插入和删除节点的操作。这里的树既不是二叉搜索树也不是平衡树他只是一个普通的二叉树,每个值都是随即插入到左子树或右子树中的.支持先/中/后序遍历.

        insert 函数用于插入一个新节点。如果树为空,即根节点为空,就创建一个新节点作为根节点。如果树不为空,就调用 `insertNode` 函数来插入节点。

        insertNode 函数用于插入一个新节点到指定的节点下。首先通过调用 `genRandomValue()` 函数生成一个随机值,然后根据该值判断插入到左子树还是右子树。如果左(右)子树为空,就在这个位置插入新节点;如果不为空,则递归调用自身继续查找插入位置。

        findLeafNodeFromLeft 函数用于找到从左侧开始的最深的叶子节点。该函数在删除节点时用到。

        deleteNode 函数用于删除指定值的节点。

        首先判断当前节点是否为空,如果为空就直接返回。

        然后判断当前节点的值是否等于要删除的值。如果是,则根据不同情况进行不同处理:如果要删除的节点没有左子节点,就将右子节点替换当前节点,并释放当前节点;

        如果要删除的节点没有右子节点,就将左子节点替换当前节点,并释放当前节点;

        如果要删除的节点既有左子节点又有右子节点,就找到左子树中最深的叶子节点,将其值赋给当前节点,然后递归删除该叶子节点。如果当前节点的值不等于要删除的值,就递归调用自身分别在左子树和右子树中删除指定值的节点。

        

        先/中/后序遍历

   preorderTraversal 函数实现了先序遍历二叉树,即先访问根节点,然后递归访问左子树和右子树。当访问到空节点时,直接返回。

   inorderTraversal 函数实现了中序遍历二叉树,即先递归访问左子树,然后访问根节点,最后递归访问右子树。当访问到空节点时,直接返回。

   postorderTraversal 函数实现了后序遍历二叉树,即先递归访问左子树,然后递归访问右子树,最后访问根节点。当访问到空节点时,直接返回。

        分层打印二叉树

        printTree 函数分层打印二叉树的功能,具体实现步骤如下:

        1. 如果传入的二叉树节点为空,则直接返回;
        2. 如果传入的二叉树节点为根节点,则将其标记为序号为1,并调用markNodes()函数,对二叉树的每个节点进行标号,并将节点的数据存入map中,从左到右从上到下依次编号;
        3. 计算二叉树打印宽度width,其中宽度为2^(depth+1);
        4. 使用分层遍历的方式,逐行输出二叉树的节点值,对于每一行,先将该行的节点数据存入临时的map lineMap 中;
        5. 根据lineMap中各节点的位置,输出该行节点值,并控制各节点的空格数量,使输出的树形结构更加整齐;
        6. 最后输出一行“//”与“BinaryTree”间有width个“-”的字符以装饰。

其中,markNodes()函数使用递归实现,给每个节点标号,并更新树的深度。各参数含义如下:

        1. node:当前节点;
        2. nodes:存储节点编号和对应数据的map;
        3. nodeNo:当前节点的编号,默认为1。

        printTree 函数中没有考虑元素自身打印时所占宽度.如果上下两行元素个数或元素位数相差较大,可能无法正确打印二叉树结构.

整个过程中使用了STL的map容器,对于简单的二叉树数据结构而言,复杂度并不高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

who_am_i__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值