线性表是数据结构中最基础、最重要的概念之一,而链表实现则是其实现方式中的一种。

线性表的基本概念

什么是线性表

线性表(Linear List)是n个具有相同特性的数据元素的有限序列。在逻辑结构上,线性表呈现出一对一的线性关系,就像一条直线上的点,每个元素都有且仅有一个直接前驱和一个直接后继(除了首尾元素)。

线性表的存储方式

线性表在逻辑上是线性的,但在物理存储上可以有多种形式:

  • 顺序存储:使用连续的存储空间(数组实现)

  • 链式存储:使用不连续的存储空间通过指针连接(链表实现)

本文重点讨论顺序存储的实现方式。

链表的原理与特点

链表的本质

链表是一种非连续、非顺序的线性数据结构,其核心是通过「节点」存储数据,并通过节点中的「指针 / 引用」建立节点间的逻辑关系,从而模拟线性表的有序性。

链表的核心特性

一、单链表(基础款)

优点:

  • 插入删除高效:找到目标节点后仅需修改指针指向,时间复杂度 O (1)(查找节点环节为 O (n))

  • 容量动态:无需预先分配固定内存空间,节点随数据增减动态创建 / 释放,长度无硬性限制

  • 内存利用率高:非连续存储模式,可充分利用内存碎片,避免连续大内存块的浪费

缺点:

  • 随机访问低效:无下标机制,访问第 n 个元素需从表头顺序遍历,时间复杂度 O (n)

  • 存储密度低:每个节点需额外存储「后继指针(next)」,增加内存开销

  • 缓存不友好:节点分散存储,CPU 缓存无法批量加载数据,命中率远低于数组

  • 操作风险高:指针修改易引发空指针、死循环等问题,调试难度较大

二、双向链表(扩展款)

专属优点:

  • 遍历灵活:同时维护「前驱指针(prev)」和「后继指针(next)」,支持正向 / 反向双向遍历

  • 删除效率更高:删除节点时无需额外遍历查找前驱节点,直接通过 prev 指针定位,操作复杂度 O (1)

专属缺点:

  • 指针开销翻倍:需同时维护两个指针,内存占用比单链表更高

  • 插入 / 修改逻辑复杂:新增 / 删除节点时需同步更新前后节点的 prev 和 next 指针,代码逻辑复杂度提升

三、循环链表(特殊款)

专属优点:

  • 无首尾边界:尾节点指针指向头节点,形成环形结构,适配环形遍历场景(如约瑟夫环、环形队列)

专属缺点:

  • 易触发死循环:遍历过程若未设置终止条件,会无限循环遍历

  • 首尾判断复杂:需额外标记或计数区分 “遍历完成”,增加业务逻辑复杂度

线性表ADT方法

我们主要实现以下方法

方法原型

语义

时间复杂度*

add(E e): boolean

尾插元素 e

均摊 O(1)

add(int i, E e): boolean

在 i 号位置插入 e,后续元素后移

O(n)

get(int i): E

读取位序 i(0≤i<n)的元素

O(1)

set(int i, E e): E

修改位序 i 的元素为 e,返回旧值

O(1)

remove(int i): E

删除 i 号位置,返回被删元素;后续元素前移

O(n)

indexOf(E e): int

顺序查找首个等于 e 的位序;不存在返回 –1

O(n)

clear(): void

清空所有元素(size 置 0,可置 null 助 GC)

O(n)O(1)

length(): int

返回当前的元素数量

O(1)

实现链表

我们也采用接口和实现分离的设计来实现一个单向链表

实现接口层

package org.example;

/**
 * @author nanak
 */
public interface LinkList<E> {
    boolean add(E e);
    boolean add(int i, E e);
    E get(int i);
    E set(int i, E e);
    E remove(int i);
    int indexOf(E e);
    void clear();
    int length();
}

实现接口

package org.example;

/**
 * @author nanak
 */
public class LinkListImpl<E> implements LinkList<E> {
    private Node<E> first;
    private Node<E> last;
    private int count = 0;

    @Override
    public boolean add(E e) {
        // 采用尾插法
        if (this.first == null) {
            this.first = new Node<>(e, null);
            this.count++;
            this.last = this.first;
            return true;
        }
        this.last.next = new Node<>(e, null);
        this.last = this.last.next;
        this.count++;
        return true;
    }

    @Override
    public boolean add(int i, E e) {
        if (i < 0 || i > this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        // 如果是0
        if (i == 0) {
            Node<E> node = new Node<>(e, this.first);
            this.first = node;
            this.count++;
            if (this.last == null) {
                this.last = this.first;
            }
            return true;
        }
        // 如果是this.count, 说明是尾部
        if (i == this.count) {
            add(e);
            return true;
        }
        Node<E> node = this.first;
        // 移动到前驱节点(i-1)
        for (int j = 0; j < i - 1; j++) {
            node = node.next;
        }
        node.next = new Node<>(e, node.next);
        this.count++;
        return true;
    }

    @Override
    public E get(int i) {
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        Node<E> node = this.first;
        // 移动到目标节点
        for (int j = 0; j < i; j++) {
            node = node.next;
        }
        return node.data;
    }

    @Override
    public E set(int i, E e) {
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        Node<E> node = this.first;
        // 移动到目标节点
        for (int j = 0; j < i; j++) {
            node = node.next;
        }
        E old = node.data;
        node.data = e;
        return old;
    }

    @Override
    public E remove(int i) {
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        Node<E> node = this.first;
        // 处理头删
        if (i == 0) {
            this.first = this.first.next;
            this.count--;
            return node.data;
        }
        for (int j = 0; j < i - 1; j++) {
            node = node.next;
        }
        Node<E> temp = node.next;
        node.next = node.next.next;
        this.count--;
        // 处理尾删
        if (node.next == null) {
            this.last = node;
        }
        return temp.data;

    }

    @Override
    public int indexOf(E e) {
        // 特殊情况 e==null或者node.data == null
        Node<E> node = this.first;
        for (int i = 0; i < this.count; i++) {
            if (node.data == null && e == null) {
                return i;
            }
            if (node.data != null && node.data.equals(e)) {
                return i;
            }
            node = node.next;
        }
        return -1;
    }

    @Override
    public void clear() {
        Node<E> current = this.first;
        while (current != null) {
            Node<E> nextNode = current.next;
            // 释放当前节点的所有引用
            current.data = null;
            current.next = null;
            // 移动到下一个节点
            current = nextNode;
        }

        this.first = null;
        this.last = null;
        this.count = 0;
    }

    @Override
    public int length() {
        return this.count;
    }

    class Node<E> {
        E data;
        Node<E> next;

        public Node(E data, Node<E> next) {
            this.data = data;
            this.next = next;
        }
    }
}

进行测试

package org.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

/**
 * LinkListImpl 单元测试
 * 覆盖:add/remove/get/set/indexOf/clear/length 所有核心方法
 */
class LinkListImplTest {

    private LinkList<Integer> list;

    // 每个测试方法执行前初始化空链表
    @BeforeEach
    void setUp() {
        list = new LinkListImpl<>();
    }

    // ==================== add(E e) 尾插法测试 ====================
    @Test
    void testAdd_E_EmptyList() {
        // 空链表尾插
        assertTrue(list.add(10));
        assertEquals(1, list.length());
        assertEquals(10, list.get(0));
        assertSame(list.get(0), list.get(0)); // 验证节点数据正确
    }

    @Test
    void testAdd_E_MultiElements() {
        // 多元素尾插
        list.add(1);
        list.add(2);
        list.add(3);
        assertEquals(3, list.length());
        assertEquals(1, list.get(0));
        assertEquals(2, list.get(1));
        assertEquals(3, list.get(2));
    }

    // ==================== add(int i, E e) 指定索引插入测试 ====================
    @Test
    void testAdd_Index_E_Head() {
        // 头插(i=0)
        list.add(0, 1);
        list.add(0, 2);
        assertEquals(2, list.length());
        assertEquals(2, list.get(0));
        assertEquals(1, list.get(1));
    }

    @Test
    void testAdd_Index_E_Tail() {
        // 尾插(i=count)
        list.add(0, 1);
        list.add(1, 2); // i=count=1
        list.add(2, 3); // i=count=2
        assertEquals(3, list.length());
        assertEquals(3, list.get(2));
    }

    @Test
    void testAdd_Index_E_Middle() {
        // 中间插入
        list.add(1);
        list.add(3);
        list.add(1, 2); // 插入到索引1
        assertEquals(3, list.length());
        assertEquals(1, list.get(0));
        assertEquals(2, list.get(1));
        assertEquals(3, list.get(2));
    }

    @Test
    void testAdd_Index_E_OutOfBounds() {
        // 越界插入(i=-1 / i>count)
        assertThrows(IndexOutOfBoundsException.class, () -> list.add(-1, 1));
        assertThrows(IndexOutOfBoundsException.class, () -> list.add(1, 1)); // 空链表i=1>count=0
        list.add(0, 1);
        assertThrows(IndexOutOfBoundsException.class, () -> list.add(2, 2)); // count=1,i=2>1
    }

    // ==================== get(int i) 获取元素测试 ====================
    @Test
    void testGet_ValidIndex() {
        list.add(10);
        list.add(20);
        assertEquals(10, list.get(0));
        assertEquals(20, list.get(1));
    }

    @Test
    void testGet_OutOfBounds() {
        // 越界获取(i=-1 / i>=count)
        assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1));
        assertThrows(IndexOutOfBoundsException.class, () -> list.get(0)); // 空链表i=0>=count=0
        list.add(1);
        assertThrows(IndexOutOfBoundsException.class, () -> list.get(1)); // count=1,i=1>=1
    }

    // ==================== set(int i, E e) 修改元素测试 ====================
    @Test
    void testSet_ValidIndex() {
        list.add(1);
        list.add(2);
        // 修改索引1的元素
        Integer old = list.set(1, 3);
        assertEquals(2, old); // 返回旧值
        assertEquals(3, list.get(1)); // 验证新值
    }

    @Test
    void testSet_OutOfBounds() {
        assertThrows(IndexOutOfBoundsException.class, () -> list.set(-1, 1));
        assertThrows(IndexOutOfBoundsException.class, () -> list.set(0, 1)); // 空链表
        list.add(1);
        assertThrows(IndexOutOfBoundsException.class, () -> list.set(1, 2)); // count=1,i=1>=1
    }

    // ==================== remove(int i) 删除元素测试 ====================
    @Test
    void testRemove_Head() {
        // 头删
        list.add(1);
        list.add(2);
        Integer removed = list.remove(0);
        assertEquals(1, removed);
        assertEquals(1, list.length());
        assertEquals(2, list.get(0));
    }

    @Test
    void testRemove_Tail() {
        // 尾删
        list.add(1);
        list.add(2);
        list.add(3);
        Integer removed = list.remove(2);
        assertEquals(3, removed);
        assertEquals(2, list.length());
        assertEquals(2, list.get(1));
        // 验证last指针更新
        list.add(4); // 尾插新元素,验证last正确
        assertEquals(4, list.get(2));
    }

    @Test
    void testRemove_Middle() {
        // 中间删除
        list.add(1);
        list.add(2);
        list.add(3);
        Integer removed = list.remove(1);
        assertEquals(2, removed);
        assertEquals(2, list.length());
        assertEquals(1, list.get(0));
        assertEquals(3, list.get(1));
    }

    @Test
    void testRemove_OnlyOneElement() {
        // 删除唯一元素(验证first/last置空)
        list.add(1);
        list.remove(0);
        assertEquals(0, list.length());
        assertThrows(IndexOutOfBoundsException.class, () -> list.get(0));
    }

    @Test
    void testRemove_OutOfBounds() {
        assertThrows(IndexOutOfBoundsException.class, () -> list.remove(-1));
        assertThrows(IndexOutOfBoundsException.class, () -> list.remove(0)); // 空链表
        list.add(1);
        assertThrows(IndexOutOfBoundsException.class, () -> list.remove(1)); // count=1,i=1>=1
    }

    // ==================== indexOf(E e) 查找索引测试 ====================
    @Test
    void testIndexOf_ExistElement() {
        list.add(1);
        list.add(2);
        list.add(3);
        assertEquals(0, list.indexOf(1));
        assertEquals(1, list.indexOf(2));
        assertEquals(2, list.indexOf(3));
    }

    @Test
    void testIndexOf_NullElement() {
        // 测试null元素
        LinkList<String> strList = new LinkListImpl<>();
        strList.add(null);
        strList.add("test");
        assertEquals(0, strList.indexOf(null));
        assertEquals(1, strList.indexOf("test"));
        assertEquals(-1, strList.indexOf("null"));
    }

    @Test
    void testIndexOf_NotExistElement() {
        list.add(1);
        list.add(2);
        assertEquals(-1, list.indexOf(3));
        assertEquals(-1, list.indexOf(null)); // 链表无null元素
    }

    // ==================== clear() 清空链表测试 ====================
    @Test
    void testClear() {
        list.add(1);
        list.add(2);
        list.clear();
        assertEquals(0, list.length());
        assertThrows(IndexOutOfBoundsException.class, () -> list.get(0));
        // 清空后可正常新增
        list.add(3);
        assertEquals(1, list.length());
        assertEquals(3, list.get(0));
    }

    // ==================== length() 长度测试 ====================
    @Test
    void testLength() {
        assertEquals(0, list.length());
        list.add(1);
        assertEquals(1, list.length());
        list.add(2);
        assertEquals(2, list.length());
        list.remove(0);
        assertEquals(1, list.length());
        list.clear();
        assertEquals(0, list.length());
    }
}

全部通过