Java-顺序表

本篇我们来详细讲解 Java 中的顺序表。

顺序表的基本概念

顺序表(Sequential List)是一种 线性表 的存储结构。它的核心特点是:用一组地址连续的存储单元依次存储线性表中的数据元素(差不多就是数组!) 。这种存储方式使得我们可以通过元素的 物理位置(索引) 直接访问元素。

核心特点

  1. 连续性: 数据元素存储在物理地址连续的内存空间中。这是顺序表最重要的特征。
  2. 随机访问: 由于元素存储连续,我们可以通过计算第一个元素的地址加上元素大小的整数倍来直接定位到第 i 个元素的位置。这使得访问任意位置的元素的时间复杂度为 O(1)。
  3. 静态性与动态性:
    • 静态顺序表: 在创建时就固定了最大容量(数组大小)。如果元素数量超过容量,则无法继续存储新元素。
    • 动态顺序表: 在元素数量接近容量上限时,能够自动申请更大的内存空间,并将原有数据复制过去。这解决了静态顺序表容量固定的问题,但扩容操作需要时间成本。

顺序表的实现(基于数组)

在 Java 中,顺序表最常使用 数组 (array) 作为底层存储结构来实现。数组本身就提供了连续的存储空间和高效的索引访问。

关键成员变量
  • data: 一个数组,用于存储实际的数据元素(例如 int[], Object[] 或泛型数组)。
  • size: 一个整数,记录当前顺序表中实际存储的元素个数(有效元素个数)。
  • capacity: 一个整数,记录 data 数组的最大容量(最多可以存储多少个元素)。
核心操作及其时间复杂度分析
  1. 初始化 (init / 构造函数)

    • 操作: 创建一个指定初始容量的数组,设置 size = 0
    • 时间复杂度: O(1)。
  2. 获取长度 (getSize)

    • 操作: 返回 size 的值。
    • 时间复杂度: O(1)。
  3. 判断是否为空 (isEmpty)

    • 操作: 判断 size == 0
    • 时间复杂度: O(1)。
  4. 获取元素 (get / getElem)

    • 操作: 检查索引 index 是否有效(0 <= index < size)。如果有效,返回 data[index]
    • 时间复杂度: O(1)。
  5. 设置元素 (set / setElem)

    • 操作: 检查索引 index 是否有效(0 <= index < size)。如果有效,将 data[index] 设置为新值。
    • 时间复杂度: O(1)。
  6. 插入元素 (insert)

    • 操作:
      1. 检查插入位置 pos 是否合法(通常 0 \<= pos \<= size)。
      2. 检查是否需要扩容(如果 size == capacity)。
      3. 将位置 pos 及其之后的所有元素向后移动一位(从 size-1pos 的元素依次向后挪动)。这是一个循环操作。
      4. 将新元素 e 放入 data[pos]
      5. size++
    • 时间复杂度:
      • 平均情况:O(n)(需要移动大约一半的元素)。
      • 最坏情况:在头部插入 (pos = 0),需要移动所有元素 O(n)。
      • 最好情况:在尾部插入 (pos = size),不需要移动元素 O(1)。
  7. 删除元素 (remove)

    • 操作:
      1. 检查索引 index 是否有效(0 <= index < size)。
      2. 将位置 index+1size-1 的所有元素向前移动一位(覆盖 data[index])。这是一个循环操作。
      3. size--
    • 时间复杂度:
      • 平均情况:O(n)(需要移动大约一半的元素)。
      • 最坏情况:删除头部元素 (index = 0),需要移动所有元素 O(n)。
      • 最好情况:删除尾部元素 (index = size-1),不需要移动元素 O(1)。
  8. 查找元素 (indexOf / contains)

    • 操作: 遍历顺序表(从 0size-1),逐个比较元素是否等于目标值 e。找到则返回索引,找不到则返回 -1false
    • 时间复杂度: O(n)(顺序遍历)。
  9. 动态扩容 (resize - 动态顺序表特有)

    • 操作:size == capacity 且需要插入新元素时,创建一个新的、容量更大的数组(通常是原容量的 1.5 倍或 2 倍),将原数组中的所有元素复制到新数组中。然后将 data 指向新数组,更新 capacity
    • 时间复杂度: O(n)(复制所有元素)。

优缺点总结

  • 优点:
    • 随机访问高效: 通过索引可在O(1)时间内访问任意元素。
    • 空间效率高: 仅存储数据和少量控制信息(size, capacity),没有额外的指针开销(与链表相比)。
    • 实现简单: 基于数组,结构清晰。
  • 缺点:
    • 插入/删除效率低: 平均需要移动大量元素,时间复杂度为 O(n)。
    • 需要连续内存: 分配大容量数组需要连续的内存空间,可能造成内存碎片问题。
    • 静态顺序表容量固定: 需要预先估计数据量大小,可能导致空间浪费或不足。
    • 动态扩容成本: 动态顺序表扩容时,复制数据的操作耗时。

Java 代码实例

示例 1: 基础静态顺序表 (存储 int 类型)

java 复制代码
public class StaticSequentialList {
    private int[] data;      // 存储元素的数组
    private int size;        // 当前元素个数
    private int capacity;    // 最大容量

    // 构造函数,指定初始容量
    public StaticSequentialList(int initCapacity) {
        if (initCapacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive");
        }
        this.data = new int[initCapacity];
        this.size = 0;
        this.capacity = initCapacity;
    }

    // 获取当前元素个数
    public int getSize() {
        return size;
    }

    // 判断是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 获取索引位置的元素
    public int get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return data[index];
    }

    // 设置索引位置的元素
    public void set(int index, int value) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        data[index] = value;
    }

    // 在指定位置插入元素
    public void insert(int pos, int value) {
        if (pos < 0 || pos > size) {
            throw new IndexOutOfBoundsException("Position: " + pos + ", Size: " + size);
        }
        if (size == capacity) {
            throw new IllegalStateException("List is full");
        }
        // 从后往前,将 pos 位置及之后的元素向后移动一位
        for (int i = size; i > pos; i--) {
            data[i] = data[i - 1];
        }
        data[pos] = value; // 插入新元素
        size++; // 更新元素个数
    }

    // 删除指定索引位置的元素
    public int remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        int oldValue = data[index];
        // 从前往后,将 index+1 位置及之后的元素向前移动一位
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--; // 更新元素个数
        return oldValue;
    }

    // 查找元素首次出现的索引,找不到返回 -1
    public int indexOf(int value) {
        for (int i = 0; i < size; i++) {
            if (data[i] == value) {
                return i;
            }
        }
        return -1;
    }

    // 打印顺序表内容
    public void display() {
        System.out.print("List: [");
        for (int i = 0; i < size; i++) {
            System.out.print(data[i]);
            if (i < size - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }
}

示例 2: 动态顺序表 (存储 int 类型,带扩容)

java 复制代码
public class DynamicSequentialList {
    private int[] data;
    private int size;
    private int capacity;
    private static final int DEFAULT_CAPACITY = 10; // 默认初始容量
    private static final double GROW_FACTOR = 1.5; // 扩容因子

    public DynamicSequentialList() {
        this(DEFAULT_CAPACITY);
    }

    public DynamicSequentialList(int initCapacity) {
        if (initCapacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive");
        }
        this.data = new int[initCapacity];
        this.size = 0;
        this.capacity = initCapacity;
    }

    // ... (getSize, isEmpty, get, set, indexOf, display 等方法与静态示例类似) ...

    // 动态扩容方法
    private void resize() {
        int newCapacity = (int) (capacity * GROW_FACTOR); // 计算新容量
        int[] newData = new int[newCapacity]; // 创建新数组
        // 将旧数组元素复制到新数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData; // 更新引用
        capacity = newCapacity; // 更新容量
        System.out.println("Resized: New capacity = " + capacity);
    }

    // 插入元素 (支持扩容)
    public void insert(int pos, int value) {
        if (pos < 0 || pos > size) {
            throw new IndexOutOfBoundsException("Position: " + pos + ", Size: " + size);
        }
        // 检查是否需要扩容
        if (size == capacity) {
            resize(); // 调用扩容方法
        }
        // 移动元素并插入 (与静态插入相同)
        for (int i = size; i > pos; i--) {
            data[i] = data[i - 1];
        }
        data[pos] = value;
        size++;
    }

    // ... (remove 方法与静态示例相同) ...
}

示例 3: 泛型动态顺序表 (支持任意类型)

java 复制代码
public class GenericDynamicSequentialList<T> {
    private Object[] data; // 使用 Object 数组存储泛型元素
    private int size;
    private int capacity;
    private static final int DEFAULT_CAPACITY = 10;
    private static final double GROW_FACTOR = 1.5;

    public GenericDynamicSequentialList() {
        this(DEFAULT_CAPACITY);
    }

    public GenericDynamicSequentialList(int initCapacity) {
        if (initCapacity <= 0) {
            throw new IllegalArgumentException("Capacity must be positive");
        }
        this.data = new Object[initCapacity];
        this.size = 0;
        this.capacity = initCapacity;
    }

    public int getSize() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    // 获取索引位置的元素 (需要强制类型转换)
    @SuppressWarnings("unchecked")
    public T get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (T) data[index];
    }

    // 设置索引位置的元素
    public void set(int index, T value) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        data[index] = value;
    }

    private void resize() {
        int newCapacity = (int) (capacity * GROW_FACTOR);
        Object[] newData = new Object[newCapacity];
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
        capacity = newCapacity;
        System.out.println("Resized: New capacity = " + capacity);
    }

    public void insert(int pos, T value) {
        if (pos < 0 || pos > size) {
            throw new IndexOutOfBoundsException("Position: " + pos + ", Size: " + size);
        }
        if (size == capacity) {
            resize();
        }
        for (int i = size; i > pos; i--) {
            data[i] = data[i - 1];
        }
        data[pos] = value;
        size++;
    }

    @SuppressWarnings("unchecked")
    public T remove(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        T oldValue = (T) data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        data[size - 1] = null; // 帮助 GC, 清除引用
        size--;
        return oldValue;
    }

    public int indexOf(T value) {
        for (int i = 0; i < size; i++) {
            if (value.equals(data[i])) { // 使用 equals 方法比较
                return i;
            }
        }
        return -1;
    }

    public void display() {
        System.out.print("List: [");
        for (int i = 0; i < size; i++) {
            System.out.print(data[i]);
            if (i < size - 1) {
                System.out.print(", ");
            }
        }
        System.out.println("]");
    }
}

总结

顺序表是数据结构中最基础、最重要的线性存储结构之一。其核心在于利用数组的 连续内存 特性实现 高效的随机访问 。Java 中的 ArrayList 类就是一个典型的、高度优化的动态顺序表实现。理解顺序表的原理、操作和性能特点是学习更复杂数据结构(如链表、栈、队列)和算法的基础。在实际应用中,需要根据数据规模、访问模式(读多写少还是写多读少)等因素,权衡选择顺序表或其他数据结构(如链表)。

下一遍我们还是继续讲解顺序表,不过侧重点是如何使用以及一些更详细的细节处理~

相关推荐
Tan_Ying_Y2 小时前
Mybatis的mapper文件中#和$的区别
java·tomcat·mybatis
难以触及的高度2 小时前
Java for循环完全指南:从基础到高性能实践
java·开发语言
sheji34162 小时前
【开题答辩全过程】以 农产品销售系统为例,包含答辩的问题和答案
java·eclipse
budingxiaomoli2 小时前
多线程(三)
java·开发语言
klzdwydz2 小时前
注解与反射
java·开发语言
talenteddriver3 小时前
java: 分页查询(自用笔记)
java·开发语言
enjoy编程3 小时前
Spring-AI 利用KeywordMetadataEnricher & SummaryMetadataEnricher 构建文本智能元数据
java·人工智能·spring
繁华似锦respect3 小时前
lambda表达式中的循环引用问题详解
java·开发语言·c++·单例模式·设计模式·哈希算法·散列表