算法通关村|第一关·青铜|小白也能学会的链表(单链表篇)

学习目标:用Java实现的(带头结点的)单链表——构造+CRUD;
文章目录:
一、头结点/虚拟结点
二、Java实现代码
(一) Node的实现特点
1. 内部类
2. 为什么Node类被定义为static
3. Node类的实现写法
(二) static的更多妙用
1. 操作函数都定义为static的原因
2. 为什么dummyHead和构造函数不用static修饰?
(三) 函数中先判断空表的原因
(四) insertNode、deleteNode函数流程(带dummyHead从而统一了操作)
1. 插入结点
2. 删除结点

一、头结点/虚拟结点

这里“虚拟结点 dummy node”的概念其实是大学教材《算法与数据结构》中描述的“附加头结点”,主要目的是为了简化链表操作,使对链表的所有结点的操作更为统一。以及这个结点的data值不会被使用,初始化为0或-1等等都是可以的。不同语境下术语有所差异,主要是理解作用和意思。
image.png

二、Java实现代码

/**
 * 表示带有虚拟头节点的基础单链表。
 * 提供插入、删除和获取链表长度的方法。
 */
public class SinglyLinkList {

    /**
     * 链表中的节点表示。
     * 每个节点包含数据和对下一个节点的引用。
     */
    static class Node {
        /** 节点中存储的数据 */
        final int data;
        
        /** 链表中的下一个节点 */
        Node next;

        /**
         * 使用给定的数据构造新节点。
         *
         * @param data 存储在节点中的数据
         */
        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }

    /** 链表的虚拟头节点 */
    public Node dummyHead;

    /**
     * 使用指定的虚拟头节点构造一个空的链表。
     *
     * @param dummyHead 虚拟头节点
     */
    public SinglyLinkList(Node dummyHead) {
        this.dummyHead = dummyHead;
        this.dummyHead.next = null;
    }

    /**
     * 返回链表的长度。
     *
     * @param dummyHead 链表的虚拟头节点
     * @return 链表的长度
     */
    public static int getLength(Node dummyHead) {
        Node flag = dummyHead;
        int count = 0;
        while (flag.next != null) {
            flag = flag.next;
            count++;
        }
        return count;
    }

    /**
     * 在链表的指定位置插入节点。
     *
     * @param dummyHead  链表的虚拟头节点
     * @param nodeInsert 要插入的节点
     * @param position   节点应插入的位置
     * @return 更新后链表的虚拟头节点
     */
    public static Node insertNode(Node dummyHead, Node nodeInsert, int position) {
        int size = getLength(dummyHead);
        if (position < 1 || position > size+1) {
            System.out.println("位置参数越界");
            return dummyHead;
        }
        Node flag = dummyHead;
        int count = 0;
        while(count < position - 1) {
            flag = flag.next;
            count++;
        }
        nodeInsert.next = flag.next;
        flag.next = nodeInsert;
        return dummyHead;
    }

    /**
     * 从链表的指定位置删除节点。
     *
     * @param dummyHead 链表的虚拟头节点
     * @param position  节点应删除的位置
     * @return 更新后链表的虚拟头节点
     */
    public static Node deleteNode(Node dummyHead, int position) {
        if (dummyHead == null) {
            System.out.println("空表");
            return null;
        }
        int size = getLength(dummyHead);
        if (position < 1 || position > size) {
            System.out.println("位置参数越界");
            return dummyHead;
        }
        Node flag = dummyHead;
        int count = 0;
        while(count < position - 1) {
            flag = flag.next;
            count++;
        }
        flag.next = flag.next.next;
        return dummyHead;
    }
}

(一)Node的实现特点

1. 内部类

将节点定义为内部类是一种组织代码的方式,这样可以将与某个类紧密相关的辅助类隐藏在主类中,使外部代码更加简洁。对于当前链表的情况,Node仅在链表类内部使用,因此将其作为内部类是有意义的。

2. 为什么Node类被定义为static
  • 独立性:当前内部类不依赖于其外部类的实例。换句话说,static内部类不持有对其外部类的任何实例的 引用。这对于节点来说是很有意义的,因为每个节点并不需要知道它所属的链表实例。
  • 内存效率:由于非static内部类会持有对其外部类实例的引用,因此它通常会占用更多的内存。对于链表这样的数据结构,可能会包含大量的节点,所以使用static内部类有助于减少内存消耗。
  • 可访问性:如果您希望外部类能够直接访问内部类,即使没有外部类的实例,那么内部类必须是static的。在链表的上下文中,您可能希望能够创建一个新的节点,而不必首先创建链表的实例。
3. Node类的实现写法
  • 这里写的是解算法题时适合的实现方法,并不符合OOAD编码规范,但简单清晰,可读性更高;

(二)static的更多妙用

1. 操作函数都定义为static的原因

static修饰的操作是属于类本身的,不依赖于任何类的SinglyLinkList实例,而只依赖于它的输入参数。这样,就可以不需要创建该类的实例即可调用这个方法。

2. 为什么dummyHead和构造函数不用static修饰?
  • 头结点,不能static,是因为不可能所有类共用一个dummyHead;
  • 而Java中,构造函数(或称为构造器)是用来初始化新创建的对象的特殊方法。不能被标记为static,因为它们的目的就是为新创建的对象实例设置初始状态;

(三)函数中先判断空表的原因

在任何其他操作之前检查list是否为空是一个很好的做法,原因如下:

  • 效率:它可以防止不必要的计算,可以立即返回。
  • 错误预防:通过在开始时检查可能导致错误的条件,我们可以防止方法后期可能出现的潜在问题。

(四)insertNode、deleteNode函数流程(带dummyHead从而统一了操作)

1. 插入结点

public static Node insertNode(Node dummyHead, Node nodeInsert, int position);

  • 验证插入位置的合法性: 1 ~ (size+1);

  • 合法插入位置的情况下,继续进行一下操作:

    1. 因为要把参数结点插入到第positon个位置,flag后移到(position-1)的位置上(提前在位置有效性做了限制,flag是一定可以后移的);
    2. 执行参数结点的插入操作;
  • 考虑头结点统一操作的作用:极限操作下(1/(size+1)位),因为flag.next = null,从而统一了操作;

2. 删除结点

public static Node deleteNode(Node dummyHead, int position);

  1. 空表判断;

  2. 验证删除位置的合法性: 1 ~ size

  3. 合法删除位置的情况下,继续进行一下操作:

    • 删除第position位置的结点,依然是flag后移到(position-1)的位置上;

    • 执行参数结点的插入操作

  4. 考虑头结点统一操作的作用:极限操作下(1/size位),因为flag.next = null,从而统一了操作;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值