JAVA:实现一个Deques双端队列算法(附带源码)

一、项目背景详细介绍

在数据结构与算法的学习中,队列(Queue) 是最基础的一类结构,它采用 先进先出(FIFO) 的存取模式。相比之下,栈(Stack) 是另一种常用的数据结构,它采用 后进先出(LIFO) 的存取方式。

然而,在很多实际应用场景中,既需要从队首插入元素,也需要从队尾插入元素,同时还可能需要从两端删除元素。单纯的队列和栈都无法满足这种需求,于是我们就需要 双端队列(Deque,Double Ended Queue)

双端队列的特性:

  • 允许从 队首队尾 插入元素;

  • 允许从 队首队尾 删除元素;

  • 可以灵活地模拟 队列(只用一端插入,另一端删除)和 (两端操作相同)。

在现代计算机科学与工程中,Deque 有着广泛的应用:

  1. 任务调度:在操作系统中,任务可能会被插入到任务队列的不同位置,以实现优先级调度;

  2. 缓存实现:LRU 缓存算法中需要频繁操作队列两端,Deque 是理想的数据结构;

  3. 滑动窗口算法:在计算最大值/最小值的滑动窗口问题中,Deque 可以高效维护窗口中的候选元素;

  4. 表达式求值:中缀、后缀表达式的求解可以利用双端队列实现。

因此,实现一个通用的 Deque,不仅能够帮助我们加深对队列和栈的理解,还能直接应用在许多工程项目中。


二、项目需求详细介绍

本项目要求使用 Java 编写一个 通用双端队列(Deque)算法实现,主要需求如下:

  1. 数据通用化(泛型支持)

    • 使用泛型 T,保证队列可以存储任意数据类型(如 IntegerString、自定义对象等)。

  2. 基本功能

    • addFirst(data):在队首插入元素;

    • addLast(data):在队尾插入元素;

    • removeFirst():删除并返回队首元素;

    • removeLast():删除并返回队尾元素;

    • peekFirst():查看队首元素但不删除;

    • peekLast():查看队尾元素但不删除;

    • isEmpty():判断队列是否为空;

    • size():获取队列长度。

  3. 存储结构

    • 内部使用 双向链表 来实现 Deque(也可以使用动态数组,但链表更直观)。

  4. 测试案例

    • 插入一组元素并分别从队首和队尾删除;

    • 验证 FIFO、LIFO 行为是否能够模拟;

    • 验证不同数据类型存储是否正确。


三、相关技术详细介绍

在本项目中,需要掌握以下相关技术:

  1. 双向链表(Doubly Linked List)

    • 每个节点包含三个部分:前驱指针 prev、存储数据 data、后继指针 next

    • 可以从任意方向遍历,方便支持从两端进行插入和删除。

  2. Java 泛型(Generics)

    • 通过定义 Deque<T>,实现对任意类型的支持。

  3. 时间复杂度分析

    • addFirst()addLast():O(1);

    • removeFirst()removeLast():O(1);

    • peekFirst()peekLast():O(1)。

  4. 应用场景

    • 任务调度(两端插入任务);

    • LRU 缓存(双端操作维护数据顺序);

    • 广度优先搜索(BFS)和双端 BFS。


四、实现思路详细介绍

  1. 定义内部节点类 Node<T>

    • 包含数据 T data

    • 包含前驱指针 Node<T> prev 和后继指针 Node<T> next

  2. 定义 Deque 类

    • 包含 head(队首)和 tail(队尾)指针;

    • 维护一个 size 变量,记录当前元素个数。

  3. 实现基本方法

    • addFirst(data):创建新节点并插入到 head 前;

    • addLast(data):创建新节点并插入到 tail 后;

    • removeFirst():删除 head 并返回其值;

    • removeLast():删除 tail 并返回其值;

    • peekFirst():返回 head.data

    • peekLast():返回 tail.data

  4. 辅助方法

    • isEmpty():判断 size == 0

    • size():返回元素个数。

  5. 测试用例

    • 入队、出队操作验证;

    • 模拟栈行为(只操作一端);

    • 模拟队列行为(前删后加)。


五、完整实现代码

// 文件:DequeDemo.java

public class DequeDemo {

    // 节点类(双向链表节点)
    private static class Node<T> {
        T data;          // 数据
        Node<T> prev;    // 前驱指针
        Node<T> next;    // 后继指针

        Node(T data) {
            this.data = data;
        }
    }

    // 双端队列类
    public static class Deque<T> {
        private Node<T> head; // 队首
        private Node<T> tail; // 队尾
        private int size;     // 元素个数

        // 构造函数
        public Deque() {
            head = null;
            tail = null;
            size = 0;
        }

        // 在队首插入元素
        public void addFirst(T data) {
            Node<T> newNode = new Node<>(data);
            if (isEmpty()) {
                head = tail = newNode;
            } else {
                newNode.next = head;
                head.prev = newNode;
                head = newNode;
            }
            size++;
        }

        // 在队尾插入元素
        public void addLast(T data) {
            Node<T> newNode = new Node<>(data);
            if (isEmpty()) {
                head = tail = newNode;
            } else {
                tail.next = newNode;
                newNode.prev = tail;
                tail = newNode;
            }
            size++;
        }

        // 删除队首元素
        public T removeFirst() {
            if (isEmpty()) {
                throw new RuntimeException("队列为空,无法删除队首!");
            }
            T value = head.data;
            head = head.next;
            if (head != null) {
                head.prev = null;
            } else {
                tail = null; // 队列为空
            }
            size--;
            return value;
        }

        // 删除队尾元素
        public T removeLast() {
            if (isEmpty()) {
                throw new RuntimeException("队列为空,无法删除队尾!");
            }
            T value = tail.data;
            tail = tail.prev;
            if (tail != null) {
                tail.next = null;
            } else {
                head = null; // 队列为空
            }
            size--;
            return value;
        }

        // 查看队首元素
        public T peekFirst() {
            if (isEmpty()) {
                throw new RuntimeException("队列为空!");
            }
            return head.data;
        }

        // 查看队尾元素
        public T peekLast() {
            if (isEmpty()) {
                throw new RuntimeException("队列为空!");
            }
            return tail.data;
        }

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

        // 获取队列长度
        public int size() {
            return size;
        }
    }

    // 测试方法
    public static void main(String[] args) {
        Deque<Integer> deque = new Deque<>();

        System.out.println("向队首插入: 1, 2");
        deque.addFirst(1);
        deque.addFirst(2);

        System.out.println("向队尾插入: 3, 4");
        deque.addLast(3);
        deque.addLast(4);

        System.out.println("当前队首元素: " + deque.peekFirst());
        System.out.println("当前队尾元素: " + deque.peekLast());
        System.out.println("队列长度: " + deque.size());

        System.out.println("从队首删除: " + deque.removeFirst());
        System.out.println("从队尾删除: " + deque.removeLast());

        System.out.println("删除后的队首: " + deque.peekFirst());
        System.out.println("删除后的队尾: " + deque.peekLast());
        System.out.println("队列是否为空: " + deque.isEmpty());
    }
}

六、代码详细解读

  1. Node<T> 节点类

    • 保存数据 T data

    • 前驱指针 prev,后继指针 next

    • 用于构建双向链表。

  2. Deque<T>

    • head 指向队首节点;

    • tail 指向队尾节点;

    • size 记录元素个数。

  3. 插入操作

    • addFirst():新节点插入到 head 前,并更新 head

    • addLast():新节点插入到 tail 后,并更新 tail

  4. 删除操作

    • removeFirst():删除 head,并将 head 移动到下一个节点;

    • removeLast():删除 tail,并将 tail 移动到前一个节点。

  5. 查看操作

    • peekFirst() 返回 head.data

    • peekLast() 返回 tail.data

  6. 测试结果

    • 插入 2, 1, 3, 4

    • 删除后输出正确,符合 双端队列 特性。


七、项目详细总结

  • 本项目实现了一个基于 双向链表通用双端队列(Deque)

  • 支持从队首和队尾进行插入和删除;

  • 支持查看队首和队尾元素;

  • 支持判空和获取长度;

  • 验证了 Deque 能同时模拟栈和队列 的特性;

  • 时间复杂度:所有基本操作均为 O(1),性能优秀。


八、项目常见问题及解答

问题1:Deque 和 Queue 有什么区别?

  • Queue 只能在尾部插入,在头部删除;

  • Deque 可以在两端插入和删除,更灵活。

问题2:为什么使用双向链表而不是 ArrayList?

  • 如果用 ArrayList 实现,删除队首需要 O(n) 时间;

  • 双向链表删除和插入只需要 O(1),更高效。

问题3:Deque 可以用来模拟栈吗?

  • 可以,只需始终在一端插入和删除即可,行为等同于栈。

问题4:Java 自带的 Deque 实现有哪些?

  • ArrayDequeLinkedList 都实现了 Deque 接口。


九、扩展方向与性能优化

  1. 基于数组的循环双端队列

    • 使用循环数组避免链表节点的内存开销;

    • 插入和删除同样是 O(1)。

  2. 线程安全 Deque

    • 在多线程环境中,可以使用 ConcurrentLinkedDeque

    • 适用于生产者-消费者模型。

  3. 应用扩展

    • LRU 缓存:Deque 可以高效维护访问顺序;

    • BFS 双向搜索:Deque 支持从两端扩展搜索;

    • 滑动窗口最大值问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值