浅谈【数据结构】树与二叉树二

目录

1、二叉排序树

1.1二叉树排序树插入

1.1.1两种插入方法

1.1.2循环法

1.1.3递归法

1.2二叉树的打印

1.3二叉树的结点删除

1.4销毁二叉树

1.5层次打印


谢谢帅气美丽且优秀的你看完我的文章还要点赞、收藏加关注

没错,说的就是你,不用再怀疑!!!

希望我的文章内容能对你有帮助,一起努力吧!!!


1、二叉排序树

二叉树排序树:以某个结点为准,该结点的左子树内所有的结点都会比该结点要小。该结点的右子树内 的所有结点都会大于该结点。

1.1二叉树排序树插入

  • 插入结点
    • 树为空的时候
      • 把结点作为根结点插入
    • 树不为空时候
      • 和根结点比较
        • 如果比根结点小,则判断根结点左子结点是否存在
          • 如果左子结点存在的话,将根结点的左子结点作为根结点继续比较
          • 如果不存在,直接将新数据作为该根结点的左子结点插入
        • 如果比根结点大,则判断根结点右子结点是否存在
          • 如果右子结点存在的话,将根结点的右子结点作为根结点继续比较
          • 如果不存在,直接将新数据作为该根结点的右子结点插入
      • 插入新节点无法就是重复上述步骤

1.1.1两种插入方法

1.1.2循环法

  • 逻辑
    • 如果根结点为空,那么新结点直接作为根结点返回
    • 如果不为空
      • 通过前后指针,循环移动来找到待插入的位置
      • 循环结束之后,待插入的位置
        • 父结点指针的下一级
          • 需要通过再对父结点数据比较一次,来确定插入方向(左右)

1.1.3递归法

1.2二叉树的打印

  • 先序访问
    • 先访问根结点,再访问左子树,最后访问右子树(根左右)
  • 中序访问
    • 先访问左子树,再访问根结点,最后访问右子树(左根右)
  • 后序访问
    • 先访问左子树,再访问右子树,最后访问根结点(左右根)

示例代码:

cpp 复制代码
#include <iostream>


// 树结点类型
typedef struct treenode
{
    int value;
    struct treenode *lchild;
    struct treenode *rchild;
}Tree;

/*
    @brief 增加新的结点进入二叉树中(循环法)
    @param tree 需要增加结点的树的根结点指针
    @param data 需要增加进树的新结点数据
    @return 返回树的根结点
*/
Tree *addNewNodeLoop(Tree *tree,int data)
{
    // 如果树为空作为根结点插入
    if(tree == nullptr)
    {
        Tree *node_ptr = new Tree;
        node_ptr->value = data;
        node_ptr->lchild = nullptr;
        node_ptr->rchild = nullptr;
        return node_ptr;   
    }

    // 如果树不为空的时候,需要和根结点进行比较
    Tree *father = nullptr; // 父结点
    Tree *current_node = tree; // 当前结点

    while(current_node)
    {
        father = current_node; // 将当前结点作为下一个结点的父结点
        // 当前结点的数据大于data,需要往左查
        if(current_node->value > data)
            current_node = current_node->lchild; // 将当前结点的左子结点作为新的根结点来继续下次比较
        // 当前结点的数据小于data,需要往右查
        else if(current_node->value < data)
            current_node = current_node->rchild; // 将当前结点的右子结点作为新的根结点来继续下次比较
        else
            return tree; // 不能存在相等情况,所以直接返回
    }

    // 出来循环:找到了插入的位置
    current_node = new Tree;
    current_node->value = data;
    current_node->lchild = nullptr;
    current_node->rchild = nullptr;

    // 确定是左插还是右插
    if(father->value > data)
        // 左插
        father->lchild = current_node;
    else
        // 右插
        father->rchild = current_node;

    return tree;
}

/*
    @brief 增加新的结点进入二叉树中(递归法)
    @param tree 需要增加结点的树的根结点指针
    @param data 需要增加进树的新结点数据
    @return 返回树的根结点
*/
Tree *addNewNode(Tree *tree,int data)
{
    // 如果树为空作为根结点插入
    if(tree == nullptr)
    {
        Tree *node_ptr = new Tree;
        node_ptr->value = data;
        node_ptr->lchild = nullptr;
        node_ptr->rchild = nullptr;
        return node_ptr;   
    }

    // 如果树不为空的时候,需要和根结点进行比较
    if(tree->value > data)
        // 左插
        tree->lchild = addNewNode(tree->lchild,data);
    else if(tree->value < data)
        // 右插
        tree->rchild = addNewNode(tree->rchild,data);
    return tree;
}

/*
    @brief 创建一棵二叉排序树
    @return 成功返回创建好的树的首地址
*/
Tree *createNewTree()
{
    // 一棵空树
    Tree *tree = nullptr;

    while(1)
    {
        int data = -1;
        std::cin >> data;
        if(data == -1)
            break;

        // 插入到树中
        tree = addNewNode(tree,data);
    }

    // 返回创建好的树
    return  tree;
}

/*
    @brief 先序遍历二叉树的结点 根左右
    @param tree 需要先序遍历的二叉树根结点指针
*/
void frontPrintTree(Tree *tree)
{
    // 判断一下根结点是否为空
    if(tree == nullptr)
        return;
    
    // 把传入的结点直接作为根结点使用

    // 打印根结点
    std::cout << tree->value;

    // 打印左子树:这里的tree->lchild其实就是左子树的根
    frontPrintTree(tree->lchild);

    // 打印右子树:这里的tree->rchild其实就是右子树的根
    frontPrintTree(tree->rchild);
}

/*
    @brief 中序遍历二叉树的结点 左根右
    @param tree 需要先序遍历的二叉树根结点指针
*/
void middlePrintTree(Tree *tree)
{
    // 判断一下根结点是否为空
    if(tree == nullptr)
        return;
    
    // 把传入的结点直接作为根结点使用

    // 打印左子树:这里的tree->lchild其实就是左子树的根
    middlePrintTree(tree->lchild);

    // 打印根结点
    std::cout << tree->value;

    // 打印右子树:这里的tree->rchild其实就是右子树的根
    middlePrintTree(tree->rchild);
}

/*
    @brief 后序遍历二叉树的结点 左右根
    @param tree 需要先序遍历的二叉树根结点指针
*/
void backPrintTree(Tree *tree)
{
    // 判断一下根结点是否为空
    if(tree == nullptr)
        return;
    
    // 把传入的结点直接作为根结点使用

    // 打印左子树:这里的tree->lchild其实就是左子树的根
    backPrintTree(tree->lchild);

    // 打印右子树:这里的tree->rchild其实就是右子树的根
    backPrintTree(tree->rchild);

    // 打印根结点
    std::cout << tree->value;
}

int main()
{
    // 创建一棵树
    Tree * tree = createNewTree();

    //  先序后序中序的打印
    frontPrintTree(tree);
    std::cout << std::endl;

    middlePrintTree(tree);
    std::cout << std::endl;

    backPrintTree(tree);
    std::cout << std::endl;
    return 0;
}

1.3二叉树的结点删除

  • 删除一个结点
    • 第一种情况:被删除的结点是叶子结点,直接删除
      • 释放叶子结点空间
      • 返回空指针
    • 第二种情况:被删除结点只有左子树,将左子树中最大的结点替换为需要删除的结点
      • 如果左子树中的第一个结点为最大结点(表示该左子树是没有右子树的)
        • 将被删除结点的 lchild 指向最大的那个结点的 lchild
        • 释放左子树中的第一个结点的空间
        • 返回被删除节点
      • 如果左子树中第一个节点不为最大结点(表示该左子树是有右子树)
        • 将最大结点的父结点的 rchild 指向最大结点的 lchild
        • 释放左子树中最大结点的空间
        • 返回被删除结点
    • 第三种情况:被删除的结点只有右子树,将右子树中的最小结点替换为需要删除的结点
      • 如果右子树的第一个结点不为最小结点
        • 将最小结点的父结点的 lchild 指向最小结点的 rchild
        • 释放最小结点
        • 返回被删除结点
      • 如果右子树的第一个结点为最小结点
        • 将被删除结点的 rchild 指向最小结点的 rchild
        • 释放最小结点
        • 返回被删除结点
    • 第四种情况:被删除结点即存在左子树也存在右子树
      • 可以在右子树中找最小的结点替换,操作和第三种情况方式一样
      • 可以在左子树中找最大的结点替换,操作和第二种情况方式一样

1.4销毁二叉树

1.5层次打印

  • 1、先入队根结点
  • 2、出队结点
    • 判断当前出队的结点左子树是否存在
      • 如果存在将左子树入队
    • 判断当前出队的结点右子树是否存在
      • 如果存在右子树入队
  • 3、打印结点数据
  • 重复以上步骤直到队列为空

***二叉树排序树示例代码***

cpp 复制代码
#include <iostream>

#include <queue>

using std::queue;

template <typename T>
class sort_tree
{
private:
    typedef struct tree_node
    {
        T data;
        struct tree_node *lchild;
        struct tree_node *rchild;
    }TreeNode;
    
    TreeNode *root;

public:
    // 默认构造
    sort_tree()
    : root(nullptr)
    {}

    // 拷贝构造
    sort_tree(const sort_tree &object)
    {
        
    }

    // 带参构造
    sort_tree(std::initializer_list<T> list) 
        : root(nullptr) // root 如果不置空,那么就是一个野指针
    {
        // 遍历list列表
        for(auto var : list)
            add(var); // 通过add来增加结点进树
    }
    
    // 先序打印
    void frontPrint(TreeNode *tn = nullptr)
    {
        TreeNode *tree_node =nullptr;
        if(tn == nullptr)
            tree_node = root;
        else 
            tree_node = tn;



        if(tree_node == nullptr)
            return;
        std::cout << tree_node->data << " ";
        if(tree_node->lchild)
            frontPrint(tree_node->lchild);
        if(tree_node->rchild)
            frontPrint(tree_node->rchild);
    }

    // 层次打印
    void levelPrint()
    {
        if(root == nullptr)
            return;

        queue<TreeNode *> q;

        // 先入根结点
        q.push(root);

        while(!q.empty())
        {
            // 获取队头元素
            TreeNode *node_ptr = q.front();

            // 出队元素
            q.pop();

            // 打印元素
            std::cout << node_ptr->data << " ";

            // 判断左右子树是否存在
            if(node_ptr->lchild)
                q.push(node_ptr->lchild);
            
            if(node_ptr->rchild)
                q.push(node_ptr->rchild);
        }
        std::cout << std::endl;
    }

    // 销毁删除
    TreeNode *destory(TreeNode *tn = nullptr)
    {
        TreeNode *tree_node = nullptr;
        if(tn == nullptr)
            tree_node = root;
        else
            tree_node = tn;

        if(tree_node->lchild)
            // 通过回溯去更新子树指针指向
            tree_node->lchild = destory(tree_node->lchild);

        if(tree_node->rchild)
            // 通过回溯去更新子树指针指向
            tree_node->rchild = destory(tree_node->rchild);

        // 根就是需要删除
        std::cout << "我被删除了" << tree_node->data << std::endl;
        delete tree_node;
        return nullptr;
    }

    // 析构/销毁
    ~sort_tree()
    {
        // 从叶子结点开始删除
        // 左右根进行遍历销毁
        destory();
    }

    // 增加结点
    bool add(T &data)
    {
        // 判断是不是空树
        if(root == nullptr)
        {
            root = new TreeNode;
            root->data = data;
            root->lchild = nullptr;
            root->rchild = nullptr;
            return true;
        }

        // 如果不为空,查找挂载的位置
        TreeNode *father_node = root;
        do
        {
            if(father_node->data > data)
            {
                // 判断左子结点存不存在
                // 如果存在将左子结点设为新的父结点
                if(father_node->lchild)
                    father_node = father_node->lchild;
                else // 如果不存在就挂载到father结点的左边
                {
                    father_node->lchild = new TreeNode;
                    father_node->lchild->data = data;
                    father_node->lchild->lchild = nullptr;
                    father_node->lchild->rchild = nullptr;
                    return true;
                }
            }
            else if(father_node->data < data)
            {
                // 判断右子结点存不存在
                // 如果存在将右子结点设为新的父结点
                if(father_node->rchild)
                    father_node = father_node->rchild;
                else // 如果不存在就挂载到father结点的右边
                {
                    father_node->rchild = new TreeNode;
                    father_node->rchild->data = data;
                    father_node->rchild->lchild = nullptr;
                    father_node->rchild->rchild = nullptr;
                    return true;
                }
            }
            else
                return false;
        } while (1);
    }

    // 删除结点
    bool del(T &data)
    {
        // 是不是空树
        if(root == nullptr)
            return false;
        
        // 不是空树
        
        // 找需要删除的结点
        TreeNode *father_ptr  = nullptr;
        TreeNode *delNode_ptr = root;
        do
        {
            // 如果小于当前结点,那么说明被删除的结点在树的左边
            if(delNode_ptr->data > data)
            { 
                // 更新父结点指针
                father_ptr = delNode_ptr;
                delNode_ptr = delNode_ptr->lchild;
            }
             // 如果大于当前结点,那么说明被删除的结点在树的右边
            else if(delNode_ptr->data < data)
            {
                // 更新父结点指针
                father_ptr = delNode_ptr;
                delNode_ptr = delNode_ptr->rchild;
            }
            else 
                break; // 找到了需要删除的结点
            
            // 为空表示树中没有找到删除的结点
            if(delNode_ptr == nullptr)
                return false;

        } while (1);
        
        // 能来到这位置。说明找到了需要删除的结点

        // 第一种情况:叶子结点
        if(!delNode_ptr->lchild&&!delNode_ptr->rchild)
        {
            // 改变父结点中子树指针的指向。避免父结点中的子树指针成为野指针
            if(father_ptr->data > data)
                father_ptr->lchild = nullptr;
            else if(father_ptr->data < data)
                father_ptr->rchild = nullptr;

            // 删除结点,释放空间
            delete delNode_ptr;
            delNode_ptr = nullptr;
            return true;
        }

        // 存在左子树:包含了第四种情况
        else if(delNode_ptr->lchild)
        {
            // 找左子树中最大的结点
            TreeNode *maxFather_ptr = nullptr;
            TreeNode *maxNode_ptr = delNode_ptr->lchild;

            // 一路向右
            while(maxNode_ptr->rchild)
            {
                maxFather_ptr = maxNode_ptr;
                maxNode_ptr = maxNode_ptr->rchild;
            }
            // 第一种情况:被删除结点的lchild指向左子树种最大结点的lchild
            if(maxNode_ptr == delNode_ptr->lchild)
                delNode_ptr->lchild = maxNode_ptr->lchild;
            else
                maxFather_ptr->rchild = maxNode_ptr->lchild;

            // 值交换
            std::swap(delNode_ptr->data,maxNode_ptr->data);

            // 删除左子树种最大结点
            maxNode_ptr->lchild = nullptr;
            delete maxNode_ptr;
            maxNode_ptr = nullptr;
            return true;
        }
        
        // 只有右子树不为空
        else
        {
            // 找右子树中最小的结点
            TreeNode *minFather_ptr = nullptr;
            TreeNode *minNode_ptr = delNode_ptr->rchild;

            // 一路向左
            while(minNode_ptr->lchild)
            {
                minFather_ptr = minNode_ptr;
                minNode_ptr = minNode_ptr->lchild;
            }
            // 第一种情况:被删除结点的rchild指向右子树种最小结点的rchild
            if(minNode_ptr == delNode_ptr->rchild)
                delNode_ptr->rchild = minNode_ptr->rchild;
            else
                minFather_ptr->lchild = minNode_ptr->rchild;

            // 值交换
            std::swap(delNode_ptr->data,minNode_ptr->data);

            // 删除右子树种最小结点
            minNode_ptr->rchild = nullptr;
            delete minNode_ptr;
            minNode_ptr = nullptr;
            return true;
        }
    }
};



int main()
{
    sort_tree<int> tree({3,1,5,6,4,2,8,9,7});

    // do
    // {
    //     int data = -1;
    //     std::cin >> data;
    //     if(data == -1)
    //         break;

    //     tree.add(data);
    // } while (1);
    
    tree.levelPrint();
    return 0;
}
相关推荐
地平线开发者7 小时前
profiler debug 工具用法与高一致性策略
算法·自动驾驶
编程大师哥7 小时前
匿名函数 lambda + 高阶函数
java·python·算法
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
我叫袁小陌7 小时前
算法解题思路指南
算法
MC皮蛋侠客7 小时前
C++17 多线程系列(五):C++17 并行算法——从串行到并行的零成本迁移
c++·多线程
地平线开发者7 小时前
Conv+BN+Add+ReLU 融合机制简介
算法·自动驾驶