【UCB CS 61B SP24】Lecture 16 - Data Structures 2: ADTs, BSTs学习笔记

本文首先介绍了抽象数据类型与树的概念,接着重点讲解二叉搜索树的定义与操作方式,并用 Java 实现一个标准的二叉搜索树结构。

1. 抽象数据类型

首先引入一个概念叫做抽象数据类型(Abstract Data Type,ADT),本课程中我们所实现的各种数据结构其实都设计为了 ADT,那么 ADT 到底是什么概念?它有什么作用?

ADT 是计算机科学中一种重要的设计概念,它通过封装数据结构和操作来隐藏实现细节,仅对外暴露操作接口。在 Java 中,ADT 通过接口(Interface)和类的组合实现。

ADT 只暴露操作(如增删查改),隐藏具体存储方式(如数组、链表或树)。例如:List 接口定义了 add()get(),但不关心底层是 ArrayList(数组)还是 LinkedList(链表)。

这样用户仅需关注“能做什么”,而非“如何实现”。例如:Queueoffer()poll() 方法,无论是用循环数组还是链表实现,调用方式完全一致。

ADT 的核心优势如下:

  • 降低复杂度:用户无需关心底层实现,例如使用 Map 时不必了解哈希表或红黑树的细节。
  • 提高代码复用性:同一接口可适配多种实现,例如 List 可动态切换为数组或链表实现。
  • 增强可维护性:修改底层实现(如优化性能)不会影响外部调用代码。
  • 强制数据完整性:通过封装避免非法操作,例如栈不允许随机访问中间元素。

2. 树

是一种非线性数据结构,由一组节点(Node)和连接这些节点的(Edge)组成,与相对比,树具有以下性质

  • 层级结构:存在唯一的根节点(Root),作为树的起点;
  • 父子关系:除根节点外,每个节点有且仅有一个父节点(Parent),由于树具有层级结构与父子关系,因此可以看作是有方向的,而图的边既可以有方向也可以没方向;
  • 无环性:树中不存在环路(无法从某节点出发沿边回到自身),因此树的边数为节点数减一,而图可以形成环路;
  • 连通性:所有节点通过边连通(全连通),不存在孤立的子结构,而图可以存在孤立子图。

树的核心术语如下:

  • 根节点:树的顶层节点,没有父节点;
  • 子节点:直接连接在当前节点下方的节点;
  • 父节点:直接连接在当前节点上方的节点;
  • 叶子节点:没有子节点的节点(树的末端);
  • 内部节点:至少有一个子节点的节点;
  • 边:连接两个节点的路径;
  • 子树:某个节点及其所有后代组成的子结构;
  • 深度:从根节点到当前节点的路径长度(根节点的深度为0或1,取决于定义);
  • 高度:从当前节点到最远叶子节点的路径长度(叶子节点的高度为0);
  • 度:一个节点拥有的子节点数量。

常见的树有以下这些类型:

  • 二叉树:每个节点最多有两个子节点(左/右);
  • 二叉搜索树:左子树节点值 < 根节点值 < 右子树节点值;
  • 平衡树:通过旋转等操作保持左右子树高度差可控(如 AVL 树、红黑树);
  • 多叉树:每个节点可以有多个子节点(如 B 树、文件系统树);
  • 堆:完全二叉树,满足父节点值 ≥ 或 ≤ 子节点值(最大堆/最小堆)。

树常用链式存储:

class TreeNode<T> {T data;List<TreeNode<T>> children;  // 多叉树TreeNode<T> left, right;  // 二叉树
}

完全二叉树也能用数组存储,父节点索引为 i,则左子节点为 2 * i + 1,右子节点为 2 * i + 2

树具有四种遍历方式:

  • 前序遍历:根 → 左子树 → 右子树,适用于复制树结构;
  • 中序遍历:左子树 → 根 → 右子树,适用于二叉搜索树的有序输出;
  • 后序遍历:左子树 → 右子树 → 根,适用于释放树内存;
  • 层序遍历:按层级从上到下、从左到右,适用于求树的高度/宽度。

树的常见应用场景如下:

  • 文件系统:目录和文件的层级管理;
  • 数据库索引:B/B+ 树加速查询;
  • 游戏 AI:决策树模拟行为逻辑;
  • XML/HTML 解析:DOM 树表示文档结构;
  • 机器学习:决策树分类算法。

3. 二叉搜索树

3.1 基础介绍

假设我们有一个排好序的有序链表如下图所示,如果像查找 G 在哪则需要从头遍历到结尾才能找到,搜索路径为 A -> B -> C -> D -> E -> F -> G

在这里插入图片描述

有什么办法能快点找到这个节点呢?假设我们将链表首部指针移到中间并翻转左半部分的连接,那么搜索时间就会减半!首先我们找到了 D,判断 G 是大于 D 的,那么我们就向右边继续寻找,搜索路径为 D -> E -> F -> G

在这里插入图片描述

既然这样,那么我们继续这样对半分,那么搜索时间就能继续减半,也就构成了二叉搜索树,搜索路径为 D -> F -> G

在这里插入图片描述

二叉搜索树(Binary Search Tree,BST)是一种有序的二叉树数据结构,满足以下性质:

  • 左子树所有节点值小于根节点值;
  • 右子树所有节点值大于根节点值;
  • 左右子树也必须是二叉搜索树。

其核心特性为:

  • 有序性:中序遍历结果为升序序列;
  • 动态操作效率:插入、删除、查找的平均时间复杂度为 O ( l o g n ) O(log n) O(logn),最坏情况为 O ( n ) O(n) O(n)
  • 内存效率:非连续存储,动态扩展;
  • 支持范围查询:可快速找到区间内的所有值。

3.2 操作方法及实现

(1)查找

想查找某个值是否在树中,可以从根节点递归地往下找,一共会有以下几种情况:

  • 当前节点为 null,则已经查完整棵树了还未找到,不存在目标值,返回 false
  • 当前节点值 = 目标值,已找到,返回 true
  • 当前节点值 > 目标值,说明要查找的目标值在子树上,递归查找子树;
  • 当前节点值 < 目标值,说明要查找的目标值在子树上,递归查找子树。

(2)插入

插入新值时同样也是采用递归的思想从根节点开始寻找插入的位置,流程不难理解:

  • 若树为空,新节点直接成为根节点;
  • 若树非空,则比较新值与当前节点值,有以下几种情况:
    • 新值 = 当前节点值:根据需求处理(通常不做处理,也就是不插入重复值);
    • 新值 < 当前节点值:向子树递归插入;
    • 新值 > 当前节点值:向子树递归插入;
    • 当前节点为 null:找到了插入位置,创建新节点并返回。

(3)删除

删除操作相对复杂一点,我们可以将其分为三种情况来讨论:

  • 删除叶节点:直接删除即可,即将其父节点所指向它的指针置为 null

  • 删除有一个子节点(或一边子树)的节点:将其父节点直接连接到该节点的子节点上,为什么这样构建新指针一定是对的?因为假如该节点在父节点的左侧,那么该节点及其子树一定也小于其父节点,因此父节点指向该节点的子树作为左子树一定是合理的,同理假如在右侧也一样。例如我们删除下面这棵树的 "flat"
    在这里插入图片描述
    在这里插入图片描述

  • 删除有两个子节点的节点:这种情况最复杂,我们需要选出一个节点顶替到当前节点的位置上,也就是将所选节点的值覆盖到当前节点后再将所选节点删除。那么选哪个节点最方便?答案是右子树中的最小节点(或左子树中的最大节点)。因为这两种类型的节点替换到当前节点上都能保证当前节点左子树中的所有值都小于自身,且右子树中的所有值的都大于自身,这两种类型的节点一定满足没有子树或最多只有一棵子树。例如我们删除下面这棵树的 k,最适合替代 k 的值为 gm
    在这里插入图片描述
    我们选择用 g 替代 k,然后将 g 删除(回到了第二种情况,直接将 e 指向 f 即可完成 g 的删除):
    在这里插入图片描述

3.3 Java实现BinarySearchTree

根据前面的讲解,我们就可以实现二叉搜索树的每个操作:

package CS61B.Lecture16;public class BinarySearchTree<T extends Comparable<T>> {private class Node {private T val;private Node left;private Node right;public Node(T val) {this.val = val;left = null;right = null;}}private Node root;public BinarySearchTree() {root = null;}public void insert(T val) {root = insertRecursive(root, val);}private Node insertRecursive(Node node, T val) {if (node == null) return new Node(val);  // 已找到插入位置int cmp = val.compareTo(node.val);if (cmp < 0) node.left = insertRecursive(node.left, val);  // 往左子树递归寻找插入位置else if (cmp > 0) node.right = insertRecursive(node.right, val);  // 往右子树递归寻找插入位置// 若值已存在则不做处理return node;}public boolean search(T val) {return searchRecursive(root, val);}private boolean searchRecursive(Node node, T val) {if (node == null) return false;  // 未找到目标值int cmp = val.compareTo(node.val);if (cmp < 0) return searchRecursive(node.left, val);  // 往左子树递归查找else if (cmp > 0) return searchRecursive(node.right, val);  // 往右子树递归查找else return true;  // 当前值等于目标值,已找到}public void delete(T val) {root = deleteRecursive(root, val);}private Node deleteRecursive(Node node, T val) {if (node == null) return null;int cmp = val.compareTo(node.val);if (cmp < 0) node.left = deleteRecursive(node.left, val);else if (cmp > 0) node.right = deleteRecursive(node.right, val);else {  // 已找到要删除的目标值// Case 1 & 2:没有子树或只有一棵子树if (node.left == null) return node.right;  // 如果没有子树则返回 null,如果有右子树则返回右子树if (node.right == null) return node.left;  // 如果有左子树则返回左子树// Case 3:既有左子树也有右子树Node minNodeInRight = findMinNodeInRight(node.right);  // 找出右子树中的最小值node.val = minNodeInRight.val;  // 替换到当前节点上node.right = deleteRecursive(node.right, minNodeInRight.val);  // 递归地在右子树中删除这个最小值}return node;}private Node findMinNodeInRight(Node node) {while (node.left != null) node = node.left;return node;}// 中序遍历,用于验证二叉搜索树是否正确public void inOrderTraversal() {inOrderTraversalRecursive(root);System.out.println();}private void inOrderTraversalRecursive(Node node) {if (node == null) return;inOrderTraversalRecursive(node.left);  // 先递归遍历左子树System.out.print(node.val + " ");  // 输出当前节点的值inOrderTraversalRecursive(node.right);  // 再递归遍历右子树}public static void main(String[] args) {BinarySearchTree<Integer> bst = new BinarySearchTree<>();bst.insert(50);bst.insert(30);bst.insert(20);bst.insert(40);bst.insert(70);bst.insert(60);bst.insert(80);bst.inOrderTraversal();  // 20 30 40 50 60 70 80System.out.println(bst.search(10));  // falseSystem.out.println(bst.search(20));  // truebst.delete(50);bst.delete(20);bst.delete(30);bst.inOrderTraversal();  // 40 60 70 80}
}

注意代码中的 T extends Comparable<T> 这是表示强制泛型类型 T 实现比较逻辑,确保二叉搜索树的核心操作(插入、查找、删除)可以正确执行,因为 BST 中需要对值进行比较:val.compareTo(node.val)

假设我们定义一个 Person 类,并尝试用 BinarySearchTree<Person> 存储,只要我们这个类是可比较的,那么就可以正常使用 BST:

class Person implements Comparable<Person> {String name;int age;@Overridepublic int compareTo(Person other) {return this.age - other.age;  // 按年龄比较}
}// 合法:Person 实现了 Comparable<Person> 接口,并重写了 compareTo 方法,具有合法的比较逻辑
BinarySearchTree<Person> bst = new BinarySearchTree<>();
bst.insert(new Person("Alice", 18));

3.4 二叉搜索树与集合

我们回顾一下集合的特性:

  • 不允许重复元素。
  • 支持高效插入、删除和查找操作。
  • 可以有序(如 TreeSet)或无序(如 HashSet)。

这么一看我们的二叉搜索树是不是就是类似集合的数据结构?二叉搜索树确实可以用来实现集合,但它的应用不仅限于此。

利用 BST 的有序性动态操作特性,可以实现一个有序集合。例如 Java 的 TreeSet 底层通过红黑树(一种平衡二叉搜索树)实现,保证了插入、删除、查找的时间复杂度为 O ( l o g n ) O(log n) O(logn)

普通 BST 存在退化为链表的可能(例如从小到大插入元素),即时间复杂度退化为 O ( n ) O(n) O(n),因此实际应用中通常使用平衡二叉搜索树(如 AVL 树、红黑树),这些数据结构在后面马上就会讲到。

以下是一些直接或间接基于 BST 的经典数据结构:

数据结构核心思想应用场景
TreeSet基于红黑树实现的有序集合需要维护元素顺序的集合操作
TreeMap基于红黑树实现的有序键值对映射字典、范围查询
AVL 树严格平衡的 BST,通过旋转保持左右子树高度差 ≤ 1对查询效率要求极高的场景
红黑树近似平衡的 BST,通过颜色标记和旋转降低平衡成本Java 的 TreeMap/TreeSet
B 树/B+ 树多路平衡搜索树(每个节点可存多个值),是 BST 的扩展数据库索引、文件系统

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

包子凑数——蓝桥杯真题Python

包子凑数 输入输出样例 示例 1 输入 2 4 5输出 6样例说明 凑不出的数目包括&#xff1a;1, 2, 3, 6, 7, 11。 示例 2 输入 2 4 6输出 INF样例说明 所有奇数都凑不出来&#xff0c;所以有无限多个 运行限制 最大运行时间&#xff1a;1s最大运行内存: 256M 最大公约数 最大公…

一周学会Flask3 Python Web开发-Jinja2模版中加载静态文件

锋哥原创的Flask3 Python Web开发 Flask3视频教程&#xff1a; 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 一个Web项目不仅需要HTML模板&#xff0c;还需要许多静态文件&#xff0c;比如 CSS、JavaScript文件、图片以及音频等。在Fla…

Python的那些事第三十二篇:用于创建静态、动画和交互式可视化的绘图库Matplotlib

Matplotlib:用于创建静态、动画和交互式可视化的绘图库 摘要 Matplotlib 是一个广泛使用的 Python 绘图库,能够创建静态、动画和交互式可视化图表。本文首先介绍了 Matplotlib 的基本功能和架构,然后通过具体的示例代码展示了如何使用 Matplotlib 创建不同类型的图表。接着…

tableau之雷达图和凹凸图

一、雷达图 概念 雷达图&#xff08;Radar Chart&#xff09;&#xff0c;也称为蜘蛛网图&#xff08;Spider Chart&#xff09;或星状图&#xff08;Star Chart&#xff09;&#xff0c;是一种用于多变量数据可视化的图表。它以中心点向外辐射的轴线表示不同的变量&#xff…

Redis-列表结构实操

列表实操 前言简单练习基本的LPUSH和RPUSH操作列表元素的访问与修改列表元素的插入和删除列表阻塞操作 困难练习分页列表游标机制业务上考虑直接访问任意页如何高效分页局限性小结 实现限时排行版轮换消息队列可靠性实现分布式锁实现 总结 前言 之前总结过-列表的数据结构,但是…

SpringBoot 2 后端通用开发模板搭建(异常处理,请求响应)

目录 一、环境准备 二、新建项目 三、整合依赖 1、MyBatis Plus 数据库操作 2、Hutool 工具库 3、Knife4j 接口文档 4、其他依赖 四、通用基础代码 1、自定义异常 2、响应包装类 3、全局异常处理器 4、请求包装类 5、全局跨域配置 补充&#xff1a;设置新建类/接…

实现Python+Django+Transformers库中的BertTokenizer和BertModel来进行BERT预训练,并将其应用于商品推荐功能

一、环境安装准备 #git拉取 bert-base-chinese 文件#创建 虚拟运行环境python -m venv myicrplatenv#刷新source myicrplatenv/bin/activate#python Django 集成nacospip install nacos-sdk-python#安装 Djangopip3 install Django5.1#安装 pymysql settings.py 里面需要 # 强制…

Rk3568驱动开发_点亮led灯代码完善(手动挡)_6

1.实现思路&#xff1a; 应用层打开设备后通过write函数向内核中写值&#xff0c;1代表要打开灯&#xff0c;0代表要关闭灯 Linux配置gpio和控制gpio多了一个虚拟内存映射操作 2.注意事项&#xff1a; 配置和读写操作的时候要谨慎&#xff0c;比如先关掉gpio再注销掉虚拟内存…

线性回归(一)基于Scikit-Learn的简单线性回归

主要参考学习资料&#xff1a; 《机器学习算法的数学解析与Python实现》莫凡 著 前置知识&#xff1a;线性代数-Python 目录 问题背景数学模型假设函数损失函数优化方法训练步骤 代码实现特点 问题背景 回归问题是一类预测连续值的问题&#xff0c;满足这样要求的数学模型称作…

P10108 [GESP202312 六级] 闯关游戏

题目大意 如题 分析 设最佳通关方案为 { s 1 , s 2 , . . . , s k } \{s_1,s_2,...,s_k\} {s1​,s2​,...,sk​}&#xff0c;其中 s i s_i si​ 代表第 i i i 次到达的关卡&#xff08; ≥ N \ge N ≥N 的不算&#xff09;。 当 a k N − 1 a_kN-1 ak​N−1 时&#…

vllm的使用方式,入门教程

vLLM是一个由伯克利大学LMSYS组织开源的大语言模型推理框架&#xff0c;旨在提升实时场景下的大语言模型服务的吞吐与内存使用效率。以下是详细的vLLM使用方式和入门教程&#xff1a; 1. 前期准备 在开始使用vLLM之前&#xff0c;建议先掌握一些基础知识&#xff0c;包括操作…

web的分离不分离:前后端分离与不分离全面分析

让我们一起走向未来 &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[1504566…

HDFS扩缩容及数据迁移

1.黑白名单机制 在HDFS中可以通过黑名单、白名单机制进行节点管理&#xff0c;决定数据可以复制/不可以复制到哪些节点。 黑名单通常是指在HDFS中被标记为不可用或不可访问的节点列表&#xff0c;这些节点可能由于硬件故障、网络问题或其他原因而暂时或永久性地无法使用。当一…

数据如何安全“过桥”?分类分级与风险评估,守护数据流通安全

信息化高速发展&#xff0c;数据已成为企业的核心资产&#xff0c;驱动着业务决策、创新与市场竞争力。随着数据开发利用不断深入&#xff0c;常态化的数据流通不仅促进了信息的快速传递与共享&#xff0c;还能帮助企业快速响应市场变化&#xff0c;把握商业机遇&#xff0c;实…

[Web 安全] PHP 反序列化漏洞 —— PHP 序列化 反序列化

关注这个专栏的其他相关笔记&#xff1a;[Web 安全] 反序列化漏洞 - 学习笔记-CSDN博客 0x01&#xff1a;PHP 序列化 — Serialize 序列化就是将对象的状态信息转化为可以存储或传输的形式的过程&#xff0c;在 PHP 中&#xff0c;通常使用 serialize() 函数来完成序列化的操作…

DeepSeek-R1:模型部署与应用实践

深入探索DeepSeek-R1&#xff1a;模型部署与应用实践 在当今人工智能飞速发展的时代&#xff0c;大语言模型&#xff08;LLMs&#xff09;已经成为众多领域的核心驱动力。DeepSeek-R1作为一款备受瞩目的模型&#xff0c;在自然语言处理任务中展现出了强大的能力。本文将深入探…

Java 大视界 -- 基于 Java 的大数据机器学习模型压缩与部署优化(99)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…

socket编程详解

TCP报文格式 0. 举例 首先来看一个TCP连接的例子&#xff0c;如图1所示&#xff0c;分别给出了服务器和客户端所调用的API&#xff0c;对这些函数有一个总体认识之后&#xff0c;再逐个对每个函数详细介绍。 图1 创建TCP连接时服务器、客户端调用的API 1. socket() 注&#xf…

企业知识库搭建:14款开源与免费系统选择

本文介绍了以下14 款知识库管理系统&#xff1a;1.Worktile&#xff1b;2.PingCode&#xff1b;3.石墨文档&#xff1b; 4. 语雀&#xff1b; 5. 有道云笔记&#xff1b; 6. Bitrix24&#xff1b; 7. Logseq等。 在如今的数字化时代&#xff0c;企业和团队面临着越来越多的信息…

Spring Cloud Alibaba学习 3- Sentinel入门使用

Spring Cloud Alibaba学习 3- Sentinel入门使用 中文文档参考&#xff1a;Sentinel中文文档 一. SpringCloud整合Sentinel 1.1 下载Sentinel-Dashboard Sentinel下载地址&#xff1a;Sentinel-Dashboard 到下载目录&#xff0c;cmd输入 java -jar sentinel-dashboard-1.8…