【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
本文内容为重写上一节课中的单链表,将其重构成更易于用户使用的链表,实现多种操作链表的方法。
1. 重构单链表SLList
在上一节课中编写的 IntList 类是裸露递归的形式,在 Java 中一般不会这么定义,因为这样用户可能需要非常了解引用,并且能够递归地思考,才能使用这个类。
因此我们需要实现一个更容易使用的单链表(Singly Linked List),首先我们定义节点类 IntNode:
package CS61B.Lecture4;public class IntNode {public int val;public IntNode next;public IntNode(int val, IntNode next) {this.val = val;this.next = next;}
}
现在就可以创建单链表类 SLList:
package CS61B.Lecture4;public class SLList {private IntNode head;public SLList(int val) {this.head = new IntNode(val, null);}public static void main(String[] args) {SLList L = new SLList(5);}
}
之前用户使用单链表需要这样定义:IntList L = new IntList(5, null);,他需要有递归考虑的思想,必须指定所指向的下一个链表的空值。
由于 IntNode 类只有在 SLList 类中使用才有意义,且用户一般不会单独去使用 IntNode 类,因此可以将其作为嵌套类放入 SLList 中,并且可以使用 private 关键字控制其权限:
package CS61B.Lecture4;public class SLList {private static class IntNode {public int val;public IntNode next;public IntNode(int val, IntNode next) {this.val = val;this.next = next;}}private IntNode head;public SLList(int val) {this.head = new IntNode(val, null);}public static void main(String[] args) {SLList L = new SLList(5);}
}
注意到我们还将 IntNode 类定义为静态的,静态嵌套类与外部类的实例无关,它更像是一个独立的类,但逻辑上仍然属于外部类的范畴,静态嵌套类可以直接访问外部类的静态字段和方法,但不能直接访问外部类的实例字段和方法(除非通过外部类的实例),因此静态嵌套类通常用于逻辑上与外部类相关,但不需要依赖外部类实例的场景。
2. 实现操作链表的方法
现在我们实现操作链表的几种方法:
package CS61B.Lecture4;public class SLList {private static class IntNode {public int val;public IntNode next;public IntNode(int val, IntNode next) {this.val = val;this.next = next;}}private IntNode head;public SLList(int val) {this.head = new IntNode(val, null);}// 获取首部节点值public int getFirst() {return this.head.val;}// 在首部添加节点public void addFirst(int val) {this.head = new IntNode(val, this.head);}// 删除首部节点并返回其值public int removeFirst() {if (this.head == null) {System.out.println("Already Empty!");return 0;}int val = this.head.val;this.head = this.head.next;return val;}// 获取尾部节点值public int getLast() {IntNode p = this.head;while (p.next != null) p = p.next;return p.val;}// 在尾部添加节点public void addLast(int val) {IntNode p = this.head;while (p.next != null) p = p.next;p.next = new IntNode(val, null);}// 删除尾部节点并返回其值public int removeLast() {if (this.head == null) {System.out.println("Already Empty!");return 0;}int val;if (this.head.next == null) {val = this.head.val;this.head = null;} else {IntNode p = this.head;while (p.next.next != null) p = p.next;val = p.next.val;p.next = null;}return val;}// 获取链表长度public int size() {return this.size(this.head); // 无法在该方法中直接实现递归,需要一个额外的辅助方法}// size()递归辅助方法private int size(IntNode p) {if (p.next == null) return 1;return 1 + size(p.next);}// 重写输出SLList对象的信息@Overridepublic String toString() {if (this.head == null) return "[]";StringBuilder res = new StringBuilder("SLList: [");IntNode p = this.head;while (p != null) {res.append(p.val);if (p.next != null) res.append(", ");else res.append("]");p = p.next;}return res.toString();}public static void main(String[] args) {SLList L = new SLList(5);L.addFirst(10);System.out.println(L.getFirst()); // 10L.addLast(15);System.out.println(L.getLast()); // 15System.out.println(L.size()); // 3System.out.println(L); // SLList: [10, 5, 15]System.out.println(L.removeFirst()); // 10System.out.println(L.removeLast()); // 15System.out.println(L.size()); // 1System.out.println(L); // SLList: [5]}
}
需要重点思考的是如何用递归的方式求解链表的长度,我们使用的是构造一个辅助方法 size(IntNode p) 使得用户可以通过 size() 方法直接获取链表长度。
3. 优化效率
我们现在关注 size() 方法,发现每次用户需要查看链表长度时都需要遍历一次链表,因此我们可以用一个变量实时记录链表的长度,也就是用空间换时间,这样每次查询只需要 O ( 1 ) O(1) O(1) 的时间复杂度,这也是上一节课中直接裸露递归的类 IntList 所无法实现的:
package CS61B.Lecture4;public class SLList {private static class IntNode {public int val;public IntNode next;public IntNode(int val, IntNode next) {this.val = val;this.next = next;}}private IntNode head;private int size;public SLList(int val) {this.head = new IntNode(val, null);this.size = 1;}// 获取首部节点值public int getFirst() {return this.head.val;}// 在首部添加节点public void addFirst(int val) {this.head = new IntNode(val, this.head);this.size++;}// 删除首部节点并返回其值public int removeFirst() {if (this.head == null) {System.out.println("Already Empty!");return 0;}int val = this.head.val;this.head = this.head.next;this.size--;return val;}// 获取尾部节点值public int getLast() {IntNode p = this.head;while (p.next != null) p = p.next;return p.val;}// 在尾部添加节点public void addLast(int val) {IntNode p = this.head;while (p.next != null) p = p.next;p.next = new IntNode(val, null);this.size++;}// 删除尾部节点并返回其值public int removeLast() {if (this.head == null) {System.out.println("Already Empty!");return 0;}int val;if (this.head.next == null) {val = this.head.val;this.head = null;} else {IntNode p = this.head;while (p.next.next != null) p = p.next;val = p.next.val;p.next = null;}this.size--;return val;}// 获取链表长度public int size() {return this.size;}// 重写输出SLList对象的信息@Overridepublic String toString() {if (this.head == null) return "[]";StringBuilder res = new StringBuilder("SLList: [");IntNode p = this.head;while (p != null) {res.append(p.val);if (p.next != null) res.append(", ");else res.append("]");p = p.next;}return res.toString();}public static void main(String[] args) {SLList L = new SLList(5);L.addFirst(10);System.out.println(L.getFirst()); // 10L.addLast(15);System.out.println(L.getLast()); // 15System.out.println(L.size()); // 3System.out.println(L); // SLList: [10, 5, 15]System.out.println(L.removeFirst()); // 10System.out.println(L.removeLast()); // 15System.out.println(L.size()); // 1System.out.println(L); // SLList: [5]}
}
4. 修复addLast()
首先我们写一个无参的构造方法,这样用户能够创建一个空链表:
public SLList() {this.head = null;this.size = 0;
}
现在我们尝试创建空链表,并用 addLast() 方法添加节点:
SLList L = new SLList();
L.addLast(10);
System.out.println(L);
会发现程序报错了,因为空链表的 this.head 就为 null,在执行 while (p.next != null) 时无法获取 p.next,因此我们可以这样修改 addLast() 方法:
// 在尾部添加节点
public void addLast(int val) {if (this.head == null) this.head = new IntNode(val, null);else {IntNode p = this.head;while (p.next != null) p = p.next;p.next = new IntNode(val, null);}this.size++;
}
5. 虚拟头节点
现在观察代码会发现有些方法里做了形如 if (this.head == null) 的特判,当工程代码量变大时如果习惯这么写会导致代码冗余复杂。
我们可以添加一个虚拟头节点,这样就不会存在头节点为空的情况,这种虚拟头节点也称哨兵节点,并且修改 remove 方法使其不返回节点值,因为可以通过 get 方法获取节点值。最后本节课的完整实现代码如下:
package CS61B.Lecture4;public class SLList {private static class IntNode {public int val;public IntNode next;public IntNode(int val, IntNode next) {this.val = val;this.next = next;}}private IntNode sentinel; // 第一个节点在sentinel.next上private int size;public SLList() {this.sentinel = new IntNode(0, null);this.size = 0;}public SLList(int val) {this.sentinel = new IntNode(0, new IntNode(val, null));this.size = 1;}// 获取首部节点值public int getFirst() {IntNode p = this.sentinel;if (p.next != null) p = p.next;return p.val;}// 在首部添加节点public void addFirst(int val) {this.sentinel.next = new IntNode(val, this.sentinel.next);this.size++;}// 删除首部节点public void removeFirst() {if (this.sentinel.next == null) return;this.sentinel.next = this.sentinel.next.next;this.size--;}// 获取尾部节点值public int getLast() {IntNode p = this.sentinel;while (p.next != null) p = p.next;return p.val;}// 在尾部添加节点public void addLast(int val) {IntNode p = this.sentinel;while (p.next != null) p = p.next;p.next = new IntNode(val, null);this.size++;}// 删除尾部节点public void removeLast() {if (this.sentinel.next == null) return;IntNode p = this.sentinel;while (p.next.next != null) p = p.next;p.next = null;this.size--;}// 获取链表长度public int size() {return this.size;}// 重写输出SLList对象的信息@Overridepublic String toString() {StringBuilder res = new StringBuilder("SLList: [");IntNode p = this.sentinel;while (p.next != null) {res.append(p.next.val);p = p.next;if (p.next != null) res.append(", ");}res.append("]");return res.toString();}public static void main(String[] args) {SLList L = new SLList();System.out.println(L.size()); // 0System.out.println(L); // SLList: []L.addLast(15);System.out.println(L.getLast()); // 15L.addFirst(10);System.out.println(L.getFirst()); // 10System.out.println(L.size()); // 2System.out.println(L); // SLList: [10, 15]L.removeLast();L.removeFirst();L.removeLast();L.removeFirst();System.out.println(L.size()); // 0System.out.println(L); // SLList: []}
}
相关文章:
【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记
本文内容为重写上一节课中的单链表,将其重构成更易于用户使用的链表,实现多种操作链表的方法。 1. 重构单链表SLList 在上一节课中编写的 IntList 类是裸露递归的形式,在 Java 中一般不会这么定义,因为这样用户可能需要非常了解…...
【科研绘图系列】R语言绘制小提琴图、散点图和韦恩图(violin scatter plot Venn)
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载画图1画图2画图3画图4画图5画图6画图7参考介绍 【科研绘图系列】R语言绘制小提琴图、散点图和韦恩图(violin & scatter plot & Venn) 加载R包 library…...
Linux中POSIX应用场景
Linux 提供了丰富的 POSIX(Portable Operating System Interface)标准接口,这些接口可以帮助开发者编写可移植、高效的应用程序。POSIX 标准定义了一系列系统调用和库函数,涵盖了文件操作、进程管理、线程管理、信号处理、同步机制…...
量子算法导论
重学了量子算法,不知道是温故而知新,还是之前的教材没有讲过这个概念。 如果把(图灵机)计算机比作一个查询机器,输入x通过f(x)作用得出结果,而查询的过程就是计算的过程。 中文解释…...
nasm - BasicWindow_64
文章目录 nasm - BasicWindow_64概述笔记my_build.batnasm_main.asmEND nasm - BasicWindow_64 概述 学习网上找到的demo. x64和x86的汇编源码还差挺多的。 x64的汇编代码不好写,细节整不对,程序就不运行。 如果要查为啥不运行,也要看和正向…...
SpringBoot:SSL证书部署+SpringBoot实现HTTPS安全访问
一、前言 SSL协议介于TCP/IP协议栈的第四层(传输层)和第七层(应用层)之间,为基于TCP的应用层协议(如HTTP)提供安全连接。它通过在客户端和服务器之间建立一个加密的通道,确保数据在传…...
selenium爬取苏宁易购平台某产品的评论
目录 selenium的介绍 1、 selenium是什么? 2、selenium的工作原理 3、如何使用selenium? webdriver浏览器驱动设置 关键步骤 代码 运行结果 注意事项 selenium的介绍 1、 selenium是什么? 用于Web应用程序测试的工具。可以驱动浏览…...
Spark提交任务
1、Spark提交任务到Yarn 1.1、DwKuduApp spark-submit --class com.io.etl.dwkudu.DwKuduApp \ --files /etl/etl-dwkudu/conf/doris.property,/etl/etl-dwkudu/conf/redis.property,/etl/etl-dwkudu/conf/log4j.property \ --master yarn --deploy-mode cluster \ --driver-…...
游戏引擎学习第113天
仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板:优化的基本过程 在游戏编程中,优化是一个非常重要的学习内容,尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度,以及如何提升其性能。在这个阶段,已经…...
token是什么
在自然语言处理(NLP)和机器学习的背景下,token 是指模型在处理文本时的最小单位。通常,这个单位可以是单词、字符,或者词的一部分。具体来说,token 的定义取决于你使用的模型和它的分词方式。 举个例子&am…...
23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成
文章目录 前言一、Spring AI 集成 DeepSeek1. 开发AI程序2. DeepSeek 大模型3. 集成 DeepSeek 大模型1. 接入前准备2. 引入依赖3. 工程配置4. 调用示例5. 小结 4. 集成第三方平台(已集成 DeepSeek 大模型)1. 接入前准备2. POM依赖3. 工程配置4. 调用示例…...
IPv6报头40字节具体怎么分配的?
目录 IPv6报头结构 字段详解 示例代码:IPv6报头的Python实现 输出示例 IPv6协议是为了解决IPv4地址耗尽问题而设计的下一代互联网协议。与IPv4相比,IPv6不仅提供了更大的地址空间,还简化了报头结构,提高了网络设备的处理效率。…...
驱动开发、移植
一、任务明确:把创龙MX8的驱动 按照我们的要求 然后移植到 我们的板子 1.Linux系统启动卡制作, sd卡 先按照 《用户手册—3-2-Linux系统启动卡制作及系统固化》 把创龙的Linux系统刷进去。 2. 把TLIMX8-EVM的板子过一遍 把刚刚烧好系统的sd卡插入 创…...
BFS与Flood Fill:算法原理、实现细节与复杂度分析
目录 1. 概述 2. BFS 的基本原理 3. Flood Fill 算法 4. BFS 实现 Flood Fill 的步骤 5. C 实现 6. 代码解析 7. 复杂度分析 8. 应用场景 总结 1. 概述 Flood Fill 算法是一种用于填充封闭区域的算法,常用于图像处理、绘图工具和游戏开发中。BFS(…...
计算机网络基础杂谈(局域网、ip、子网掩码、网关、DNS)
目录 1. 简单局域网的构成 2. IP 地址 3. 子网掩码 4. IP地址详解自定义IP 5. IP 地址详解 6. 网关 7. DNS 域名解析 8. ping 1. 简单局域网的构成 交换机是组建局域网最重要的设备,换句话说,没有交换机就没法搭建局域网 交换机不能让局域网连…...
雷龙CS SD NAND(贴片式TF卡)测评体验
一、产品概述 近期获赠雷龙科技(Longsto)推出的CS系列贴片式SD NAND存储解决方案,包含两片工业级贴片式NAND芯片(CSNP16GCR01-AOW)及全兼容转接板。该方案支持TF卡形态扩展,实现高可靠性嵌入式存储应用。 …...
【Alertmanager】alertmanager告警系统原理剖析与应用实战,应有尽有非常全面
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...
Java——权限修饰符
一、权限修饰符的继承访问规则 以下按访问范围从宽到窄排序: 修饰符同包同类同包子类同包非子类跨包子类跨包非子类public✔️✔️✔️✔️✔️protected✔️✔️✔️✔️❌默认(包级)✔️✔️✔️❌❌private✔️❌❌❌❌ 关键点…...
一周学会Flask3 Python Web开发-redirect重定向
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 前面我们学过渲染到模板页面,这个其实是一种内部的转发,浏览器地址栏地址没有变化。如果我们想重定向…...
python面向对象:方法
1. 实例方法 实例方法用于操作实例变量,必须包含 self 参数。 class Person:def __init__(self, name):self.name namedef greet(self):print(f"Hello, my name is {self.name}")person1 Person("Alice") person1.greet() # 输出ÿ…...
Linux 0.11源码深度解析:kernel/chr_drv/tty_io.c —— 终端I/O的控制中枢与行规约引擎
一、文件概述:用户与内核的交互桥梁tty_io.c 位于 /kernel/chr_drv目录,是Linux 0.11中终端(Terminal/TTY)输入输出的核心实现。在1991年的命令行时代,终端是用户与计算机交互的唯一窗口。这个文件负责管理键盘输入的…...
为什么WHERE中的函数调用会引发灾难?揭秘KES与Oracle的函数执行顺序之谜
在 WHERE 子句里放一个"有副作用"的函数,就像在高速公路上放了一个随机变道的司机——也许今天没事,但迟早会出事故。引言:一段看起来"理所当然"的代码在一次代码评审中,我看到了这样一条 SQL:SEL…...
杨校老师课堂之栈结构的专项训练
括号匹配 题目描述 假设表达式中允许包含圆括号和方括号两种括号,其嵌套的顺序随意,如()或[([][])]等为正确的匹配,[(])或(或(()))均为错误的匹配 本题的任务是检验一个给定的表达式中的括号是否匹配正确 输入一个只包含圆括号和方括号的字…...
项目实训——Werewolf-Agent 多智能体狼人杀中DSPy应用优化器优化
一、前言 上周,我在我们的项目中引入了dspy并使用它进行一个简单的测试,在测试过程中,我进行了几局游戏,发现预言家每次的输出结果都相差不大,这让我在玩起来比较无趣,因为在每个阶段,我都可以…...
终极安卓大屏控制方案:Escrcpy免费高效多屏管理工具
终极安卓大屏控制方案:Escrcpy免费高效多屏管理工具 【免费下载链接】escrcpy 📱 Display and control your Android device graphically with scrcpy. 项目地址: https://gitcode.com/GitHub_Trending/es/escrcpy 厌倦了在小屏幕上操作手机应用&…...
从零到一:计算机校招求职实战指南与面试宝典深度解析
从零到一:计算机校招求职实战指南与面试宝典深度解析 【免费下载链接】InterviewGuide 🔥🔥「InterviewGuide」是阿秀从校园->职场多年计算机自学过程的记录以及学弟学妹们计算机校招&秋招经验总结文章的汇总,包括但不限于…...
定义类的方法和CRC建模
在面向对象分析与设计(OOAD)中,定义类的方法 和 CRC 建模 是衔接“需求分析”与“详细设计”的关键技术。前者关注如何为类分配职责(行为),后者提供了一种轻量、协作式的建模方法来验证类的职责与协作关系。 一、定义类的方法 1.1 什么是类的方法? 类的方法(Method)…...
从注入到调用:一个完整的Unity il2cpp运行时Hook实战指南(附C++代码)
从注入到调用:一个完整的Unity il2cpp运行时Hook实战指南(附C代码) 在游戏开发与逆向工程领域,Unity引擎的il2cpp后端因其性能优势被广泛采用,但也带来了动态分析的独特挑战。本文将深入探讨如何通过运行时注入技术&am…...
python argparse
### 聊聊 Python 里的 argparse:命令行参数处理那点事 1. 它是什么 argparse 是 Python 标准库里的一个模块,专门用来解析命令行参数。有人可能会说,处理参数不就是 sys.argv 切一切、判断一下吗?确实可以,但那种方式就…...
深度学习中的Dropout正则化技术与Keras实践
1. 理解Dropout正则化的核心价值在深度学习模型训练过程中,过拟合就像一位记忆力超强却缺乏理解力的学生——它能完美复述训练数据中的每个细节,却无法应对新问题的变化。2012年由Hinton团队提出的Dropout技术,通过随机"关闭"神经网…...
