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

news2024/11/15 4:35:37

目录

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二叉树的打印

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

示例代码:

#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、打印结点数据
  • 重复以上步骤直到队列为空

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

#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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2074341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端实现 http请求中 表单请求体和json请求体的互相转换,外加转为 冒号换行格式,用于ApiFox批量导入

在线体验&#xff1a;https://ikaros-521.github.io/dev_tool/http/param_json_converter.html 直接上源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Query String to JSON Converter</titl…

慢SQL定位及优化

1.如何定位慢查询 方案1&#xff1a;开源工具 调式工具&#xff1a;Arthas 运维工具&#xff1a;Prometheus、Skywalking 方案2&#xff1a;MySQL自带慢日志 慢查询日志记录了所有执行时间超过指定参数&#xff08;long_query_time&#xff0c;单位&#xff1a;秒&#xff0c…

QT error: undefined reference to `vtable for Net‘

报错 C:\Users\Administrator\Desktop\VideoHill\GikISearch\net.cpp:4: error: undefined reference to vtable for Net 以下是两个可能错误原因 1&#xff0c;未定义Q_OBJECT 宏 在头文件中加上 加上#include <QObject>&#xff0c; 改写继承QObject 和定义宏 …

【HarmonyOS】鸿蒙应用蓝牙功能实现 (三)

【HarmonyOS】鸿蒙应用蓝牙功能实现 &#xff08;三&#xff09; 前言 今天整理蓝牙Demo代码&#xff0c;查看官网时发现自己帐号没有登录&#xff0c;竟然也可以访问最新的API文档&#xff0c;真是喜大奔普。看来华为已经开始对外开放最新的API文档&#xff0c;不再有白名单…

Leetcode每日刷题之面试题01.06字符串压缩(C++)

1.题目解析 本题的目的是遍历一个字符串&#xff0c;将重复的字符串以该字符出现次数进行压缩&#xff0c;最终返回压缩后的字符串即可 题目来源&#xff1a;面试题01.06.字符串压缩 2.算法原理 我们从左往右遍历字符串&#xff0c;用 ch 记录当前要压缩的字符&#xff0c;cnt …

交叉编译Qt5.12.8附带编译opengl

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、重要说明二、准备环境1.下载qt5.12.8源代码2.配置系统环境3.安装必要工具4.解压qt5源代码5.开始配置编译6.配置qtcreator 三、编译opengl总结 前言 最近有…

zdppy+vue3+onlyoffice文档管理系统实战 20240825上课笔记 zdppy_cache框架增加resize清理缓存的方法

遗留问题 设置缓存&#xff0c;已完成获取缓存&#xff0c;已实现删除缓存&#xff0c;已实现查询所有key&#xff0c;带查询参数&#xff1a;active只查激活的&#xff0c;value包含value默认只获取key查询缓存大小清空缓存判断是否为管理员 实现删除缓存的接口 async def …

[C语言]一、C语言基础

G:\Cpp\C语言精讲 1. C语言入门 1.1 初识计算机语言 计算机编程语言&#xff0c;就是人与计算机交流的方式。人们可以使用编程语言对计算机下达命令&#xff0c;让计算机完成人们需要的功能。 计算机语言有很多种。如&#xff1a;C 、C、Java、Go、JavaScript、Python&#x…

音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息

通过FFprobe命令可以显示WAV音频文件每个packet&#xff08;也称为数据包或多媒体包&#xff09;的信息&#xff1a; ffprobe -of json -show_packets XXX.wav 输出如下&#xff1a; 其中&#xff1a; 1.codec_type&#xff1a;packet类型&#xff0c;表示该路流是视频还是音…

【CSS】使用 CSS 自定义属性(变量)-- var()

自定义属性&#xff08;有时候也被称作CSS 变量或者级联变量&#xff09;是由 CSS 作者定义的&#xff0c;它包含的值可以在整个文档中重复使用。由自定义属性标记设定值&#xff08;比如&#xff1a; --main-color: black;&#xff09;&#xff0c;由 var() 函数来获取值&…

一台电脑配置两个Git账号(github和gitlab),不同仓库使用不同的git

我们工作时一般都是使用gitlab&#xff0c;工作电脑也一般配置的 git 是连接 gitlab 的&#xff0c;那么当我们如果想用同一个电脑实现不同仓库根据自己的需要到底是推送代码到github还是 gitlab&#xff0c;以及使用哪个账号&#xff0c;(比如如果想用工作电脑维护自己的 gith…

C语言学习,Turbo C 开发环境回顾

&#xff08;1&#xff09;Turbo C 集成开发环境&#xff1a; &#xff08;2&#xff09;按AltF&#xff0c;进入File菜单&#xff1a; &#xff08;3&#xff09;按AltR&#xff0c;进入Run菜单&#xff1a; &#xff08;4&#xff09;按AltC&#xff0c;进入Compile菜单&…

Java 入门指南:Java 泛型(generics)

Java 泛型概要 Java 泛型&#xff08;generics&#xff09; 是 JDK 5 中引入的一个新特性。泛型的本质是参数化类型&#xff0c;也就是所操作的数据类型被指定为一个参数&#xff08;可以称之为类型形参&#xff0c;然后在使用/调用时传入具体的类型。&#xff09; 使用 Java …

【机器学习】特征工程的基本概念以及LASSO回归和主成分分析优化方法

引言 特征工程是机器学习中的一个关键步骤&#xff0c;它涉及到从原始数据中提取和构造新的特征&#xff0c;以提高模型的性能和预测能力LASSO&#xff08;Least Absolute Shrinkage and Selection Operator&#xff09;回归是一种用于回归分析的线性模型&#xff0c;它通过引入…

字节跳动-生活服务-java后端-一面

基础题 计算机网络 1.tcp三次握手和四次挥手&#xff1f;tcp的第三次握手可以传输应用层数据嘛&#xff1f; 4.1 TCP 三次握手与四次挥手面试题 | 小林coding (xiaolincoding.com) 2.描述一下打开百度首页后发生的网络过程&#xff1f; 计算机网络面试题 | 小林coding (xi…

linux-基础知识1

简单命令 init 0 关机 int 6 重启 pwd 查看当前所在目录&#xff0c; cd切换目录 ls 列出目录下的内容 clear 清屏 date 查看时间 路径 linux表示硬件设备的文件在dev目录 /tmp是临时目录&#xff0c;可以创建目录和文件&#xff0c;但不能保证安全 df查看文件系统…

数据仓库系列 1:什么是数据仓库,它与传统数据库有什么不同?

想象一下,你正站在一座巨大的仓库前。这座仓库不是用来存放普通商品的,而是存储着海量的数据 - 这就是数据仓库。在大数据时代,数据仓库已经成为企业数据管理的核心。但它究竟是什么?又为什么如此重要?让我们一起揭开数据仓库的神秘面纱,探索它与我们熟知的传统数据库有何不同…

IDEA2023的激活与安装

前言 开始了java的学习之旅&#xff0c;当然少不了IDEA这个得力的开发工具软件。但是IDEA是付费的&#xff0c;免费版功能有太少&#xff0c;怎么使用上正式版呢&#xff01;当然还是激活啦 第一步&#xff1a;官网下载安装包 安装步骤就不展现了&#xff0c;无脑下一步就可以…

【学习笔记】技术分析-华为智驾控制器MDC Pro 610分析

华为的智能驾驶控制器一直在迭代&#xff0c;和网络上广泛披露的早期MDC 610相比&#xff0c;华为 MDC Pro 610 智能驾驶控制器&#xff0c;现在的样品设计采用了海思的双系统级芯片 (SoC) 提高了处理能力&#xff0c;三星的存储模块为无缝数据处理提供了充足的内存&#xff0c…

高并发业务下的无损技术方案设计

0 前言 秒杀&#xff0c;既有需求真实且迫切的用户&#xff0c;也有试图牟利的黄牛。系统挑战&#xff0c;就是相较于以往千倍万倍的用户规模&#xff0c;可能是真人可能是机器人&#xff0c;在同一瞬间对系统发起冲击&#xff0c;需要海量的计算资源才能支撑。 秒杀系统的设计…