【UCB CS 61B SP24】Lecture 4 - Lists 2: SLLists学习笔记

news2025/2/21 14:04:29

本文内容为重写上一节课中的单链表,将其重构成更易于用户使用的链表,实现多种操作链表的方法。

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: []
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu系统3分钟本地部署DeepSeek-R1蒸馏模型,支持联网

本文提供Ubuntu ollama Page Assist,3步快速安装DeepSeek-R1蒸馏模型,支持联网,支持API。 目录 DeepSeek-R1安装分3步: Step 1, 安装ollama(已安装可忽略) Step 2, 下载DeepSeek-R1模型 Step 3, 从…

谷粒商城—分布式高级②.md

认证服务 1. 环境搭建 创建gulimall-auth-server模块,导依赖,引入login.html和reg.html,并把静态资源放到nginx的static目录下 2. 注册功能 (1) 验证码倒计时 //点击发送验证码按钮触发下面函数 $("#sendCode").click(function () {//如果有disabled,说明最近…

C语言-----操作符的分类

1. 操作符的分类 •算术操作符&#xff1a; 、- 、 * 、/、% 移位操作符:<< >> 位操作符: & | ^ 赋值操作符: / 、 % 、 、- 、 *、/、 %、 <<、 >>、&、| 、 ^ 单⽬操作符&#xff1a;&#xff01;、 、- 、 & 、 * 、 、 …

PWM(脉宽调制)技术详解:从基础到应用实践示例

PWM&#xff08;脉宽调制&#xff09;技术详解&#xff1a;从基础到应用实践示例 目录 PWM&#xff08;脉宽调制&#xff09;技术详解&#xff1a;从基础到应用实践示例学前思考&#xff1a;一、PWM概述二、PWM的基本原理三、PWM的应用场景四、PWM的硬件配置与使用五、PWM的编程…

AI智能成长系统 | 应用探讨研究

研究背景 在现代家庭中&#xff0c;三岁宝宝的成长环境日益复杂。由于宝宝每天接触的人群多样&#xff0c;包括家庭成员、同龄小朋友以及可能的陌生人&#xff0c;其语言环境也相应地变得复杂多变。这种环境下&#xff0c;宝宝很容易接触到一些不适宜的语言&#xff0c;即俗称…

java 网络安全感知 网络安全学java

&#x1f345; 点击文末小卡片 &#xff0c;免费获取网络安全全套资料&#xff0c;资料在手&#xff0c;涨薪更快 实验五 java网络编程及安全 实验内容 1&#xff0e;掌握Socket程序的编写&#xff1b;2&#xff0e;掌握密码技术的使用&#xff1b;3&#xff0e;设计安全传输…

VisionMaster4.4 python脚本 图像处理 转换函数 爱之初体验

最近有接触过一丢丢VM4.3的模块开发. 一直有把python图像处理部分模块移植进来的打算 不过时间不够没来得及折腾.偶尔发现4.4支持py脚本 于是拿来折腾.一下午. 发现4.4支持python脚本,好开心. 首先安装VM4.4 注意一定要是4.4 打开后拖了一个模块. 但是发现import numpy imp…

python-leetcode 40.二叉树的层序遍历

题目&#xff1a; 给定二叉树的根节点root,返回其节点值得层序遍历&#xff08;即逐层从左到右访问所有节点&#xff09; 方法&#xff1a;广度优先搜索 # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, val0, leftNone, rightNon…

蓝桥杯学习大纲

&#xff08;致酷德与热爱算法、编程的小伙伴们&#xff09; 在查阅了相当多的资料后&#xff0c;发现没有那篇博客、文章很符合我们备战蓝桥杯的学习路径。所以&#xff0c;干脆自己整理一篇&#xff0c;欢迎大家补充&#xff01; 一、蓝桥必备高频考点 我们以此为重点学习…

小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统,不需要降级 v1.0.91 (2025)

小米AX3000T 路由器如何开启 SSH 安装 OpenWRT 系统&#xff0c;不需要降级 v1.0.91 &#xff08;2025&#xff09; 本文内容需要你有一定的 Linux 操作基础&#xff0c;最好是程序员那种&#xff0c;英文水平足够用才行。一般人不需要使用这么复杂的路由器操作系统&#xff0c…

水基试剂,湿式化学,清水,干式化学,干粉,卤烃清洁剂,二氧化碳灭火器UL8检测报告标准讲解:

水基试剂&#xff0c;湿式化学&#xff0c;清水&#xff0c;干式化学&#xff0c;干粉&#xff0c;卤烃清洁剂&#xff0c;二氧化碳灭火器UL检测报告标准讲解&#xff1a; 本政策涵盖的灭火器 水基试剂灭火器 水基试剂灭火器使用水基试剂带走燃烧三要素中的热量要素&#xf…

汽车免拆诊断案例 | 2010 款路虎揽胜车空调偶尔出风异常

故障现象  一辆2010款路虎揽胜车&#xff0c;搭载5.0 L发动机&#xff0c;累计行驶里程约为16万km。车主反映&#xff0c;接通空调开关后&#xff0c;有时出风忽大忽小&#xff0c;有时不出风&#xff0c;有时要等2 min左右才出风&#xff1b;有时两三天出现一次&#xff0c;…

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 项目。以下是一…

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

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

DeepSeek智能测试助手:分类+推理+导出一站式工具

前言 测试开发工程师在日常工作中需要处理大量测试文档&#xff0c;并且这些文档需要被高效分类、清洗和管理&#xff0c;同时结合强大的 AI 推理能力&#xff08;如 DeepSeek 模型&#xff09;进行智能化处理和分析。为此&#xff0c;我们开发了一款基于 PyQt5 的 GUI 工具&a…

计算机毕业设计Python农产品推荐系统 农产品爬虫 农产品可视化 农产品大数据(源码+LW文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

「正版软件」PDF Reader - 专业 PDF 编辑阅读工具软件

PDF Reader 轻松查看、编辑、批注、转换、数字签名和管理 PDF 文件&#xff0c;以提高工作效率并充分利用 PDF 文档。 像专业人士一样编辑 PDF 编辑 PDF 文本 轻松添加、删除或修改 PDF 文档中的原始文本以更正错误。自定义文本属性&#xff0c;如颜色、字体大小、样式和粗细。…

日期类(完全讲解版)

1. 类的设计思想 Date 类的设计目的是为了封装和处理日期信息&#xff0c;它提供了对日期的基本操作&#xff0c;如日期加减、日期比较、日期合法性检查等。类中的私有成员 int _year, int _month, int _day 存储了日期的年、月、日。 类的声明和构造 Date 类的声明&#xff1…

洛谷 P10726 [GESP202406 八级] 空间跳跃 C++ 完整题解

一、题目链接 P10726 [GESP202406 八级] 空间跳跃 - 洛谷 二、解题思路 我们要对输入的挡板进行排序&#xff0c;按高度从高到低&#xff08;从小到大&#xff09;。 排序之后s和t都要更新。 struct Baffle {int l, r;int h;int id; } b[1005];void Sort() {sort(b 1, b 1 n…

【设计模式精讲】创建型模式之工厂方法模式(简单工厂、工厂方法)

文章目录 第四章 创建型模式4.2 工厂方法模式4.2.1 需求: 模拟发放奖品业务4.2.2 原始开发方式4.2.3 简单工厂模式4.2.3.1 简单工厂模式介绍4.2.3.2 简单工厂原理4.2.3.3 简单工厂模式重构代码4.2.3.4 简单工厂模式总结 4.2.4 工厂方法模式4.2.4.1 工厂方法模式介绍4.2.4.2 工厂…