【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对象的信息
    @Override
    public 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());  // 10

        L.addLast(15);
        System.out.println(L.getLast());  // 15

        System.out.println(L.size());  // 3
        System.out.println(L);  // SLList: [10, 5, 15]

        System.out.println(L.removeFirst());  // 10
        System.out.println(L.removeLast());  // 15
        System.out.println(L.size());  // 1
        System.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对象的信息
    @Override
    public 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());  // 10

        L.addLast(15);
        System.out.println(L.getLast());  // 15

        System.out.println(L.size());  // 3
        System.out.println(L);  // SLList: [10, 5, 15]

        System.out.println(L.removeFirst());  // 10
        System.out.println(L.removeLast());  // 15
        System.out.println(L.size());  // 1
        System.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对象的信息
    @Override
    public 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());  // 0
        System.out.println(L);  // SLList: []

        L.addLast(15);
        System.out.println(L.getLast());  // 15

        L.addFirst(10);
        System.out.println(L.getFirst());  // 10

        System.out.println(L.size());  // 2
        System.out.println(L);  // SLList: [10, 15]

        L.removeLast();
        L.removeFirst();
        L.removeLast();
        L.removeFirst();
        System.out.println(L.size());  // 0
        System.out.println(L);  // SLList: []
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPT-Sovits:语音克隆训练-遇坑解决

前言 本来以为3050完全无法执行GPT-Sovits训练的,但经过实践发现其实是可以,并且仅花费了十数分钟便成功训练和推理验证了自己的语音模型。 官方笔记:GPT-SoVITS指南 语雀 项目地址:https://github.com/RVC-Boss/GPT-SoVITS 本人…

如何调用 DeepSeek API:详细教程与示例

目录 一、准备工作 二、DeepSeek API 调用步骤 1. 选择 API 端点 2. 构建 API 请求 3. 发送请求并处理响应 三、Python 示例:调用 DeepSeek API 1. 安装依赖 2. 编写代码 3. 运行代码 四、常见问题及解决方法 1. API 调用返回 401 错误 2. API 调用返回…

成员函数定义后面加const是什么功能:C++中const成员函数的作用

成员函数定义后面加const是什么功能:C中const成员函数的作用 前言C中const成员函数的作用总结 前言 在PX4的代码中的位置控制模块中,有这样一个成员函数 void getAttitudeSetpoint(vehicle_attitude_setpoint_s &attitude_setpoint) const;该函数的…

数据结构-----双向链表

一、双向循环列表 head.h #ifndef __head_h__ #define __head_h__ #include <stdio.h> #include <string.h>…

基于Flask的第七次人口普查数据分析系统的设计与实现

【Flask】基于Flask的第七次人口普查数据分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 基于Flask的人口普查可视化分析系统 二、项目界面展示 登录/注册 首页/详情 …

纯手工搭建整套CI/CD流水线指南

目录 一、前言 二、环境准备 1、服务器开荒&#xff08;192.168.1.200&#xff09; 2、离线资源清单&#xff08;提前用U盘拷好&#xff09; 三、硬核安装&#xff1a;比拧螺丝还细的步骤 Step1&#xff1a;搭建GitLab&#xff08;注意&#xff01;这是只内存饕餮&#xf…

RocketMQ - 常见问题

RocketMQ常见问题 文章目录 RocketMQ常见问题一&#xff1a;消息幂等问题1&#xff1a;什么是消费幂等2&#xff1a;消息重复的场景分析2.1&#xff1a;发送时消息重复2.2&#xff1a;消费时消息重复2.3&#xff1a;Rebalance时消息重复 3&#xff1a;通用解决方案3.1&#xff…

macos sequoia 禁用 ctrl+enter 打开鼠标右键菜单功能

macos sequoia默认ctrlenter会打开鼠标右键菜单&#xff0c;使得很多软件有冲突。关闭方法&#xff1a; end

【Python爬虫(29)】爬虫数据生命线:质量评估与监控全解

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…

Three.js 快速入门教程【二】透视投影相机

系列文章目录 系列文章目录 Three.js 快速入门教程【一】开启你的 3D Web 开发之旅 Three.js 快速入门教程【二】透视投影相机 Three.js 快速入门教程【三】渲染器 Three.js 快速入门教程【四】三维坐标系 Three.js 快速入门教程【五】动画渲染循环 Three.js 快速入门教程【六…

循环神经网络RNN原理与优化

目录 前言 RNN背景 RNN原理 上半部分&#xff1a;RNN结构及按时间线展开图 下半部分&#xff1a;RNN在不同时刻的网络连接和计算过程 LSTM RNN存在的问题 LSTM的结构与原理 数学表达层面 与RNN对比优势 应用场景拓展 从简易但严谨的代码来看RNN和LSTM RNN LSTM 前言 绕循环神经…

Mac arm架构使用 Yarn 全局安装 Vue CLI

dgqdgqdeMacBook-Pro spid-admin % vue --version zsh: command not found: vue要使用 Yarn 安装 Vue CLI&#xff0c;你可以执行以下命令&#xff1a; yarn global add vue/cli这个命令会全局安装 Vue CLI&#xff0c;让你可以使用 vue 命令创建、管理 Vue.js 项目。以下是一…

TensorFlow深度学习实战(8)——卷积神经网络

TensorFlow深度学习实战&#xff08;8&#xff09;——卷积神经网络 0. 前言1. 全连接网络的缺陷2. 卷积神经网络2.1 卷积神经网络的基本概念2.2 TensorFlow 中的卷积层2.3 TensorFlow 中的池化层2.4 卷积神经网络总结 3. 构建卷积神经网络3.1 LeNet3.2 使用 TensorFlow 实现 L…

.NET + Vue3 的前后端项目在IIS的发布

目录 一、发布准备 1、安装 IIS 2、安装 Windows Hosting Bundle&#xff08;.NET Core 托管捆绑包&#xff09; 3、安装 IIS URL Rewrite 二、项目发布 1、后端项目发布 2、前端项目发布 3、将项目部署到 IIS中 三、网站配置 1、IP配置 2、防火墙配置 3、跨域配置…

电脑想安装 Windows 11 需要开启 TPM 2.0 怎么办?

尽管 TPM 2.0 已经内置在许多新电脑中&#xff0c;但很多人并不知道如何激活这一功能&#xff0c;甚至完全忽略了它的存在。其实&#xff0c;只需简单的几步操作&#xff0c;你就能开启这项强大的安全特性&#xff0c;为你的数字生活增添一层坚固的防护屏障。无论你是普通用户还…

嵌入式开发岗位认识

目录 1.核心定义2.岗位方向3.行业方向4.技术方向5.工作职责6.核心技能7.等级标准8.优势与劣势9.市场薪资10. 发展路径11. 市场趋势12. 技术趋势 1.核心定义 嵌入式系统&#xff1a; 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪的专用计算机系统 特点…

爱普生 SG-8101CE 可编程晶振在笔记本电脑的应用

在笔记本电脑的精密架构中&#xff0c;每一个微小的元件都如同精密仪器中的齿轮&#xff0c;虽小却对整体性能起着关键作用。如今的笔记本电脑早已不再局限于简单的办公用途&#xff0c;其功能愈发丰富多样。从日常轻松的文字处理、网页浏览&#xff0c;到专业领域中对图形处理…

Python VsCode DeepSeek接入

Python VsCode DeepSeek接入 创建API key 首先进入DeepSeek官网&#xff0c;https://www.deepseek.com/ 点击左侧“API Keys”&#xff0c;创建API key&#xff0c;输出名称为“AI” 点击“创建"&#xff0c;将API key保存&#xff0c;复制在其它地方。 在VsCode中下载…

基于eBPF的全栈可观测性系统:重新定义云原生环境诊断范式

引言&#xff1a;突破传统APM的性能桎梏 某头部电商平台采用eBPF重构可观测体系后&#xff0c;生产环境指标采集性能提升327倍&#xff1a;百万QPS场景下传统代理模式CPU占用达63%&#xff0c;而eBPF直采方案仅消耗0.9%内核资源。核心业务的全链路追踪时延从900μs降至18μs&a…

java项目之风顺农场供销一体系统的设计与实现(源码+文档)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的风顺农场供销一体系统的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 风顺农场供销…