数据结构 之 二叉树功能的代码实现

news2024/12/24 9:33:21

文章目录

  • 二叉搜索树
    • 搜索
    • 删除节点
    • 二叉树的遍历
  • 代码实现

二叉搜索树

  • 无序树的查找,就是整个遍历,所以时间复杂度为O(N)。
  • 为了优化无序查找的时间复杂度,我们把树进行排序,这里引入了二叉搜索树。

    二叉搜索树是一个有序树,即左节点的值比根节点的值小,右节点的值比根节点的值大

  • 二叉搜索树的时间复杂度为O(logN)

    因为每次查找都会减少剩余数据一半的数据量

搜索

  • 整体思路

    1、定义一个TreeNode,其中包含该节点的value值,以及左右孩子
    2、如果 搜索值 = 节点值,返回;
    3、如果 搜索值 > 节点值,在右孩子处查找
    4、如果 搜索值 < 节点值,在左孩子处查找

  • 代码截图(完整代码在【代码实现】模块可以查看到)
    在这里插入图片描述
    在这里插入图片描述

删除节点

  • 思路:
    • 1、找到目标节点

    • 2、找到目标节点的父节点

    • 3、删除的节点是叶子节点

      (1)父节点为空
      (2)父节点不为空:判断目标节点是左孩子还是右孩子,
      (3)找到目标节点,将赋值为null
      在这里插入图片描述

    • 4、删除的节点有两个子树

      (1)找到目标节点左子树的最大值(或 右子树的最小值)
      (2)需要先删除最大值,然后将最大值赋值给目标节点赋值:如果不先删除,赋值成功后会有两个最大值
      在这里插入图片描述

    • 5、删除的节点只有一个子树

      (1)删除的节点是根节点,且只有左子树:直接将左子树赋给根节点
      (2)删除的节点是根节点,且只有右子树:直接将右子树赋给根节点
      (3)删除的节点为父节点的左子树,且节点只有左子树:节点的左子树赋值给父节点的左子树
      (4)删除的节点为父节点的左子树,且节点只有右子树:节点的右子树赋值给父节点的左子树
      (5)删除的节点为父节点的右子树,且节点只有左子树:节点的左子树赋值给父节点的右子树
      (6)删除的节点为父节点的右子树,且节点只有右子树:节点的右子树赋值给父节点的右子树
      在这里插入图片描述
      在这里插入图片描述

二叉树的遍历

  • 前序遍历:根 - 左 - 右,借助递归方法传入左节点或右节点

  • 中序遍历:左 - 根 - 右,借助递归方法传入左节点或右节点

  • 后续遍历:左 - 右 - 根,借助递归方法传入左节点或右节点

  • 层序(广度)遍历:借助队列,根节点出队时,将左右孩子入队的思想

  • 在这里插入图片描述

代码实现

  • TreeNode类
    public class TreeNode {
        private int value; 		// 节点的值
        private TreeNode left;	// 左节点
        private TreeNode right; // 右节点
        // 构造函数
        public TreeNode(int value) {this.value = value;}
        // get 和 set 方法
        public int getValue() {return value;}
        public void setValue(int value) {this.value = value;}
        public TreeNode getLeft() {return left;}
        public void setLeft(TreeNode left) {this.left = left;}
        public TreeNode getRight() {return right;}
        public void setRight(TreeNode right) {this.right = right;}
    }
    
  • BinaryTree类:搜索、删除、遍历于一体
    import java.util.LinkedList;
    import java.util.Queue;
    
    public class BinaryTree {
    
        public TreeNode root;       // 定义一个二叉树的根节点
        /***
         * 普通插入法:
         * 1、根节点为空,直接赋值,直接返回;
         * 2、根节点不为空,开始遍历,
         */
        public void insert(int value){
            // 创建一个新的节点
            TreeNode newNode = new TreeNode(value);
            // 先来判断root节点是不是为空
            if(root==null){     // root为空,直接赋值,直接返回
                root = newNode;
                return;
            }
            // root节点不为空,所以需要开始遍历了
            TreeNode index = root;
            // 定义个父节点
            TreeNode pre = null;
            while(true){
                pre = index;
                if(value>=index.getValue()){    //  如果插入的值比较大,就一直往右走
                    index = index.getRight();
                    if(index == null){
                        pre.setRight(newNode);
                        return;
                    }
                }else {
                    index = index.getLeft();   //  如果插入的值比较大,就一直往右走
                    if(index==null){
                        pre.setLeft(newNode);
                        return;
                    }
                }
            }
        }
    
        // 【递归插入法】
        public void insertDG(TreeNode node,int value){
            TreeNode newNode = new TreeNode(value);
            if(root==null){
                root = newNode;
                return;
            }
            // root不为空
            if(node.getValue()>=value){
                // 判断当前节点的左节点是否为空
                if(node.getLeft()==null){
                    node.setLeft(newNode);
                    return;
                }
                // 左节点不为空,需要递归
                insertDG(node.getLeft(),value);
            }else{      // 存入的值大,需要往右边遍历
                // 先判断当前节点的右节点是否为空
                if(node.getRight()==null){
                    node.setRight(newNode);
                    return;
                }
                // 右节点不为空,需要递归
                insertDG(node.getRight(),value);
            }
        }
        /***
         * 【前序遍历:父 - 左 - 右】
         * @param node
         */
        public void beforOrder(TreeNode node){
            // 需要遍历的节点为空,直接返回
            if(node==null){return;}
            // 节点不为空
            System.out.print(node.getValue()+" ");
            // 递归遍历左子树
            beforOrder(node.getLeft());
            // 递归遍历右子树
            beforOrder(node.getRight());
        }
        /***
         * 【中序遍历:左 -  父 - 右】
         * @param node
         */
        public void inOrder(TreeNode node){
            // 递归出口
            if(node == null){return;}
            // 递归遍历左子树
            inOrder(node.getLeft());
            System.out.print(node.getValue()+" ");
            // 递归遍历右子树
            inOrder(node.getRight());
        }
    
        /***
         * 【后序遍历:左 - 右 -父】
         * @param node
         */
        public void afterOrder(TreeNode node){
            // 递归出口
            if(node==null){return;}
            // 递归遍历左子树
            afterOrder(node.getLeft());
            // 递归遍历右子树
            afterOrder(node.getRight());
            System.out.print(node.getValue()+" ");
        }
    
        /***
         * 广度遍历
         * @param node
         */
        public void deepOrder(TreeNode node){
            Queue<TreeNode>  queue = new LinkedList<>();    // 定义一个队列
            TreeNode index = null;  // 记录从队列中取出的节点
            if(node==null){
                System.out.println("树为空");
                return;
            }else{
                queue.offer(node);
                while(!queue.isEmpty()){    // 队列不为空时操作
                    index = queue.poll();
                    System.out.print(index.getValue()+" ");
                    if(index.getLeft()!=null){       // 左节点不为空,入队
                        queue.offer(index.getLeft());
                    }
                    if(index.getRight()!=null){      // 右节点不为空,入队
                        queue.offer(index.getRight());
                    }
                }
            }
        }
    
        /***
         * [普通方法:查找]
         * @param value:需要查找的值
         * @return:TreeNode
         */
        public TreeNode search(int value){
            // 定义一个遍历的节点
            TreeNode index = root;
            // 开始遍历,节点不为空
            while(index!=null){
                if(index.getValue()==value){
                    return index;
                }else if(index.getValue()>value){
                    index = index.getLeft();
                }else{
                    index = index.getRight();
                }
            }
            return null;
        }
    
        /***
         * [递归方法:查找]
         * @param node:查找的节点
         * @param value:查找的值
         * @return:TreeNode节点
         */
        public TreeNode searchDG(TreeNode node,int value){
            // 递归出口
            if(node==null){return  null; }
            // 开始递归
            if(node.getValue() == value){
                return node;
            }else if(node.getValue()>value){
                return  searchDG(node.getLeft(),value);
            }else {
                return searchDG(node.getLeft(),value);
            }
        }
    
    
        // 查找父节点
        public TreeNode searchParent(TreeNode node,int value){
            // 递归出口
            if(node==null){
                return null;
            }
            // 判断当前节点是不是目标节点的父节点
            // 是父节点的条件:左节点不为空且等于value  或者 右节点不为空,且等于value
            if((node.getLeft()!=null && node.getLeft().getValue()==value) || (node.getRight()!=null && node.getRight().getValue()==value)){
                return node;
            }else if(node.getValue()>value){
                // 当前
                return searchParent(node.getLeft(),value);
            }else{
                return searchParent(node.getRight(),value);
            }
        }
    
        // 删除节点
        public void delete(int value){
            // 如果跟节点为空,直接返回
            if(root == null){
                System.out.println("空树,无法删除");
                return ;
            }
            // 根节点不为空:
            // 找到目标节点
            TreeNode target = search(value);
            if(target==null){
                System.out.println("不存在的节点");
                return;
            }
            // 找到父节点
            TreeNode parent = searchParent(root,value);
    
            if(target.getLeft()==null && target.getRight()==null){
                // 删除叶子节点
    
                // 1、如果父节点为空,那么这个节点就是根节点
                if(parent == null){
                    root = null;
                    return;
                }
                // 2、父节点不为空,那么需要判断目标节点是左节点 还是 右节点了
    
                if(parent.getLeft()!=null && parent.getLeft()==target){      // (1)目标节点是父节点的左孩子
                    parent.setLeft(null);
                }else{       // (2)目标节点是父节点的右孩子
                    parent.setRight(null);
                }
            }else if(target.getLeft()!=null && target.getLeft()!=null){
                // 删除有两个子树的节点
    
                // 找到目标节点左子树的最大值
                TreeNode index = target.getLeft();
                while(index.getRight()!=null){
                    index = index.getRight();
                }
                int Max = index.getValue();
                // 要先删除后赋值
                delete(Max);
                target.setValue(Max);
    
            }else{
                // 删除只有一个子树的节点
    
                //没有父节点,即是根节点
                if(parent==null){
                    // 判断是左子树还是右子树
                    if(target.getLeft()!=null){
                        // 根节点,只有左子树
                        root = target.getLeft();
                    }else{
                        // 根节点,只有右子树
                        root = target.getRight();
                    }
                    return;
                }
                // 有父节点,即非根节点
                if(parent.getLeft()!=null && parent.getLeft().getValue()==value){
                    // 删除节点是父节点的左子树。判断要删除的节点的子节点是左子树还是右子树
    
                    if(target.getLeft()!=null){
                        // 要删除的节点的唯一节点只有左子树
                        parent.setLeft(target.getLeft());
                    }else{
                        // 要删除的节点的唯一节点只有右子树
                        parent.setLeft(target.getRight());
                    }
                }else{
                    // 删除节点是父节点的右子树
    
                    if(target.getLeft()!=null){
                        // 要删除的节点的唯一节点只有左子树
                        parent.setRight(target.getLeft());
                    }else{
                        // 要删除的节点的唯一节点只有右子树
                        parent.setRight(target.getRight());
                    }
                }
            }
        }
    
    }
    
  • Test类
    public class Test {
        public static void main(String[] args) {
            BinaryTree forest = new BinaryTree();
            forest.insert(10);
            forest.insertDG(forest.root,15);
            forest.insert(7);
            forest.insertDG(forest.root,3);
            forest.insert(20);
            forest.insertDG(forest.root,12);
            forest.insert(9);
            forest.insertDG(forest.root,25);
            forest.insert(17);
            forest.insertDG(forest.root,2);
            forest.delete(10);
            System.out.println("前序遍历:");
            forest.beforOrder(forest.root);
            System.out.println("\n广度遍历:");
            forest.deepOrder(forest.root);
        }
    }
    

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

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

相关文章

vue el-select下拉框在弹框里面错位,

1&#xff1a;原因是因为 底层滚动条滚动的问题。 2&#xff1a;解决方案 加上这个属性 :popper-append-to-body"false" 和样式 <el-select:popper-append-to-body"false"> </el-select><style> .el-select .el-select-dropdown {t…

数据埋点系列 4|数据分析与可视化:从数据到洞察

在前面的文章中,我们讨论了数据埋点的基础知识、技术实现以及数据质量保证。现在,我们拥有了高质量的数据,是时候深入挖掘这些数据的价值了。本文将带你探索如何通过数据分析和可视化,将原始数据转化为有价值的业务洞察。 目录 1. 数据分析基础1.1 描述性统计1.2 推断统计1.3 相…

Haproxy的配置详解与使用

一、haproxy简介 HAProxy是一个使用C语言编写的自由及开放源代码软件&#xff0c;其提供高可用性、负载均衡&#xff0c;以及基于TCP和HTTP的应用程序代理。 HAProxy特别适用于那些负载特大的web站点&#xff0c;这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬…

uniapp实现自定义弹窗组件,支持富文本传入内容

1.首先安装vuex 通过此命令安装 ​​npm install vuex --save​​ 创建initModal.js import Vuex from vuex // 自定义弹窗 export default function initModal (v) {// 挂在store到全局Vue原型上v.prototype.$modalStore new Vuex.Store({state: {show: false,title: 标题,c…

【人工智能】深入理解自监督学习中的表征学习与对比学习

我的主页&#xff1a;2的n次方_ 1. 自监督学习 1.1 自监督学习的概念 自监督学习是一种无需大规模标注数据的学习方法&#xff0c;通过构造代理任务&#xff0c;模型可以从数据本身获取监督信号&#xff0c;从而学习有用的特征表征。 1.2 自监督学习的背景与重要性 在当今大…

【C++进阶学习】第十三弹——C++智能指针的深入解析

前言&#xff1a; 在C编程中&#xff0c;内存管理是至关重要的一个环节。传统的手动内存管理方式容易导致内存泄漏、悬挂指针等问题。为了解决这些问题&#xff0c;C引入了智能指针。本文将详细讲解C中智能指针的概念、种类、使用方法以及注意事项。 目录 一、引言 二、智能指…

链表---数据结构-黑马

链表 定义 链表是数据元素的线性集合&#xff0c;其每个元素都指向下一个元素&#xff0c;元素存储上是不连续的。 分类 单向链表&#xff0c;每个元素只知道自己的下一个元素是谁。 双向链表&#xff0c;每个元素知道自己的上一个元素和下一个元素。 循环链表&#xff0c;…

分布式锁:Mysql实现,Redis实现,Zookeeper实现

目录 前置知识 Mysql实现分布式锁 1.get_lock函数 Java代码实现&#xff1a; 2.for update尾缀 Java代码实现&#xff1a; 3.自己定义锁表 Java代码实现&#xff1a; 4.时间戳列实现乐观锁 Java代码实现&#xff1a; Redis实现分布式锁 Zookeeper实现分布式锁&#…

Oracle搭建一主两备dataguard环境的详细步骤

​ 上一篇文章介绍了Oracle一主两备的DG环境&#xff0c;如何进行switchover切换&#xff0c;也许你会问Oracle一主两备dataguard环境要怎么搭建&#xff0c;本篇文章将为你讲述一主两备dataguard详细搭建步骤。 环境说明 主机名IP地址db_unique_name数据库角色ora11g10.10.1…

驱动数智化升级,AI大模型准备好了吗?

大数据产业创新服务媒体 ——聚焦数据 改变商业 AI大模型的快速崛起&#xff0c;为企业带来了前所未有的变革机遇。从自然语言处理到图像识别&#xff0c;从精准营销到智能制造&#xff0c;AI大模型正逐步渗透到各行各业的核心业务中。然而&#xff0c;随着技术的不断演进&…

力扣刷题-循环队列

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 思路&#xff1a; 我们在这里采用的是用数组的形式实现循环链表&#xff0c;我认为这个用数组是更为简单的&#xff0c;我们只需要控制下标就可以实现循环链表的效果。具体实现代…

Python数据可视化案例——折线图

目录 json介绍&#xff1a; Pyecharts介绍 安装pyecharts包 构建一个基础的折线图 配置全局配置项 综合案例&#xff1a; 使用工具对数据进行查看 &#xff1a; 数据处理 json介绍&#xff1a; json是一种轻量级的数据交互格式&#xff0c;采用完全独立于编程语言的文…

2024 该学前端还是学后端?

2024 该学前端还是学后端&#xff1f; 现状分析pragmatic-drag-and-drop后端开发 现状分析 对于这个问题&#xff0c;个人作为Java后端开发者&#xff0c;那么当然是比较熟悉Java后端开发&#xff0c;从这么久的工作体验来说&#xff0c;因为个人也是比较熟悉Java后端&#xf…

【第19章】Spring Cloud之Gateway自定义Logback配置

文章目录 前言一、内置配置1. 关联依赖2. 内置配置 二、自定义配置1. 日志级别2. 彩色日志3. 自定义配置4. 增加打印语句5. 效果展示 总结 前言 网关层作为我们程序的主入口&#xff0c;有着至关重要的作用&#xff0c;下面我们通过自定义Logback配置增强网关层的日志输出&…

【实用工具】Stirling-PDF入门安装教程: 优质开源的PDF处理工具/编辑工具

文章目录 项目简介功能展示Page Operations 页面操作Conversion Operations 转换操作Security & Permissions 安全与权限Other Operations 其他业务 如何安装并使用Docker RunDocker Compose 项目简介 这是一款使用 Docker 的基于本地托管网络的强大 PDF 操作工具。它能让…

2024年翻译工具新风尚:实时翻译与精准度并进

语言交流的障碍随着全球化的不断深入日益成为连接不同文化和国家的挑战。然而&#xff0c;在科技日新月异的今天&#xff0c;类似谷歌翻译这样的工具正在高速发展这。这次我们来一起探讨深受用户喜欢的翻译工具有哪些。 1.福昕在线翻译 链接直达&#xff1a;https://fanyi.pd…

贷齐乐系统最新版SQL注入(绕过WAF可union select跨表查询)

目录 标题&#xff1a;贷齐乐系统最新版SQL注入&#xff08;绕过WAF可union select跨表查询&#xff09; 内容&#xff1a; 一&#xff0c;环境部署 二&#xff0c;源码分析 三&#xff0c;sql注入 总结&#xff1a; [回到顶部]&#xff08;#article_top&#xff09; 一&am…

Linux使用学习笔记1到2 命令行与shell 基础运维命令

在学习使用ubuntu等各种喜他构建服务器的过程中遇到很多问题&#xff0c;意识到只是跟着网络的教程没办法管理好一个完整的应用部署和运行。遂开始学习linux基本知识&#xff0c;以应对服务器常见问题和软件的使用和维护。 shell 望文生义&#xff0c;大概意思是一个外壳&…

交错字符串[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 给定三个字符串s1、s2、s3&#xff0c;请你帮忙验证s3是否是由s1 和s2交错 组成的。 两个字符串s和t交错 的定义与过程如下&#xff0c;其中每个字符串都会被分割成若干 非空 子字符串&#xff1a; s s1 s2 ... sn t t1 t2 …

数据结构---单链表实现

单链表是什么 我的理解是“特殊的数组”&#xff0c;通过访问地址来连接起来 1怎么创建链表 ----通过结构体&#xff08;成员有存入数据的data和指向下一个节点的地址的指针&#xff08;结构体指针&#xff09;next 初始架构---DataType 对应存入数据类型&#xff0c;此处的N…