队列的数据结构与Java实现

队列的数据结构与Java实现

1. 队列的基本概念

队列是一种遵循"先进先出"(FIFO)原则的线性数据结构。它允许在队尾进行插入(enqueue)操作,在队头进行删除(dequeue)操作。

1.1 队列的常见操作

  • enqueue(item): 将元素添加到队列尾部
  • dequeue(): 移除并返回队列头部的元素
  • peek(): 查看但不移除队列头部的元素
  • isEmpty(): 检查队列是否为空
  • isFull(): 检查队列是否已满(仅适用于固定大小队列)

2. Java中的队列实现

2.1 基于数组的实现

java 复制代码
/**
 * 基于循环数组实现的队列
 * 优点:内存连续,访问速度快
 * 缺点:固定大小,扩容成本高
 */
public class ArrayQueue {
    private int maxSize;       // 队列的最大容量
    private int[] queueArray; // 存储队列元素的数组
    private int front;        // 队头指针,指向队列第一个元素
    private int rear;         // 队尾指针,指向队列最后一个元素
    private int size;         // 当前队列中的元素数量

    public ArrayQueue(int size) {
        this.maxSize = size;
        this.queueArray = new int[maxSize];
        this.front = 0;
        this.rear = -1;
        this.size = 0;
    }

    /**
     * 入队操作 - 将新元素添加到队列尾部
     * @param value 要添加的元素值
     * @throws RuntimeException 如果队列已满则抛出异常
     * 示例: queue.enqueue(5) 将5加入队列
     */
    /**
     * 入队操作 - 将新元素添加到队列尾部
     * @param value 要添加的元素值
     * @throws IllegalStateException 如果队列已满则抛出异常
     * @see #isFull() 检查队列是否已满
     * 示例: queue.enqueue(5) 将5加入队列
     */
    public void enqueue(int value) {
        if (isFull()) {
            throw new IllegalStateException("Queue is full");
        }
        rear = (rear + 1) % maxSize;
        queueArray[rear] = value;
        size++;
    }

    /**
     * 出队操作 - 移除并返回队列头部的元素
     * @return 被移除的队头元素
     * @throws RuntimeException 如果队列为空则抛出异常
     * 示例: int first = queue.dequeue() 获取并移除第一个元素
     */
    /**
     * 出队操作 - 移除并返回队列头部的元素
     * @return 被移除的队头元素
     * @throws NoSuchElementException 如果队列为空则抛出异常
     * @see #isEmpty() 检查队列是否为空
     * 示例: int first = queue.dequeue() 获取并移除第一个元素
     */
    public int dequeue() {
        if (isEmpty()) {
            throw new NoSuchElementException("Queue is empty");
        }
        int value = queueArray[front];
        front = (front + 1) % maxSize;
        size--;
        return value;
    }

    /**
     * 查看队头元素(不移除)
     * @return 当前队头元素的值
     * @throws RuntimeException 如果队列为空则抛出异常
     * 示例: int first = queue.peek() 查看但不移除第一个元素
     */
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("Queue is empty");
        }
        return queueArray[front];
    }
}

2.2 基于链表的实现

java 复制代码
/**
 * 基于链表实现的队列
 * 优点:动态扩容,内存利用率高
 * 缺点:内存不连续,访问速度稍慢
 */
public class LinkedQueue {
    /**
     * 链表节点类
     */
    private static class Node {
        int data;   // 节点存储的数据
        Node next;  // 指向下一个节点的引用

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

    private Node front; // 队头指针,指向队列第一个节点
    private Node rear;  // 队尾指针,指向队列最后一个节点

    /**
     * 入队操作 - 将新元素添加到队列尾部
     * @param data 要添加的元素值
     * 实现原理:
     * 1. 创建新节点
     * 2. 如果队列为空,设置front和rear都指向新节点
     * 3. 否则将新节点链接到rear节点后面,并更新rear指针
     */
    public void enqueue(int data) {
        Node newNode = new Node(data);
        if (rear == null) {
            front = rear = newNode;
        } else {
            rear.next = newNode;
            rear = newNode;
        }
    }

    /**
     * 出队操作 - 移除并返回队列头部的元素
     * @return 被移除的队头元素
     * @throws RuntimeException 如果队列为空则抛出异常
     * 示例: int first = queue.dequeue() 获取并移除第一个元素
     */
    public int dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("Queue is empty");
        }
        int data = front.data;
        front = front.next;
        if (front == null) {
            rear = null;
        }
        return data;
    }

    /**
     * 查看队头元素(不移除)
     * @return 当前队头元素的值
     * @throws RuntimeException 如果队列为空则抛出异常
     * 示例: int first = queue.peek() 查看但不移除第一个元素
     */
    public int peek() {
        if (isEmpty()) {
            throw new RuntimeException("Queue is empty");
        }
        return front.data;
    }
}

2.3 双端队列实现

双端队列(Deque)是一种允许在两端进行插入和删除操作的队列。与普通队列不同,双端队列可以在队头和队尾进行操作。

数据结构原理

双端队列通常使用循环数组或链表实现。循环数组可以更高效地利用内存空间,而链表则更灵活。

实现示例
java 复制代码
public class ArrayDeque {
    private int[] elements;  // 存储队列元素的数组
    private int head;       // 队列头部指针
    private int tail;       // 队列尾部指针
    private int size;       // 当前队列元素数量
    private int capacity;   // 队列容量

    /**
     * 构造函数 - 初始化双端队列
     * @param capacity 队列容量
     */
    public ArrayDeque(int capacity) {
        this.capacity = capacity;
        this.elements = new int[capacity];
        this.head = 0;  // 头部指针初始指向0
        this.tail = 0;  // 尾部指针初始指向0
        this.size = 0;  // 初始元素数量为0
    }

    /**
     * 在队列头部添加元素
     * @param value 要添加的元素值
     * @throws RuntimeException 如果队列已满则抛出异常
     * 实现原理: 头部指针向前移动一位(使用模运算处理循环数组边界)
     */
    public void addFirst(int value) {
        if (isFull()) {
            throw new RuntimeException("Deque is full");
        }
        head = (head - 1 + capacity) % capacity;  // 循环数组处理
        elements[head] = value;
        size++;
    }

    /**
     * 在队列尾部添加元素
     * @param value 要添加的元素值
     * @throws RuntimeException 如果队列已满则抛出异常
     * 实现原理: 尾部指针向后移动一位(使用模运算处理循环数组边界)
     */
    public void addLast(int value) {
        if (isFull()) {
            throw new RuntimeException("Deque is full");
        }
        elements[tail] = value;
        tail = (tail + 1) % capacity;  // 循环数组处理
        size++;
    }

    /**
     * 移除并返回队列头部元素
     * @return 被移除的队头元素
     * @throws RuntimeException 如果队列为空则抛出异常
     * 实现原理: 头部指针向后移动一位(使用模运算处理循环数组边界)
     */
    public int removeFirst() {
        if (isEmpty()) {
            throw new RuntimeException("Deque is empty");
        }
        int value = elements[head];
        head = (head + 1) % capacity;  // 循环数组处理
        size--;
        return value;
    }

    /**
     * 移除并返回队列尾部元素
     * @return 被移除的队尾元素
     * @throws RuntimeException 如果队列为空则抛出异常
     * 实现原理: 尾部指针向前移动一位(使用模运算处理循环数组边界)
     */
    public int removeLast() {
        if (isEmpty()) {
            throw new RuntimeException("Deque is empty");
        }
        tail = (tail - 1 + capacity) % capacity;  // 循环数组处理
        int value = elements[tail];
        size--;
        return value;
    }
}

2.4 优先队列实现

优先队列是一种特殊的队列,其中每个元素都有一个优先级值。与普通队列不同,优先队列的出队顺序不是先进先出(FIFO),而是根据元素的优先级,优先级最高的元素最先出队。

数据结构原理

优先队列通常使用堆(Heap)数据结构实现。堆是一种完全二叉树,满足以下性质:

  • 最大堆:每个节点的值都大于或等于其子节点的值
  • 最小堆:每个节点的值都小于或等于其子节点的值
java 复制代码
public class PriorityQueue {
    
    private int[] heap; // 存储元素的数组
    
    private int size; // 当前元素数量

    private int capacity; //  最大容量

    public PriorityQueue(int capacity) {
        this.capacity = capacity;
        this.heap = new int[capacity];
        this.size = 0;
    }

    /**
     * 入队操作 - 将元素插入到优先队列中
     * @param value 要插入的元素值
     * @throws RuntimeException 如果队列已满则抛出异常
     * 实现原理:
     * 1. 将新元素添加到队列末尾
     * 2. 执行上浮操作(heapifyUp)调整堆结构
     */
    public void enqueue(int value) {
        if (isFull()) {
            throw new RuntimeException("Priority queue is full");
        }
        heap[size] = value;
        heapifyUp(size);
        size++;
    }

    /**
     * 出队操作 - 移除并返回优先级最高的元素(堆顶元素)
     * @return 优先级最高的元素
     * @throws RuntimeException 如果队列为空则抛出异常
     * 实现原理:
     * 1. 取出堆顶元素(最大值)
     * 2. 将堆末尾元素移到堆顶
     * 3. 执行下沉操作(heapifyDown)调整堆结构
     */
    public int dequeue() {
        if (isEmpty()) {
            throw new RuntimeException("Priority queue is empty");
        }
        int max = heap[0];
        heap[0] = heap[size - 1];
        size--;
        heapifyDown(0);
        return max;
    }

    /**
     * 上浮操作 - 从指定位置向上调整队列结构
     * @param index 需要上浮的节点索引
     * 实现原理:
     * 1. 计算父节点位置
     * 2. 如果当前节点值大于父节点值,则交换它们
     * 3. 递归向上调整直到满足队列性质
     */
    private void heapifyUp(int index) {
        int parent = (index - 1) / 2;
        if (index > 0 && heap[index] > heap[parent]) {
            swap(index, parent);
            heapifyUp(parent);
        }
    }

    /**
     * 下沉操作 - 从指定位置向下调整队列结构
     * @param index 需要下沉的节点索引
     * 实现原理:
     * 1. 计算左右子节点位置
     * 2. 找出当前节点、左子节点、右子节点中的最大值
     * 3. 如果最大值不是当前节点,则交换它们
     * 4. 递归向下调整直到满足队列性质
     */
    private void heapifyDown(int index) {
        int left = 2 * index + 1;
        int right = 2 * index + 2;
        int largest = index;

        if (left < size && heap[left] > heap[largest]) {
            largest = left;
        }

        if (right < size && heap[right] > heap[largest]) {
            largest = right;
        }

        if (largest != index) {
            swap(index, largest);
            heapifyDown(largest);
        }
    }
}

3. 时间复杂度分析

  • 基本操作时间复杂度:

    • enqueue: O(1)
    • dequeue: O(1)
    • peek: O(1)
    • isEmpty: O(1)
  • 空间复杂度: O(n)

4. 线程安全考虑

  1. 非线程安全实现:

    • 上述ArrayQueue和LinkedQueue都不是线程安全的
    • 多线程环境下可能导致数据不一致
  2. 线程安全解决方案:

    • 使用synchronized关键字修饰方法
    • 使用java.util.concurrent包中的并发集合
    • 推荐使用ConcurrentLinkedQueue作为线程安全队列实现
  3. 性能优化建议:

    • 避免过度同步,尽量缩小同步块范围
    • 考虑使用读写锁(ReentrantReadWriteLock)优化读多写少场景
    • 对于高并发场景,优先考虑无锁实现(CAS)
    • 合理设置队列容量,避免内存溢出
    • 使用Disruptor框架替代队列实现超高性能场景

5. 工程实践建议

  1. 优先使用Java标准库中的Queue接口实现
  2. 常用实现类
    • ArrayDeque - 基于数组的双端队列
    • LinkedList - 基于链表的队列
    • ConcurrentLinkedQueue - 基于链表的无界非阻塞队列
    • LinkedBlockingQueue - 基于链表的有界阻塞队列
    • ArrayBlockingQueue - 基于数组的有界阻塞队列
    • PriorityBlockingQueue - 无界优先级阻塞队列
    • SynchronousQueue - 不存储元素的阻塞队列
  3. 队列大小限制:
    • 数组实现有固定容量限制
    • 链表实现受限于内存大小
  4. 异常处理:
    • 空队列出队操作应明确抛出异常
    • 队列满情况处理

6. 性能对比测试

java 复制代码
public class QueueBenchmark {
    public static void main(String[] args) {
        int size = 10000000;
        
        // ArrayQueue测试
        long start = System.currentTimeMillis();
        ArrayQueue arrayQueue = new ArrayQueue(size);
        for (int i = 0; i < size; i++) {
            arrayQueue.enqueue(i);
        }
        for (int i = 0; i < size; i++) {
            arrayQueue.dequeue();
        }
        System.out.println("ArrayQueue耗时: " + (System.currentTimeMillis() - start) + "ms");
        
        // LinkedQueue测试
        start = System.currentTimeMillis();
        LinkedQueue linkedQueue = new LinkedQueue();
        for (int i = 0; i < size; i++) {
            linkedQueue.enqueue(i);
        }
        for (int i = 0; i < size; i++) {
            linkedQueue.dequeue();
        }
        System.out.println("LinkedQueue耗时: " + (System.currentTimeMillis() - start) + "ms");

        //双端队列测试
        start = System.currentTimeMillis();
        ArrayDeque arrayDeque = new ArrayDeque(size);   
        for (int i = 0; i < size; i++) {
            arrayDeque.addFirst(i);
        }
        for (int i = 0; i < size; i++) {
            arrayDeque.removeFirst();
        }
        System.out.println("ArrayDeque耗时: " + (System.currentTimeMillis() - start) + "ms");

         // PriorityQueue测试
        start = System.currentTimeMillis();
        PriorityQueue priorityQueue = new PriorityQueue(size);
        for (int i = 0; i < size; i++) {
            priorityQueue.enqueue(i);
        }
        for (int i = 0; i < size; i++) {
            priorityQueue.dequeue();
        }
        System.out.println("PriorityQueue耗时: " + (System.currentTimeMillis() - start) + "ms");
    }
}

7. 队列的应用场景

  1. 任务调度
  2. 消息队列
  3. 广度优先搜索
  4. 打印任务管理
  5. 缓冲区实现

8. 完整代码

本文中的代码示例可以在我的GitHub仓库中找到:查看完整代码

相关推荐
八了个戒13 分钟前
「数据可视化 D3系列」入门第三章:深入理解 Update-Enter-Exit 模式
开发语言·前端·javascript·数据可视化
noravinsc41 分钟前
html页面打开后中文乱码
前端·html
小满zs1 小时前
React-router v7 第四章(路由传参)
前端·react.js
小陈同学呦1 小时前
聊聊双列瀑布流
前端·javascript·面试
键指江湖2 小时前
React 在组件间共享状态
前端·javascript·react.js
诸葛亮的芭蕉扇2 小时前
D3路网图技术文档
前端·javascript·vue.js·microsoft
小离a_a2 小时前
小程序css实现容器内 数据滚动 无缝衔接 点击暂停
前端·css·小程序
徐小夕3 小时前
花了2个月时间研究了市面上的4款开源表格组件,崩溃了,决定自己写一款
前端·javascript·react.js
by————组态3 小时前
低代码 Web 组态
前端·人工智能·物联网·低代码·数学建模·组态
拉不动的猪3 小时前
UniApp金融理财产品项目简单介绍
前端·javascript·面试