图解LinkedList数据结构设计与应用案例

260 阅读5分钟

image.png

LinkedList 是 Java 中的一个双向链表实现,它继承自 AbstractSequentialList 并实现了 ListDeque 接口。LinkedList 提供了双向链表的数据结构,支持快速的元素插入和删除操作,尤其是在列表的头部和尾部。它允许重复元素和 null 值,并且可以作为队列、栈或双端队列使用。由于其链表的特性,LinkedList 在进行元素添加和删除操作时具有较高的灵活性,但在随机访问操作上的性能不如 ArrayList。在多线程环境中,LinkedList 需要外部同步来保证线程安全。它适用于需要频繁插入和删除元素的场景,如实现 LIFO 栈或 FIFO 队列。

c LinkedList

LinkedList 在 Java 中是基于双向链表实现的,它包含多个节点,每个节点都包含数据和两个引用,分别指向前一个节点和后一个节点。以下是 LinkedList 的设计:

设计思考:

  1. 需求场景
    • 在许多编程任务中,需要一个可以快速进行插入和删除操作的动态数组。例如,在实现栈、队列、双向队列等数据结构时,这些操作非常常见。
  2. 现有技术局限性
    • ArrayList 提供了快速的随机访问能力,但在进行插入和删除操作时,可能需要移动数组中的大量元素,导致效率低下。
    • Vector 类似于 ArrayList,但它是线程安全的,但使用 synchronized 进行同步,导致并发性能较差。
  3. 技术融合
    • LinkedList 结合了链表的插入和删除效率高的特点,并提供了双向链表的实现,允许从两端快速地添加或移除元素。
  4. 设计理念
    • LinkedList 通过使用链表结构,可以有效地进行插入和删除操作,因为这些操作仅需要改变节点的指针,而不需要移动整个数组。
    • 它还实现了 List 接口,提供了与 ArrayList 相同的接口,但具有不同的性能特性。
  5. 实现方式
    • LinkedList 由一系列 Node 对象组成,每个 Node 包含数据和两个引用(previous 和 next),分别指向前一个和后一个节点。

2、 数据结构

image.png 以下是 LinkedList 数据结构的主要特点:

  1. 链式存储:元素在内存中不是连续存储的,而是通过指针(引用)连接起来的。
  2. 节点结构:每个节点至少包含两部分信息,一个是存储数据的元素,另一个是指向同链表中下一个节点的引用。在双向链表中,还会有一个指向前一个节点的引用。
  3. 动态大小LinkedList 的大小是动态的,可以根据需要随时插入或删除节点。
  4. 允许空链表:可以创建一个不包含任何节点的空链表。
  5. 插入和删除效率高:在链表的任意位置插入或删除节点的操作时间复杂度为 O(1),因为这些操作只涉及到节点的引用的改动。
  6. 访问元素效率低:访问特定索引位置的元素需要从头节点开始遍历链表,时间复杂度为 O(n)。
  7. 没有空间浪费:与数组不同,链表不需要预先分配固定大小的存储空间。
  8. 有序性:链表中的节点按照它们被插入的顺序保持有序。
  9. 可以实现为双向或循环链表:标准的 LinkedList 实现可以是双向的,也可以是循环的(尾节点指向头节点)。

3、 执行流程

image.png

图说明:
  • 初始化 LinkedList:
    • 创建一个空的 LinkedList 实例。
  • 添加元素:
    • 将新元素添加到 LinkedList
  • 删除元素:
    • 从 LinkedList 删除指定的元素。
  • 访问元素:
    • 根据索引访问 LinkedList 中的元素。
  • 遍历 LinkedList:
    • 通过节点间的链接顺序遍历整个 LinkedList
  • 检查边界条件:
    • 在执行索引相关操作前,检查索引是否在有效范围内。
  • 获取节点:
    • 获取指定索引处的节点。
  • 更新节点指针:
    • 在添加或删除元素时,更新节点间的指针。
  • 返回节点数据:
    • 返回指定节点的数据。
  • LinkedList 节点:
    • LinkedList 由一系列节点组成,每个节点包含前一个节点、后一个节点和节点数据。
  • Node prev:
    • 节点中保存的对前一个节点的引用。
  • Node next:
    • 节点中保存的对后一个节点的引用。
  • Node data:
    • 节点中保存的数据。

4、优点:

  1. 高效的插入/删除操作
    • 在任意位置插入和删除元素的时间复杂度为 O(1)。
  2. 允许空链表
    • 与 ArrayList 不同,LinkedList 允许空链表存在,不会因为空数组而抛出异常。
  3. 能够作为栈、队列和双端队列使用
    • 提供了实现栈、队列和双端队列所需的方法。

5、缺点:

  1. 随机访问性能差
    • 由于链表的结构,随机访问元素的时间复杂度为 O(n)。
  2. 内存开销
    • 相比于 ArrayListLinkedList 需要额外的内存来存储节点的指针。

6、使用场景:

  • 需要快速插入和删除元素的场景,如实现一个消息队列。
  • 作为栈、队列或双端队列使用。

7、类设计

image.png

8、应用案例

LinkedList 通常用于实现那些需要快速插入和删除元素的数据结构,如栈(Stack)和队列(Queue)。这是一个简单的任务调度系统,用于管理任务的执行顺序:

import java.util.LinkedList;

// 任务类,用于表示需要执行的任务
class Task implements Comparable<Task> {
    private final String name;
    private final int priority;

    public Task(String name, int priority) {
        this.name = name;
        this.priority = priority;
    }

    @Override
    public int compareTo(Task other) {
        return Integer.compare(this.priority, other.priority);
    }

    @Override
    public String toString() {
        return name + " (Priority: " + priority + ")";
    }
}

public class TaskScheduler {
    private final LinkedList<Task> taskQueue;

    public TaskScheduler() {
        taskQueue = new LinkedList<>();
    }

    // 添加任务到队列
    public void addTask(Task task) {
        taskQueue.add(task);
    }

    // 执行所有任务
    public void executeTasks() {
        while (!taskQueue.isEmpty()) {
            Task task = taskQueue.poll(); // 取出优先级最高的任务
            System.out.println("Executing task: " + task);
            // 模拟任务执行时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        TaskScheduler scheduler = new TaskScheduler();
        scheduler.addTask(new Task("Task 1", 3));
        scheduler.addTask(new Task("Task 2", 1)); // 高优先级任务
        scheduler.addTask(new Task("Task 3", 2));

        // 执行所有任务
        scheduler.executeTasks();
    }
}