线性表是数据结构中最基础、最重要的概念之一,而数组实现(顺序表)则是其实现方式中最为直观和高效的一种。

线性表的基本概念

什么是线性表

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

线性表的存储方式

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

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

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

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

顺序表的原理与特点

顺序表的本质

顺序表(Sequential List)是用一段物理地址连续的存储单元依次存储数据元素的线性结构,本质上就是数组,但增加了动态扩容和高级数据结构操作。

顺序表的核心特性

优点:

  • 随机访问高效:通过下标访问元素,时间复杂度O(1)

  • 存储密度高:不需要额外的指针空间

  • 缓存友好:连续内存空间,CPU缓存命中率高

缺点:

  • 插入删除低效:需要移动大量元素,时间复杂度O(n)

  • 容量固定:需要预先分配空间或动态扩容

  • 扩容代价高:扩容时需要复制所有元素

线性表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 SeqList<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;

import java.util.Arrays;

/**
 * @author nanak
 */
public class SeqListImpl<E> implements SeqList<E>{
    private Object[] data;
    private int count;

    public SeqListImpl() {
        this(10);
    }

    public SeqListImpl(int initialCapacity) {
        if (initialCapacity <= 0) {
            throw new IllegalArgumentException("初始容量必须>0: " + initialCapacity);
        }
        this.data = new Object[initialCapacity];
    }

    @Override
    public boolean add(E e) {
        if (this.count==this.data.length) {
            grow();
        }
        data[this.count]=(Object) e;
        this.count++;
        return true;
    }

    @Override
    public boolean add(int i, E e) {
        if (i < 0 || i > this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }

        if (this.count == data.length) {
            grow();
        }
        System.arraycopy(data, i, data, i + 1, this.count - i);

        data[i] = (Object) e;

        this.count++;

        return true;
    }

    @Override
    public E get(int i) {
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        return (E) this.data[i];
    }

    @Override
    public E set(int i, E e) {
        E old = (E) this.data[i];
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        this.data[i] = (Object) e;
        return old;
    }

    @Override
    public E remove(int i) {
        if (i < 0 || i >= this.count) {
            throw new IndexOutOfBoundsException(
                    "索引越界:当前大小=" + this.count + ",传入索引=" + i
            );
        }
        E temp = (E) this.data[i];
        System.arraycopy(data, i + 1, data, i, count - i - 1);
        this.count--;
        return temp;
    }

    @Override
    public int indexOf(E e) {
        for (int i = 0; i < this.count; i++) {
            if (this.data[i] == null && e == null) {
                return i;
            }
            if (this.data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public void clear() {
        this.data = new Object[10];
        this.count = 0;
    }

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

    public int getCapacity() {
        return this.data.length;
    }

    private void grow() {
        int oldCapacity = data.length;
        // 扩容公式:原容量 + 原容量/2(位运算更高效)
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 数组拷贝:创建新数组并复制原数据
        data = Arrays.copyOf(data, newCapacity);
    }
}

进行测试

package org.example;

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

/**
 * SeqListImpl 测试类
 */
public class SeqListImplTest {

    private SeqListImpl<String> seqList;

    /**
     * 每个测试方法执行前初始化空顺序表
     */
    @BeforeEach
    void setUp() {
        seqList = new SeqListImpl<>();
    }

    /**
     * 测试默认初始化容量(10)
     */
    @Test
    void testDefaultCapacity() {
        assertEquals(10, seqList.getCapacity());
    }

    /**
     * 测试自定义初始化容量
     */
    @Test
    void testCustomCapacity() {
        SeqListImpl<Integer> list = new SeqListImpl<>(5);
        assertEquals(5, list.getCapacity());
    }

    /**
     * 测试初始化容量为负数时抛出异常
     */
    @Test
    void testNegativeCapacity() {
        assertThrows(IllegalArgumentException.class, () -> new SeqListImpl<>(-3));
    }

    /**
     * 测试尾插元素(add(E))
     */
    @Test
    void testAddTail() {
        // 插入3个元素
        assertTrue(seqList.add("A"));
        assertTrue(seqList.add("B"));
        assertTrue(seqList.add("C"));

        // 验证元素顺序和值
        assertEquals("A", seqList.get(0));
        assertEquals("B", seqList.get(1));
        assertEquals("C", seqList.get(2));

        // 验证当前元素个数(count):需通过间接方式验证(比如越界)
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.get(3));
    }

    /**
     * 测试扩容逻辑(原容量10,插入11个元素触发扩容)
     */
    @Test
    void testGrow() {
        // 插入10个元素(填满初始容量)
        for (int i = 0; i < 10; i++) {
            seqList.add("test" + i);
        }
        assertEquals(10, seqList.getCapacity()); // 扩容前容量仍为10

        // 插入第11个元素,触发扩容(10 -> 15)
        seqList.add("growTest");
        assertEquals(15, seqList.getCapacity()); // 扩容后容量变为15
        assertEquals("growTest", seqList.get(10)); // 验证第11个元素插入成功
    }

    /**
     * 测试指定位置插入元素(add(int, E))
     */
    @Test
    void testAddByIndex() {
        // 先插入基础元素
        seqList.add("A");
        seqList.add("C");

        // 在索引1位置插入B
        seqList.add(1, "B");

        // 验证插入后顺序
        assertEquals("A", seqList.get(0));
        assertEquals("B", seqList.get(1));
        assertEquals("C", seqList.get(2));

        // 测试在头部插入
        seqList.add(0, "HEAD");
        assertEquals("HEAD", seqList.get(0));

        // 测试在尾部插入(等同于add(E))
        seqList.add(4, "TAIL");
        assertEquals("TAIL", seqList.get(4));
    }

    /**
     * 测试指定位置插入越界(索引<0 或 索引>count)
     */
    @Test
    void testAddByIndexOutOfBounds() {
        seqList.add("A"); // count=1

        // 索引<0
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.add(-1, "X"));
        // 索引>count(当前count=1,索引2越界)
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.add(2, "X"));
    }

    /**
     * 测试修改元素(set(int, E))
     */
    @Test
    void testSet() {
        seqList.add("OLD");
        // 修改索引0的元素
        String oldValue = seqList.set(0, "NEW");

        // 验证返回值为旧值
        assertEquals("OLD", oldValue);
        // 验证新值生效
        assertEquals("NEW", seqList.get(0));
    }

    /**
     * 测试修改元素越界
     */
    @Test
    void testSetOutOfBounds() {
        seqList.add("A");
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.set(1, "X"));
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.set(-1, "X"));
    }

    /**
     * 测试删除元素(remove(int))
     */
    @Test
    void testRemove() {
        seqList.add("A");
        seqList.add("B");
        seqList.add("C");

        // 删除索引1的元素(B)
        String removed = seqList.remove(1);
        assertEquals("B", removed);

        // 验证删除后元素移位
        assertEquals("C", seqList.get(1));
        // 验证删除后越界
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.get(2));
    }

    /**
     * 测试删除元素越界
     */
    @Test
    void testRemoveOutOfBounds() {
        seqList.add("A");
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.remove(1));
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.remove(-1));
    }

    /**
     * 测试查找元素索引(indexOf(E))
     */
    @Test
    void testIndexOf() {
        seqList.add("A");
        seqList.add("B");
        seqList.add("A");

        // 查找存在的元素(返回第一个匹配索引)
        assertEquals(0, seqList.indexOf("A"));
        assertEquals(1, seqList.indexOf("B"));

        // 查找不存在的元素
        assertEquals(-1, seqList.indexOf("C"));

        // 查找null(需处理空指针,补充测试)
        seqList.add(null);
        assertEquals(3, seqList.indexOf(null));
    }

    /**
     * 测试清空顺序表(clear)
     */
    @Test
    void testClear() {
        seqList.add("A");
        seqList.add("B");

        seqList.clear();

        // 验证清空后元素不可访问
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.get(0));
        // 验证容量重置为默认0
        assertEquals(0, seqList.length());
    }

    /**
     * 测试get方法越界
     */
    @Test
    void testGetOutOfBounds() {
        // 空表访问索引0
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.get(0));
        // 访问负索引
        assertThrows(IndexOutOfBoundsException.class, () -> seqList.get(-1));
    }
}

全部通过