树——二叉搜索树

news2025/2/4 3:45:10

二叉搜索树

概述

  随着计算机算力的提升和对数据结构的深入研究,二叉搜索树也不断被优化和扩展,例如AVL树、红黑树等。

特性

  二叉搜索树(也称二叉排序树)是符合下面特征的二叉树:

  1.   树节点增加 key 属性,用来比较谁大谁小,key 不可以重复;
  2.   对于任意一个树节点,它的 key 比左子树的 key 都大,同时也比右子树的 key 都小。

  查找的时间复杂度与树高相关,插入、删除也是如此。

  注:

  •   二叉搜索树 - 英文 binary search tree,简称 BST
  •   二叉排序树 - 英文 binary ordered tree 或 binary sorted tree

代码

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Binary Search Tree 二叉搜索树
 */
@SuppressWarnings("all")
public class BSTTree1 {

    BSTNode root; // 根节点

    static class BSTNode {
        int key;
        Object value;
        BSTNode left;
        BSTNode right;

        public BSTNode(int key) {
            this.key = key;
        }

        public BSTNode(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public BSTNode(int key, Object value, BSTNode left, BSTNode right) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
        }
    }

    /**
     * <h3>查找关键字对应的值</h3>
     *
     * @param key 关键字
     * @return 关键字对应的值
     */
    public Object get(int key) {
        BSTNode node = root;
        while (node != null) {
            if (key < node.key) {
                node = node.left;
            } else if (node.key < key) {
                node = node.right;
            } else {
                return node.value;
            }
        }
        return null;
    }

    /**
     * <h3>查找最小关键字对应值</h3>
     *
     * @return 关键字对应的值
     */
    public Object min() {
        return min(root);
    }

    private Object min(BSTNode node) {
        if (node == null) {
            return null;
        }
        BSTNode p = node;
        while (p.left != null) {
            p = p.left;
        }
        return p.value;
    }

    /**
     * <h3>查找最大关键字对应值</h3>
     *
     * @return 关键字对应的值
     */
    public Object max() {
        return max(root);
    }

    private Object max(BSTNode node) {
        if (node == null) {
            return null;
        }
        BSTNode p = node;
        while (p.right != null) {
            p = p.right;
        }
        return p.value;
    }

    /**
     * <h3>存储关键字和对应值</h3>
     *
     * @param key   关键字
     * @param value 值
     */
    public void put(int key, Object value) {
        root = doPut(root, key, value);
    }

    private BSTNode doPut(BSTNode node, int key, Object value) {
        if (node == null) {
            return new BSTNode(key, value);
        }
        if (key < node.key) {
            node.left = doPut(node.left, key, value);
        } else if (node.key < key) {
            node.right = doPut(node.right, key, value);
        } else {
            node.value = value;
        }
        return node;
    }

    /**
     * <h3>查找关键字的前任值</h3>
     *
     * @param key 关键字
     * @return 前任值
     */
    public Object predecessor(int key) {
        BSTNode p = root;
        BSTNode ancestorFromLeft = null;
        while (p != null) {
            if (key < p.key) {
                p = p.left;
            } else if (p.key < key) {
                ancestorFromLeft = p;
                p = p.right;
            } else {
                break;
            }
        }
        // 没找到节点
        if (p == null) {
            return null;
        }
        // 找到节点 情况1:节点有左子树,此时前任就是左子树的最大值
        if (p.left != null) {
            return max(p.left);
        }
        // 找到节点 情况2:节点没有左子树,若离它最近的、自左而来的祖先就是前任
        return ancestorFromLeft != null ?
                ancestorFromLeft.value : null;
    }

    /**
     * <h3>查找关键字的后任值</h3>
     *
     * @param key 关键字
     * @return 后任值
     */
    public Object successor(int key) {
        BSTNode p = root;
        BSTNode ancestorFromRight = null;
        while (p != null) {
            if (key < p.key) {
                ancestorFromRight = p;
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            } else {
                break;
            }
        }
        // 没找到节点
        if (p == null) {
            return null;
        }
        // 找到节点 情况1:节点有右子树,此时后任就是右子树的最小值
        if (p.right != null) {
            return min(p.right);
        }
        // 找到节点 情况2:节点没有右子树,若离它最近的、自右而来的祖先就是后任
        return ancestorFromRight != null ?
                ancestorFromRight.value : null;
    }

    /**
     * <h3>根据关键字删除</h3>
     *
     * @param key 关键字
     * @return 被删除关键字对应值
     */

//    public Object remove(int key) {
//        ArrayList<Object> result = new ArrayList<>(); // 保存被删除节点的值
//        root = doRemove(root, key, result);
//        return result.isEmpty() ? null : result.get(0);
//    }
//
//    /*
//              4
//             / \
//            2   6
//           /     \
//          1       7
//
//        node 起点
//        返回值 删剩下的孩子(找到) 或 null(没找到)
//     */
//    private BSTNode doRemove(BSTNode node, int key, ArrayList<Object> result) {
//        if (node == null) {
//            return null;
//        }
//        if (key < node.key) {
//            node.left = doRemove(node.left, key, result);
//            return node;
//        }
//        if (node.key < key) {
//            node.right = doRemove(node.right, key, result);
//            return node;
//        }
//        result.add(node.value);
//        if (node.left == null) { // 情况1 - 只有右孩子
//            return node.right;
//        }
//        if (node.right == null) { // 情况2 - 只有左孩子
//            return node.left;
//        }
//        BSTNode s = node.right; // 情况3 - 有两个孩子
//        while (s.left != null) {
//            s = s.left;
//        }
//        s.right = doRemove(node.right, s.key, new ArrayList<>());
//        s.left = node.left;
//        return s;
//    }

    public Object remove(int key) {
        BSTNode p = root;
        BSTNode parent = null;
        while (p != null) {
            if (key < p.key) {
                parent = p;
                p = p.left;
            } else if (p.key < key) {
                parent = p;
                p = p.right;
            } else {
                break;
            }
        }
        if (p == null) {
            return null;
        }
        // 删除操作
        if (p.left == null) {
            shift(parent, p, p.right); // 情况1
        } else if (p.right == null) {
            shift(parent, p, p.left); // 情况2
        } else {
            // 情况4
            // 4.1 被删除节点找后继
            BSTNode s = p.right;
            BSTNode sParent = p; // 后继父亲
            while (s.left != null) {
                sParent = s;
                s = s.left;
            }
            // 后继节点即为 s
            if (sParent != p) { // 不相邻
                // 4.2 删除和后继不相邻, 处理后继的后事
                shift(sParent, s, s.right); // 不可能有左孩子
                s.right = p.right;
            }
            // 4.3 后继取代被删除节点
            shift(parent, p, s);
            s.left = p.left;
        }
        return p.value;
    }

    /**
     * 托孤方法
     *
     * @param parent  被删除节点的父亲
     * @param deleted 被删除节点
     * @param child   被顶上去的节点
     */
    private void shift(BSTNode parent, BSTNode deleted, BSTNode child) {
        if (parent == null) {
            root = child;
        } else if (deleted == parent.left) {
            parent.left = child;
        } else {
            parent.right = child;
        }
    }

    /*
                 4
               /   \
              2     6
             / \   / \
            1   3 5   7
     */

    // 找 < key 的所有 value
    public List<Object> less(int key) { // key=6
        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key < key) {
                    result.add(pop.value);
                } else {
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }

    // 找 > key 的所有 value
    public List<Object> greater(int key) {
        /*ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key > key) {
                    result.add(pop.value);
                }
                p = pop.right;
            }
        }
        return result;*/

        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.right;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key > key) {
                    result.add(pop.value);
                } else {
                    break;
                }
                p = pop.left;
            }
        }
        return result;
    }

    // 找 >= key1 且 <= key2 的所有值
    public List<Object> between(int key1, int key2) {
        ArrayList<Object> result = new ArrayList<>();
        BSTNode p = root;
        LinkedList<BSTNode> stack = new LinkedList<>();
        while (p != null || !stack.isEmpty()) {
            if (p != null) {
                stack.push(p);
                p = p.left;
            } else {
                BSTNode pop = stack.pop();
                // 处理值
                if (pop.key >= key1 && pop.key <= key2) {
                    result.add(pop.value);
                } else if (pop.key > key2) {
                    break;
                }
                p = pop.right;
            }
        }
        return result;
    }

}

补充

如果希望让除 int 外更多的类型能够作为 key

  •   一种方式是 key 必须实现 Comparable 接口;
  •   还有一种做法不要求 key 实现 Comparable 接口,而是在构造 Tree 时把比较规则作为 Comparator 传入,将来比较 key 大小时都调用此 Comparator 进行比较,这种做法可以参考 Java 中的 java.util.TreeMap。

前驱后继

  •   一个节点的前驱(前任)节点是指比它小的节点中,最大的那个;
  •   一个节点的后继(后任)节点是指比它大的节点中,最小的那个。

力扣题目

  1.   450. 删除二叉搜索树中的节点 
  2.   701. 二叉搜索树中的插入操作 
  3.   700. 二叉搜索树中的搜索 
  4.   98. 验证二叉搜索树 
  5.   938. 二叉搜索树的范围和
  6.   1008. 前序遍历构造二叉搜索树 
  7.   235. 二叉搜索树的最近公共祖先

来源

  数据结构与算法

路漫漫其修远兮,吾将上下而求索。

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

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

相关文章

Git介绍与常用命令总结

Git介绍与其常用命令总结 1、Git介绍2、Git的使用3、Git常用命令3.1 初始化仓库3.2 克隆仓库3.3 配置用户信息3.4 提交代码(Commit)3.5 推送代码(Push)3.6 拉取代码(Pull)3.7 分支(Branch)3.8 远程仓库(Remote)3.9 撤销回退本地改动3.10 更新本地仓库与远程仓库 1、Git介绍 Gi…

编程流程图

对于复杂流程&#xff0c;我做开发之前一般会 先画一下流程图。特别是多个部门有交叉的情况下&#xff1a; processOn&#xff1a; 这个是我之前 一直的选择&#xff0c;他可以画上面的这些&#xff0c;流程图&#xff0c;网页操作&#xff0c;但是他不是免费的&#xff0c;查过…

【LVGL环境搭建】

LVGL环境搭建 win模拟器环境搭建一.二.三.四.五. Ubuntu模拟器环境搭建一. 前置准备二. 下载LVGL Source code&#xff1a;三. 安装sdl2&#xff1a;四. 开启VScode执行五. 安装扩展套件六. 按F5执行七. 执行结果 win模拟器环境搭建 一. 二. 三. 四. 五. Ubuntu模拟器环境…

同城上门预约软件开发:改变生活服务模式

随着互联网技术的飞速发展&#xff0c;人们的生活方式也在发生着深刻的变化。特别是在生活服务领域&#xff0c;新的需求和模式不断涌现。其中&#xff0c;同城上门预约服务正逐渐成为一种新的趋势。本文将探讨开发同城上门预约软件的意义、市场需求、功能设计以及面临的挑战。…

C++类和对象入门(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、面相过程和面向对象的初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#…

大语言模型之LlaMA系列-LlaMA 2及LlaMA_chat(下)

多转一致性的系统消息 - System Message for Multi-Turn Consistency 在对话设置中&#xff0c;某些指示应适用于所有对话轮次。 例如&#xff0c;简洁地响应&#xff0c;或"充当"某个公众人物。当我们向Llama 2-Chat提供此类指示时&#xff0c;后续应响应始终遵守约…

专业139总分400+南昌大学811信号与系统考研经验电子信息与通信工程集成电路

今年专业课811信号与系统139分&#xff0c;总分400&#xff0c;顺利上岸南昌大学&#xff0c;回首这一年的复习&#xff0c;有很多经验想和大家分享&#xff0c;希望对大家复习会有一些帮助。专业课&#xff1a;139分&#xff0c;811信号与系统 主要参考书&#xff1a;《信号与…

2024年人工智能应用与先进制造科学国际学术会议(ICAIAAMS 2024)

2024年人工智能应用与先进制造科学国际学术会议(ICAIAAMS 2024) 2024 International Conference on Artificial Intelligence Applications and Advanced Manufacturing Science (ICAIAAMS 2024) 会议简介&#xff1a; 2024年人工智能应用与先进制造科学国际学术会议&#xff…

C语言基础:头歌练习数组练习

&#xff08;字符串插入&#xff09; 任务描述 题目描述:输入两个字符串a和b&#xff0c;将b串中的最大字符插入到a串中最小字符后面。 样例输入&#xff1a; MynameisAmy MynameisJane 样例输出&#xff1a; MynameisAymy 题目分析&#xff1a;a字符串中最小的字符是A…

黑马程序员——html css基础——day06——Flex布局

目录&#xff1a; 小米登录 第一步搭建大盒子logo设置标题和input设置密码框和登录按钮完整写法&#xff1a;爱宠案例 大盒子dog搭建h2标题的做法ul布局修改li和a链接的样式给li添加背景图片完整的写法&#xff1a;标准流浮动 基本使用产品区域布局 左右布局区域小li布局清除浮…

天拓四方:边缘计算网关功能、特点与应用举例

传统的数据处理方式面临网络延迟、带宽限制和安全风险等问题。为了解决这些问题&#xff0c;边缘计算技术应运而生&#xff0c;而边缘计算网关作为其核心组件&#xff0c;正发挥着越来越重要的作用。边缘计算网关位于数据源和云数据中心之间。它具备数据采集、协议转换、数据处…

极限挑战:使用 Go 打造百亿级文件系统的实践之旅

JuiceFS 企业版是一款为云环境设计的分布式文件系统&#xff0c;单命名空间内可稳定管理高达百亿级数量的文件。 构建这个大规模、高性能的文件系统面临众多复杂性挑战&#xff0c;其中最为关键的环节之一就是元数据引擎的设计。JuiceFS 企业版于 2017 年上线&#xff0c;经过…

Istio-解决Zipkin对项目的侵入性问题

Istio采用SideCar模式注入的Enovy代理在某些情况下不能完全解决对项目的无侵入性&#xff0c;比如需要用到Istio的链路追踪功能的时候。需要在代码中手动注入链路追踪需要的header&#xff0c;这样就出现了Istio对业务功能的侵入性。 istio服务网格的调用链跟踪需要依赖在服务之…

鸿蒙ArkUI日期选择组件

鸿蒙ArkUI日期选择组件&#xff0c;基于基础组件进行的二次封装的日期选择组件&#xff0c;快速实现日期选择。 /*** 日期*/ Component export default struct DiygwDate{//绑定的值Link Watch(onValue) value:string;// 隐藏值State valueField: string value;// 显示值Sta…

探索Gin框架:Golang使用Gin完成文件上传

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在之前的文章中&#xff0c;我们讲解了Gin框架的快速入门使用&#xff0c;今天我们来聊聊如何使用…

【2024美赛】A题(中英文):资源可用性与性别比例Problem A: Resource Availability and Sex Ratios

【2024美赛】A题&#xff08;中英文&#xff09;&#xff1a;资源可用性与性别比例Problem A: Resource Availability and Sex Ratios 写在最前面2024美赛翻译 —— 跳转链接 中文赛题问题A&#xff1a;资源可用性与性别比例需要检查的问题包括&#xff1a; 英文赛题Problem A:…

惊鸿一瞥-网络初识

&#x1f495;"Echo"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;惊鸿一瞥-网络初识 一.网络的发展过程 网络的发展过程是循序渐进的,大致可以分为四个阶段: 单机时代->局域网时代->广域网时代->互联网时代 单机时代:就是每个机器之间…

如何在CentOS安装DataEase数据分析服务并实现远程访问管理界面

如何在CentOS安装DataEase数据分析服务并实现远程访问管理界面 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 202…

Pytroch 自写训练模板适合入门版 包含十五种经典的自己复现的一维模型 1D CNN

训练模板 在毕业之前&#xff0c;决定整理一下手头的代码&#xff0c;自己做1D-CNN这吗久&#xff0c;打算开源一下自己使用的1D-CNN的代码&#xff0c;包括用随机数生成一个模拟的数据集&#xff0c;到自己写的一个比较好的适合入门的基础训练模板&#xff0c;以及自己复现的…

【大厂AI课学习笔记】1.4 算法的进步(2)

关于感知器的兴衰。 MORE&#xff1a; 感知器的兴衰 一、感知器的发明与初期振动 在人工智能的历史长河中&#xff0c;感知器&#xff08;Perceptron&#xff09;无疑是一个里程碑式的存在。它最初由心理学家Frank Rosenblatt在1950年代提出&#xff0c;并在随后的几年中得到…