【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(链表)。
这样用户仅需关注“能做什么”,而非“如何实现”。例如:Queue 的 offer() 和 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的值为g与m:

我们选择用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 的扩展 | 数据库索引、文件系统 |
相关文章:
【UCB CS 61B SP24】Lecture 16 - Data Structures 2: ADTs, BSTs学习笔记
本文首先介绍了抽象数据类型与树的概念,接着重点讲解二叉搜索树的定义与操作方式,并用 Java 实现一个标准的二叉搜索树结构。 1. 抽象数据类型 首先引入一个概念叫做抽象数据类型(Abstract Data Type,ADT)࿰…...
RabbitMQ系列(零)概要
一、消息队列总览 1. 什么是消息队列? 消息队列(Message Queue)是一种异步通信机制,允许分布式系统中的服务通过生产-消费模型传递数据。其核心价值在于: 解耦性:生产者与消费者无需同时在线或直接交互削…...
Java 大视界 -- Java 大数据在智能物流路径规划与车辆调度中的创新应用(102)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
HarmonyOS Design 介绍
HarmonyOS Design 介绍 文章目录 HarmonyOS Design 介绍一、HarmonyOS Design 是什么?1. 设计系统(Design System)2. UI 框架的支持3. 设计工具和资源4. 开发指南5. 与其他设计系统的对比总结 二、HarmonyOS Design 特点 | 应用场景1. Harmon…...
云计算如何解决延迟问题?
在云计算中,延迟(latency)指的是从请求发出到收到响应之间的时间间隔。延迟过高可能会严重影响用户体验,特别是在需要实时响应的应用中,如在线游戏、视频流、金融交易等。云计算服务如何解决延迟问题,通常依…...
【算法系列】快速排序详解
文章目录 快速排序的多种实现方式1. 基本快速排序(Lomuto 分区方案)1.1 基本原理1.2 步骤1.3 Java 实现示例 2. Hoare 分区方案2.1 基本原理2.2 步骤2.3 Java 实现示例 3. 三数取中法3.1 基本原理3.2 步骤3.3 Java 实现示例 4. 尾递归优化4.1 基本原理4.…...
电脑键盘知识
1、键盘四大功能区 1. 功能区 2. 主要信息输入区 3. 编辑区 4. 数字键盘区 笔记本电脑键盘的功能区,使用前需先按Fn键 1.1、功能区 ESC:退出 F1:显示帮助信息 F2:重命名 F4:重复上一步操作 F5:刷新网页 …...
Grok 3 vs. DeepSeek vs. ChatGPT:2025终极AI对决
2025 年,AI 领域的竞争愈发激烈,三个重量级选手争夺霸主地位:Grok 3(由 xAI 开发)、DeepSeek(国内 AI 初创公司)和 ChatGPT(OpenAI 产品)。每个模型都有自己独特的优势,无论是在深度思考、速度、编程辅助、创意输出,还是在成本控制方面,都展现出强大的实力。但究竟…...
【MySQL篇】数据库基础
目录 1,什么是数据库? 2,主流数据库 3,MySQL介绍 1,MySQL架构 2,SQL分类 3,MySQL存储引擎 1,什么是数据库? 数据库(Database,简称DB…...
vscode java环境中文乱码的问题
先说我的结论: 由于我的系统是windows的,所以vscode使用的是默认gbk的编码进行的。 但是我的目的是全部都使用utf-8,因为我的程序始终是要去linux上去运行的,总不能在本地是好的,然后到服务器上就不行了吧,…...
基于SpringBoot+mybatisplus+vueJS的Cosplay文化展示与交流社区设计与实现
博主介绍:硕士研究生,专注于信息化技术领域开发与管理,会使用java、标准c/c等开发语言,以及毕业项目实战✌ 从事基于java BS架构、CS架构、c/c 编程工作近16年,拥有近12年的管理工作经验,拥有较丰富的技术架…...
组件传递props校验
注意:prop是只读的!不可以修改父组件的数据。 可以检验传过来的内容是否类型没问题。 App.vue <template><div><!-- <parentDemo/> --><componentA/></div></template> <script> import ComponentA …...
数据结构与算法-图论-最短路-拓展运用
选择最佳路线 分析: 这是一道图论中的最短路径问题,目标是在给定的公交网络中,找到从琪琪家附近的车站出发,到她朋友家附近车站(编号为 s )的最短时间。以下是对该问题的详细分析: 问题关键信息…...
0—QT ui界面一览
2025.2.26,感谢gpt4 1.控件盒子 1. Layouts(布局) 布局控件用于组织界面上的控件,确保它们的位置和排列方式合理。 Vertical Layout(垂直布局) :将控件按垂直方向排列。 建议:适…...
纷析云:赋能企业财务数字化转型的开源解决方案
在企业数字化转型的浪潮中,财务管理的高效与安全成为关键。纷析云凭借其开源、安全、灵活的财务软件解决方案,为企业提供了一条理想的转型路径。 一、开源的力量:自主、安全、高效 纷析云的核心优势在于其100%开源的财务软件源码。这意味着…...
P8716 [蓝桥杯 2020 省 AB2] 回文日期
1 题目说明 2 题目分析 暴力不会超时,O(n)的时间复杂度, < 1 0 8 <10^8 <108。分析见代码: #include<iostream> #include<string> using namespace std;int m[13]{0,31,28,31,30,31,30,31,31,30,31,30,31};// 判断日期…...
(十)趣学设计模式 之 外观模式!
目录 一、 啥是外观模式?二、 为什么要用外观模式?三、 外观模式的实现方式四、 外观模式的优缺点五、 外观模式的应用场景六、 总结 🌟我的其他文章也讲解的比较有趣😁,如果喜欢博主的讲解方式,可以多多支…...
apache-maven-3.2.1
MAVEN_HOME D:\apache-maven-3.2.1 PATH D:\apache-maven-3.2.1\bin cmd mvn -v <localRepository>d:\localRepository</localRepository> setting.xml <?xml version"1.0" encoding"UTF-8"?><!-- Licensed to the Apache Soft…...
编程题-连接两字母单词得到的最长回文串(中等)
题目: 给你一个字符串数组 words 。words 中每个元素都是一个包含 两个 小写英文字母的单词。 请你从 words 中选择一些元素并按 任意顺序 连接它们,并得到一个 尽可能长的回文串 。每个元素 至多 只能使用一次。 请你返回你能得到的最长回文串的 长度…...
react 新手入门指南,常用命令
React 是一个用于构建用户界面的 JavaScript 库,它通过组件化的方式构建应用程序的 UI,适用于构建单页应用(SPA)。以下是一个详细的 React 新手入门指南,包括常用命令和基本概念。 1. 环境准备 在开始之前,确保你已经安装了 Node.js 和 npm。可以通过以下命令检查版本:…...
论文笔记(七十二)Reward Centering(三)
Reward Centering(三) 文章概括摘要3 基于值的奖励中心化4 案例研究: 以奖励为中心的 Q-learning5 讨论、局限性与未来工作致谢 文章概括 引用: article{naik2024reward,title{Reward Centering},author{Naik, Abhishek and Wan…...
【论文笔记-ECCV 2024】AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品
AnyControl:使用文本到图像生成的多功能控件创建您的艺术作品 图1 AnyControl的多控制图像合成。该研究的模型支持多个控制信号的自由组合,并生成与每个输入对齐的和谐结果。输入到模型中的输入控制信号以组合图像显示,以实现更好的可视化。 …...
Vue3核心编译库@vuecompiler-core内容分享
vue/compiler-core 是 Vue 3 中的一个核心编译库,主要用于编译 Vue 的模板。它为 Vue 3 提供了处理模板编译的功能,包含了将模板转换为抽象语法树(AST)、生成渲染函数以及与响应式系统进行集成等功能。 vue/compiler-core 的主要…...
Redisson使用场景及原理
目录 一、前言 二、安装Redis 1、Windows安装Redis 2、启动方式 3、设置密码 三、项目集成Redission客户端 1、引入依赖 四、实用场景 1、操作缓存 2、分布式锁 3、限流 3.1 创建限流器 3.2 设置限流参数 3.3 获取令牌 3.4 带超时时间获取令牌 3.5 总结 一、…...
【二分查找 图论】P8794 [蓝桥杯 2022 国 A] 环境治理|普及
本文涉及的基础知识点 本博文代码打包下载 C二分查找 C图论 [蓝桥杯 2022 国 A] 环境治理 题目描述 LQ 国拥有 n n n 个城市,从 0 0 0 到 n − 1 n - 1 n−1 编号,这 n n n 个城市两两之间都有且仅有一条双向道路连接,这意味着任意两…...
c/c++蓝桥杯经典编程题100道(22)最短路径问题
最短路径问题 ->返回c/c蓝桥杯经典编程题100道-目录 目录 最短路径问题 一、题型解释 二、例题问题描述 三、C语言实现 解法1:Dijkstra算法(正权图,难度★★) 解法2:Bellman-Ford算法(含负权边&a…...
25中医研究生复试面试问题汇总 中医专业知识问题很全! 中医试全流程攻略 中医考研复试调剂真题汇总
各位备考中医研究生的小伙伴们,一想到复试,是不是立刻紧张到不行,担心老师会抛出一大堆刁钻的问题?别怕!其实中医复试也是有套路可循的,只要看完这篇攻略,你就会发现复试并没有想象中那么难&…...
stm32hal库寻迹+蓝牙智能车(STM32F103C8T6)
简介: 这个小车的芯片是STM32F103C8T6,其他的芯片也可以照猫画虎,基本配置差不多,要注意的就是,管脚复用,管脚的特殊功能,(这点不用担心,hal库每个管脚的功能都会给你罗列,很方便的.)由于我做的比较简单,只是用到了几个简单外设.主要是由带霍尔编码器电机的车模,电机…...
centos22.04 dpkg -l 输出状态标识含义
dpkg -l 输出状态标识含义 dpkg -l 命令用于列出系统中已安装的软件包,每行输出的前两个字符是软件包状态的标识,不同的组合代表不同的状态,具体含义如下: 第一个字符:表示期望的状态(Desired state) u:未知(Unknown)i:安装(Install)r:移除(Remove)p:清除(Pu…...
前端TypeScript 面试题及参考答案
目录 解释 unknown 与 any 的区别,如何安全使用 unknown 类型? 如何用类型守卫处理联合类型变量的方法调用? 实现一个工具类型 Nullable ,使 T 可被赋值为 null/undefined 如何用 keyof 和 in 关键字实现枚举类型到联合类型的转换? 类型断言 as 与尖括号语法有何差异…...
