【数据结构】List 详解

目录

[1. List 的核心概念](#1. List 的核心概念)

核心特性:

[2. List 的抽象数据类型(ADT)](#2. List 的抽象数据类型(ADT))

基本操作

[3. List 的主要实现方式](#3. List 的主要实现方式)

[3.1 顺序表(基于数组)](#3.1 顺序表(基于数组))

[3.2 链表(基于节点)](#3.2 链表(基于节点))

[4. Java 中的 List 接口及实现类](#4. Java 中的 List 接口及实现类)

[4.1 List 接口的继承关系](#4.1 List 接口的继承关系)

[4.2 主要实现类对比](#4.2 主要实现类对比)

[5. 具体实现详解](#5. 具体实现详解)

[5.1 ArrayList - 基于动态数组的实现](#5.1 ArrayList - 基于动态数组的实现)

[5.2 LinkedList - 基于链表的实现](#5.2 LinkedList - 基于链表的实现)

[6. 时间复杂度分析](#6. 时间复杂度分析)

[7. 使用场景和选择建议](#7. 使用场景和选择建议)

[选择 ArrayList 当:](#选择 ArrayList 当:)

[选择 LinkedList 当:](#选择 LinkedList 当:)

示例:性能对比场景

[8. 常见算法和应用](#8. 常见算法和应用)

[8.1 列表反转](#8.1 列表反转)

[8.2 列表排序](#8.2 列表排序)


1. List 的核心概念

​List(列表/线性表)​ ​ 是一种线性数据结构,它表示一个​​有序的、可重复的​​元素序列。

核心特性:

  1. ​有序性​​:元素按照插入的顺序存储,每个元素都有确定的位置(索引)

  2. ​可重复性​​:允许包含重复的元素

  3. ​动态大小​​:大多数实现支持动态扩容

  4. ​随机访问​​:可以通过整数索引直接访问任何位置的元素


2. List 的抽象数据类型(ADT)

在讨论具体实现之前,先了解 List 应该支持哪些基本操作:

基本操作

操作 方法签名 描述
​添加​ add(E element) 在末尾添加元素
​插入​ add(int index, E element) 在指定位置插入元素
​获取​ get(int index) 获取指定位置的元素
​设置​ set(int index, E element) 修改指定位置的元素
​删除​ remove(int index) 删除指定位置的元素
​删除元素​ remove(Object o) 删除第一次出现的指定元素
​大小​ size() 返回元素个数
​判空​ isEmpty() 判断是否为空
​包含​ contains(Object o) 判断是否包含某元素
​查找​ indexOf(Object o) 返回元素第一次出现的索引
​清空​ clear() 清空所有元素

3. List 的主要实现方式

List 有两种经典的实现方式,对应不同的底层数据结构:

3.1 顺序表(基于数组)

  • ​底层结构​​:动态数组

  • ​代表实现​ ​:ArrayListVector

  • ​特点​​:

    • 内存连续分配

    • 支持快速随机访问(O(1))

    • 尾部操作高效,中间插入/删除需要移动元素

3.2 链表(基于节点)

  • ​底层结构​​:节点通过指针连接

  • ​代表实现​ ​:LinkedList

  • ​特点​​:

    • 内存非连续分配

    • 插入删除高效(O(1),已知位置时)

    • 随机访问需要遍历(O(n))


4. Java 中的 List 接口及实现类

4.1 List 接口的继承关系

复制代码
// List 是一个泛型接口
public interface List<E> extends Collection<E> {
    // 定义了一系列列表操作的方法
}

4.2 主要实现类对比

特性 ArrayList LinkedList Vector
​底层结构​ 动态数组 双向链表 动态数组
​线程安全​ 是(同步)
​随机访问​ ​O(1)​​ - 很快 O(n) - 慢 ​O(1)​​ - 很快
​头部插入​ O(n) - 需要移动元素 ​O(1)​​ - 很快 O(n) - 需要移动元素
​尾部插入​ ​O(1)​​ 平均 - 快 ​O(1)​​ - 很快 ​O(1)​​ 平均 - 快
​中间插入​ O(n) - 需要移动元素 ​O(1)​​(已知位置时) O(n) - 需要移动元素
​内存开销​ 较小(只存数据) 较大(需要存储指针) 较小(只存数据)

5. 具体实现详解

5.1 ArrayList - 基于动态数组的实现

复制代码
// ArrayList 的基本使用
List<String> arrayList = new ArrayList<>();

// 添加元素
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add(1, "Orange"); // 在索引1处插入

// 访问元素
String fruit = arrayList.get(0); // "Apple"

// 遍历方式1:for循环(随机访问特性体现)
for (int i = 0; i < arrayList.size(); i++) {
    System.out.println(arrayList.get(i));
}

// 遍历方式2:增强for循环
for (String item : arrayList) {
    System.out.println(item);
}

// 遍历方式3:迭代器
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

​ArrayList 的扩容机制​​:

复制代码
// 当容量不足时,ArrayList会自动扩容
List<Integer> list = new ArrayList<>(5); // 初始容量5
for (int i = 0; i < 10; i++) {
    list.add(i); // 当添加第6个元素时,会触发扩容
}

// 扩容过程(简化):
// 1. 创建新数组(通常是原容量的1.5倍)
// 2. 将旧数组元素拷贝到新数组
// 3. 更新内部数组引用

5.2 LinkedList - 基于链表的实现

复制代码
// LinkedList 的基本使用
List<String> linkedList = new LinkedList<>();

// 添加元素
linkedList.add("First");
linkedList.add("Second");
linkedList.addFirst("Head"); // 链表特有的方法
linkedList.addLast("Tail");  // 链表特有的方法

// LinkedList 还实现了 Deque 接口,可以用作栈或队列
Deque<String> deque = (Deque<String>) linkedList;
deque.offerFirst("New Head");
deque.offerLast("New Tail");

​链表节点结构​​:

复制代码
// 双向链表节点的简化表示
class Node<E> {
    E item;         // 存储的数据
    Node<E> next;   // 指向下一个节点
    Node<E> prev;   // 指向上一个节点
}

6. 时间复杂度分析

操作 ArrayList LinkedList 说明
​get(int index)​ O(1) O(n) ArrayList通过索引直接计算地址
​add(E element)​ O(1) 平均 O(1) 尾部添加
​add(int index, E element)​ O(n) O(1)(已知位置) 中间插入
​remove(int index)​ O(n) O(1)(已知位置) 中间删除
​set(int index, E element)​ O(1) O(n) 修改指定位置
​contains(Object o)​ O(n) O(n) 需要遍历查找
​indexOf(Object o)​ O(n) O(n) 需要遍历查找

7. 使用场景和选择建议

选择 ArrayList 当:

  • ​频繁随机访问​​元素(按索引读取)

  • ​主要在尾部进行添加/删除​​操作

  • 内存相对紧张,希望减少开销

  • 不需要线程安全(或可以自行处理同步)

选择 LinkedList 当:

  • ​频繁在任意位置插入/删除​​元素

  • 需要实现​​栈、队列、双端队列​

  • 内存充足,可以接受额外指针开销

  • 列表规模很大,中间操作频繁

示例:性能对比场景

复制代码
// 场景1:频繁随机访问 - 适合ArrayList
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
    int value = list.get(i); // ArrayList: O(1), LinkedList: O(n)
}

// 场景2:频繁在头部插入 - 适合LinkedList  
List<Integer> list = new LinkedList<>();
for (int i = 0; i < 100000; i++) {
    list.add(0, i); // ArrayList: O(n), LinkedList: O(1)
}

8. 常见算法和应用

8.1 列表反转

复制代码
// 方法1:使用Collections工具类
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Collections.reverse(list);

// 方法2:手动实现
public static <T> void reverseList(List<T> list) {
    int left = 0, right = list.size() - 1;
    while (left < right) {
        T temp = list.get(left);
        list.set(left, list.get(right));
        list.set(right, temp);
        left++;
        right--;
    }
}

8.2 列表排序

复制代码
List<Integer> numbers = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));

// 自然排序
Collections.sort(numbers);

// 自定义排序
Collections.sort(numbers, (a, b) -> b - a); // 降序

// 使用Stream API排序
List<Integer> sorted = numbers.stream()
    .sorted()
    .collect(Collectors.toList());

​List 是最基础、最常用的数据结构之一​​,理解其特性和实现方式对于编程至关重要:

  1. ​ArrayList​ ​:基于动态数组,​​查询快,增删慢​​,适合查询多的场景

  2. ​LinkedList​ ​:基于双向链表,​​增删快,查询慢​​,适合频繁插入删除的场景

  3. ​Vector​​:线程安全的ArrayList,但性能较差,一般不推荐使用

​选择原则​​:

  • 80%的情况下,​​ArrayList 是默认选择​

  • 只有在需要频繁在列表中间操作时,才考虑 LinkedList

  • 在多线程环境下,考虑使用 Collections.synchronizedList()CopyOnWriteArrayList

相关推荐
曹牧2 小时前
Java:实现List的定长截取
java·开发语言·list
神龙斗士2404 小时前
Java 数组的定义与使用
java·开发语言·数据结构·算法
Z_z在努力5 小时前
【数据结构】队列(Queue)全面详解
java·开发语言·数据结构
岑梓铭5 小时前
《考研408数据结构》第二章《线性表(顺序表、链表)》复习笔记
数据结构·笔记·考研
日落辞朝阳8 小时前
数据结构——顺序表
数据结构
Z_z在努力8 小时前
【数据结构】哈希表(Hash Table)详解
数据结构·哈希算法·散列表
MMjeaty9 小时前
数据结构——栈和队列
数据结构·算法
瓜瓜怪兽亚9 小时前
前端基础知识---10 Node.js(三)
数据结构·数据库·node.js
古译汉书14 小时前
嵌入式铁头山羊STM32-各章节详细笔记-查阅传送门
数据结构·笔记·stm32·单片机·嵌入式硬件·个人开发