搜索树基础:二叉搜索树(详解特性用途,图解实现过程)

简介: 搜索树基础:二叉搜索树(详解特性用途,图解实现过程)

二叉搜索树的特性

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  3. 它的左右子树也分别为二叉搜索树

二叉搜索树的主要用途

二叉搜索树主要用于实现高效的数据查找和排序:

  1. 查找:由于二叉搜索树的特性,可以通过比较节点的键值来快速定位目标节点。在查找操作中,对于一颗相对平衡的二叉搜索树,每次都可以将搜索范围缩小一半,因此平均时间复杂度为O(log n),其中n是树中节点的数量。
  2. 排序:对于一个无序的序列,可以利用二叉搜索树的性质进行排序。具体实现方法是,将序列中的元素依次插入到二叉搜索树中,然后进行中序遍历,即可按照升序输出有序序列。这种基于二叉搜索树的排序算法,对于一颗相对平衡的二叉搜索树,平均时间复杂度为O(nlog n)

需要注意的是,二叉搜索树的性能取决于树的形状。如果构建出的二叉搜索树高度不平衡(只有左树或只有右树),查找的时间复杂度可能达到O(n),导致操作效率会大幅下降。因此,在实际使用中,需要注意维护二叉搜索树的平衡性,以提高操作效率,在此基础上演化出了 AVL树、红黑树等更具平衡性的数据结构。


二叉搜索树是学习其他“搜索”数据结构的基础,下面就围绕二叉搜索树的一些操作进行展开介绍:

二叉搜索树的基本操作

为了实现二叉搜索树的相关操作,这里首先给出二叉搜索树节点的定义:

static class TreeNode {
   public int val;
     public TreeNode left;
     public TreeNode right;

     public TreeNode(int val) {
         this.val = val;
     }
 }
// 二叉搜索树根节点,初始时为空
public TreeNode root = null;

1、二叉搜索树的查找

实现思路(利用二叉搜索树的特性):

  1. 从根节点开始,与目标键值进行比较。
  2. 如果目标键值等于当前节点的键值,则找到了目标节点;
  3. 如果目标键值小于当前节点的键值,则继续在左子树中查找;
  4. 如果目标键值大于当前节点的键值,则继续在右子树中查找;
  5. 重复步骤2、3、4直到找到目标节点或者遍历到叶子节点。
public TreeNode find(int val) {
    // 从根节点开始查找
    TreeNode cur = root;
    while (cur != null) {
        if (cur.val == val) {
            // 找到返回结点
            return cur;
        } else if (cur.val > val) {
            // 当前结点值大于目标结点
            cur = cur.left;
        } else {
            // 当前结点小于目标结点
            cur = cur.right;
        }
    }
    // 找不到
    return null;
}

2、二叉搜索树的插入

经过简单分析,当搜索树非空时,二叉树的插入,总是插入到相应的叶子结点(由于结点之间单向联通,所以需要记录待插入位置的父节点):

  1. 从根节点开始,与要插入的键值进行比较。
  2. 如果要插入的键值小于当前节点的键值并且当前节点的左子节点为空,则将新节点作为当前节点的左子节点;
  3. 如果要插入的键值大于当前节点的键值并且当前节点的右子节点为空,则将新节点作为当前节点的右子节点。
public boolean insert(int val) {
    // 如果当前根节点为空,则插入到根节点
    if (root == null) {
        root = new TreeNode(val);
        return true;
    }
    TreeNode cur = root;
    // 记录插入位置的父节点
    TreeNode parent = null;
    // 寻找插入位置
    while (cur != null) {
        if (cur.val > val) {
          // 当前结点值大于目标结点
            parent = cur;
            cur = cur.left;
        } else if (cur.val < val) {
          // 当前结点值小于目标结点
            parent = cur;
            cur = cur.right;
        } else {
            // 如果二叉搜索树中已存在,直接返回
            System.out.println("已存在:" + val);
            return false;
        }
    }
    // 构造插入结点
    TreeNode node = new TreeNode(val);
    // 此时 parent 结点指向插入位置的父节点
    if (parent.val > val) {
        parent.left = node;
    } else {
        parent.right = node;
    }
    return true;
}

3、二叉搜索树的删除(难点)

(1)找到待删结点

二叉搜索树的删除,首先需要找到待删除结点,然后执行删除操作(由于结点之间单向联通,所以需要记录待删结点的父节点):

public void remove(int val) {
  // 待删结点,从root开始找
    TreeNode cur = root;
    // 待删结点的父节点
    TreeNode parent = null;
    while (cur != null) {
        if (cur.val == val) {
          // 找到目标结点,调用删除操作
            removeNode(cur, parent);
            return;
        } else if (cur.val > val) {
          // 当前结点值大于目标结点
            parent = cur;
            cur = cur.left;
        } else {
          // 当前结点值小于目标结点
            parent = cur;
            cur = cur.right;
        }
    }
    System.out.println("不存在:" + val);
}

(2)分情况删除

二叉搜索树的删除操作,是一个相对复杂的操作,需要考虑多种不同情况:

设待删除结点为 cur, 待删除结点的双亲结点为 parent

cur.left == null

思路

cur 是 root,则 root = cur.right

cur 不是 root,cur 是 parent.left,则 parent.left = cur.right

cur 不是 root,cur 是 parent.right,则 parent.right = cur.right

cur.right == null

思路:

  1. cur 是 root,则 root = cur.left
  2. cur 不是 root,cur 是 parent.left,则 parent.left = cur.left
  3. cur 不是 root,cur 是 parent.right,则 parent.right = cur.left

cur.left != null && cur.right != null

思路

此时需要使用替换法进行删除。可以选择用其左子树cur.left的最大值节点,或右子树cur.right的最小值节点的值填补到被删除节点中,然后删除这个最大或最小值节点。

上面这两种选哪个都行,下面我就以选择右子树 cur.right 中的最小值结点替换删除为例:

  • 当右子树的根节点有左孩子,那么 minParent.left 是右子树的最小值

  • 当右树根结点没有左孩子,那么 minParent.right 是右子树的最小值

最终实现代码:

private void removeNode(TreeNode cur, TreeNode parent) {
    if (cur.left == null) {
        // 1.如果待删除结点左孩子为空
        if (cur == root) {
            //左孩子为空的前提下,cur为根节点
            cur = cur.right;
        } else if (parent.left == cur) {
            //左孩子为空的前提下,cur为父节点的左树
            parent.left = cur.right;
        } else {
            //左孩子为空的前提下,cur为父节点的右树
            parent.right = cur.right;
        }
    } else if (cur.right == null) {
        // 2.如果待删除结点右孩子为空
        if (cur == root) {
            //右孩子为空的前提下,cur为根节点
            cur = cur.left;
        } else if (parent.left == cur) {
            //右孩子为空的前提下,cur为父节点的左树
            parent.left = cur.left;
        } else {
            //右孩子为空的前提下,cur为父节点的右树
            parent.right = cur.left;
        }
    } else {
        // 3.如果待删除结点左右孩子都不为空

        //这里是cur.left!=null && cur.right!=null的情况
        //替换删除:(找到右树里的最小值替换删除/找到左树的最大值替换删除)
        //下面展示找到右树里的最小值替换删除
        TreeNode minParent = cur;
        TreeNode min = cur.right;
        while (min.left != null) {
            minParent = min;
            min = min.left;
        }
        //此时 min 的左边一定为空
        //值替换
        cur.val = min.val;
        if (minParent.right == min) {
            // 当右树根结点没有左孩子,那么 minParent.right 是右子树的最小值
            minParent.right = min.right;
        } else {
            // 当右子树的根节点有左孩子,那么 minParent.left 是右子树的最小值
            minParent.left = min.right;
        }
    }
}


相关文章
|
安全 Shell 开发工具
|
机器学习/深度学习 人工智能 API
大模型推理服务全景图
国内大模型推理需求激增,性能提升的主战场将从训练转移到推理。
1515 98
|
存储 算法
递归算法
【10月更文挑战第11天】递归算法是一种强大而又具有挑战性的算法技术。通过深入理解和掌握递归的原理、应用以及优化方法,我们可以更好地利用它来解决各种问题,并在编程实践中发挥其独特的优势。同时,我们也要注意递归算法可能带来的性能和栈空间问题,通过合理的设计和优化来提高算法的效率和稳定性。
736 156
|
机器学习/深度学习 JSON 算法
二叉树遍历算法的应用场景有哪些?
【10月更文挑战第29天】二叉树遍历算法作为一种基础而重要的算法,在许多领域都有着不可或缺的应用,它为解决各种复杂的问题提供了有效的手段和思路。随着计算机科学的不断发展,二叉树遍历算法也在不断地被优化和扩展,以适应新的应用场景和需求。
885 0
|
边缘计算 人工智能 监控
边缘计算与AI结合的场景案例研究
【8月更文第17天】随着物联网(IoT)设备数量的爆炸性增长,对实时数据处理的需求也随之增加。传统的云计算模型在处理这些数据时可能会遇到延迟问题,尤其是在需要即时响应的应用中。边缘计算作为一种新兴的技术趋势,旨在通过将计算资源更靠近数据源来解决这个问题。本文将探讨如何将人工智能(AI)技术与边缘计算结合,以实现高效的实时数据分析和决策制定。
1457 1
|
人工智能 自然语言处理 搜索推荐
解读:claude国内中文版_CLAUDE国内镜像网站
Claude AI,由 Anthropic 公司开发,以信息论之父 Claude Shannon 命名,代表了人工智能语言模型发展的新方向。它不仅继承了现有语言模型强大的文本生成能力,更着重于安全性、透明度和可控性,致力于打造更负责任、更值得信赖的 AI 系统
|
算法
广度优先遍历(BFS):逐层探索图的算法奥秘
在图论中,广度优先遍历(Breadth-First Search,BFS)是一种图遍历算法,它以一种逐层的方式探索图的节点。通过从起始节点开始,逐层向外扩展,BFS能够帮助我们解决许多与图相关的问题。
570 0
|
监控 算法 编译器
初识 Verilog HDL , 什么是verilog HDL?
这是一篇关于Verilog HDL的学习笔记摘要。Verilog是一种硬件描述语言,用于数字系统的多层抽象设计,包括行为、数据流和结构。设计流程包括功能设计、Verilog描述、软件模拟、逻辑综合和硬件实现。模块是Verilog的基本单元,代表逻辑实体,通过并行运行和分层连接实现复杂系统。模块包含端口列表和定义,通过模块调用(实例化)实现子模块连接。Verilog的参数声明和预处理指令(如`define、`include和`timescale)增加了代码的可读性和灵活性。笔记指出Verilog与C语言有相似之处,易于学习。
|
机器学习/深度学习 算法框架/工具 计算机视觉
使用Python实现图像分类与识别模型
使用Python实现图像分类与识别模型
570 2
使用Python实现图像分类与识别模型
|
存储 算法 Serverless
数据结构-概念版(六)
数据结构-概念版
706 0