数据结构随笔记: 队列

1. 什么是队列

队列是一种具有先进先出(FIFO)特性的线性数据结构,它由一系列元素组成,这些元素按照入队和出队的顺序进行操作。队列通常包含以下几个重要的属性和操作:

  1. 队列的属性:

    • 队列的大小(Size):表示队列中元素的个数。
    • 队列的容量(Capacity):表示队列能够容纳的最大元素个数。
    • 队列的头部(Front):表示队列中第一个元素。
    • 队列的尾部(Rear):表示队列中最后一个元素。
  2. 队列的基本操作:

    • 入队(Enqueue):将一个元素添加到队列的尾部。
    • 出队(Dequeue):从队列的头部移除一个元素。
    • 查看队列头部元素(Front):查看队列头部的元素,但不移除。
    • 判断队列是否为空(IsEmpty):判断队列中是否有元素。
    • 获取队列的大小(Size):获取队列中元素的个数。
  3. 队列的实现方式:

    • 顺序队列:使用数组实现的队列,通过数组的下标来表示队列的头部和尾部。
    • 链式队列:使用链表实现的队列,通过指针来连接队列中的元素。
    • 循环队列:使用数组实现的队列,通过循环利用数组空间来解决元素搬移的问题。
  4. 队列的应用:

    • 网络数据包的传输:网络路由器使用队列来存储待发送的数据包。
    • 多线程任务处理:线程池中使用队列来存储待执行的任务。
    • 缓冲区管理:生产者消费者模型中的缓冲区通常使用队列来管理数据。

总的来说,队列是一种非常常用的数据结构,它在计算机科学中有着广泛的应用,能够有效地管理数据的顺序和处理顺序相关的任务,是程序设计中不可或缺的重要工具。

2. 顺序队列

顺序队列是一种基于数组实现的队列数据结构,它的特点是元素在队列中的顺序是固定的。顺序队列有两个指针 front 和 rear 分别指向队列的前端和后端。

入队操作(enqueue):将新元素插入到队列的后端(rear),然后将 rear 向后移动一位。

出队操作(dequeue):将队列的前端(front)的元素移除,并将 front 向后移动一位。

使用数组实现顺序队列的步骤如下:

  1. 定义一个固定大小的数组,用于存储队列元素。
  2. 定义两个指针 frontrear 分别表示队列的前端和后端。
  3. 初始时,将 frontrear 的值都设为 -1,表示队列为空。
  4. 入队操作:
    • 首先,判断队列是否已满,即判断 rear 是否等于数组的大小减 1。如果相等,则说明队列已满,无法再添加新元素。
    • 如果队列未满,将 rear 的值增加 1,然后将要入队的元素存储在数组中 rear 的位置上。
  5. 出队操作:
    • 首先,判断队列是否为空,即判断 front 是否等于 rear。如果相等,则说明队列为空,无法执行出队操作。
    • 如果队列不为空,则将 front 的值增加 1,表示队头元素已被取出。
  6. 获取队头元素操作:
    • 首先,判断队列是否为空,即判断 front 是否等于 rear。如果相等,则说明队列为空,没有队头元素可以获取。
    • 如果队列不为空,则返回数组中 front+1 位置上的元素。

下面是使用Java代码实现顺序队列的示例:

public class ArrayQueue {
    private int[] array; // 用于存储队列元素的数组
    private int capacity; // 队列的容量
    private int front; // 队列头部的索引
    private int rear; // 队列尾部的索引

    // 构造方法,初始化队列
    public ArrayQueue(int capacity) {
        this.capacity = capacity;
        this.array = new int[this.capacity];
        this.front = -1;
        this.rear = -1;
    }

    // 入队操作
    public void enqueue(int item) {
        if (rear == capacity) {
            System.out.println("队列已满,无法入队。");
            return;
        }
        array[rear] = item;
        rear++;
    }

    // 出队操作
    public int dequeue() {
        if (front == rear) {
            System.out.println("队列为空,无法出队。");
            return -1;
        }
        int item = array[front];
        front++;
        return item;
    }

    // 查看队列头部元素
    public int peek() {
        if (front == rear) {
            System.out.println("队列为空,无法查看元素。");
            return -1;
        }
        return array[front];
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }

    // 获取队列的大小
    public int size() {
        return rear - front;
    }

    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(5);
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);

        System.out.println("队列头部元素: " + queue.peek());
        System.out.println("队列大小: " + queue.size());

        System.out.println("出队元素: " + queue.dequeue());
        System.out.println("出队元素: " + queue.dequeue());

        System.out.println("队列是否为空? " + queue.isEmpty());
    }
}
 

顺序队列的实现可以使用数组来存储队列元素。需要注意的是,当 rear 指针到达数组的末尾时,如果还有元素需要入队,则需要进行溢出判断,可以选择将队列元素整体后移。

顺序队列的优点是简单、易于实现,但是在出队操作时可能会浪费一定的空间。当队列中出队元素较多时,可能会导致队列前端空间的浪费。为了解决这个问题,可以使用循环队列或链式队列来优化顺序队列的实现。

3. 循环队列

循环队列是一种基于数组实现的队列数据结构,它的特点是在顺序队列的基础上实现了循环利用空间的功能。循环队列中的数组是首尾相连的,即队列的最后一个元素的下一个位置是队列的第一个位置。

循环队列有两个指针 front 和 rear 分别指向队列的前端和后端。

入队操作(enqueue):将新元素插入到队列的后端(rear),然后将 rear 向后移动一位。如果数组已满,即 rear 的下一个位置是 front,则表示队列已满,无法继续入队。

出队操作(dequeue):将队列的前端(front)的元素移除,并将 front 向后移动一位。如果数组为空,即 front = rear,则表示队列为空,无法继续出队。

使用数组实现循环队列的步骤如下:

  1. 定义一个数组和两个指针,一个指向队头(front),一个指向队尾(rear)。
  2. 初始化队头和队尾指针为0,表示队列为空。
  3. 创建队列时,将数组长度初始化为 capacity + 1,额外留一个空位作为约定。
  4. 入队操作:
    • 首先判断队列是否已满,如果 (rear + 1) % 数组长度 == front,则队列已满。
    • 如果队列不满,将新元素存入队尾指针所指的位置,然后将队尾指针后移一位((rear + 1) % 数组长度)。
  5. 出队操作:
    • 首先判断队列是否为空,如果 front == rear,则队列为空。
    • 如果队列不为空,取出队头指针所指的元素,然后将队头指针后移一位((front + 1) % 数组长度)。
  6. 获取队头元素:
    • 首先判断队列是否为空,如果 front == rear,则队列为空。
    • 如果队列不为空,返回队头指针所指的元素。
  7. 遍历打印队列元素:
    • 首先判断队列是否为空,如果 front == rear,则队列为空。
    • 如果队列不为空,使用循环从队头指针开始遍历到队尾指针,打印出每个元素。

通过将数组长度初始化为 capacity + 1,额外留一个空位作为约定,可以更好地管理队列的空间利用。

下面是使用Java代码实现循环队列的示例:

public class CircularQueue {
    private int[] array; // 用于存储队列元素的数组
    private int capacity; // 队列的容量
    private int front; // 队列头部的索引
    private int rear; // 队列尾部的索引

    // 构造方法,初始化队列
    public CircularQueue(int capacity) {
        this.capacity = capacity + 1; // 额外留一个空位作为约定
        this.array = new int[this.capacity];
        this.front = 0;
        this.rear = 0;
    }

    // 入队操作
    public void enqueue(int item) {
        if ((rear + 1) % capacity == front) {
            System.out.println("队列已满,无法入队。");
            return;
        }
        array[rear] = item;
        rear = (rear + 1) % capacity;
    }

    // 出队操作
    public int dequeue() {
        if (front == rear) {
            System.out.println("队列为空,无法出队。");
            return -1;
        }
        int item = array[front];
        front = (front + 1) % capacity;
        return item;
    }

    // 查看队列头部元素
    public int peek() {
        if (front == rear) {
            System.out.println("队列为空,无法查看元素。");
            return -1;
        }
        return array[front];
    }

    // 判断队列是否为空
    public boolean isEmpty() {
        return front == rear;
    }

    // 获取队列的大小
    public int size() {
        return (rear - front + capacity) % capacity;
    }

    public static void main(String[] args) {
        CircularQueue queue = new CircularQueue(5);
        queue.enqueue(1);
        queue.enqueue(2);
        queue.enqueue(3);

        System.out.println("队列头部元素: " + queue.peek());
        System.out.println("队列大小: " + queue.size());

        System.out.println("出队元素: " + queue.dequeue());
        System.out.println("出队元素: " + queue.dequeue());

        System.out.println("队列是否为空? " + queue.isEmpty());
    }
}
 

循环队列的入队和出队操作时间复杂度都是 O(1),相比顺序队列,在入队操作时无需进行整体元素的后移,因此效率更高。

需要注意的是,循环队列的判空条件是 front = rear,判满条件是 (rear + 1) % 数组长度 = front。

4. 链式队列

链式队列(Linked Queue)是一种使用链表实现的队列数据结构。与数组队列不同,链式队列的底层数据结构是由节点构成的链表。

链式队列的特点:

  1. 链式队列的大小可以动态地增长和减少,没有固定的容量限制。
  2. 链式队列不需要移动元素,因为每个节点都有指向下一个节点的指针。
  3. 入队和出队操作的时间复杂度都是O(1)。

链式队列的实现: 链式队列由节点构成,每个节点除了存储元素本身的值之外,还有一个指向下一个节点的指针。

要实现链式队列,首先需要定义一个节点类,包含两个属性:数据和指向下一个节点的指针。

private static class Node<T> {
    T data;      // 节点中存储的数据
    Node<T> next;  // 指向下一个节点的指针

    public Node(T data) {
        this.data = data;
        this.next = null;
    }
}

链式队列类有两个重要的属性:头部节点和尾部节点。头部节点(Front)指向队列的第一个节点,而尾部节点(Rear)指向队列的最后一个节点。

private Node<T> front;  // 队列头部节点
private Node<T> rear;   // 队列尾部节点

链式队列类还包含一些基本操作:

  1. 入队(enqueue):将元素添加到队列的尾部。
  2. 出队(dequeue):从队列的头部移除一个元素。
  3. 查看队列头部元素(peek):查看队列头部的元素,但不移除。
  4. 判断队列是否为空(isEmpty):判断队列中是否有元素。
  5. 获取队列的大小(size):获取队列中元素的个数。

入队操作的实现: 入队操作会创建一个新节点,并将其添加到队列的尾部。如果队列为空,则将新节点同时作为头部和尾部节点;否则,将新节点添加到队列尾部,并更新尾部节点的指针。

public void enqueue(T item) {
    Node<T> newNode = new Node<>(item);
    if (isEmpty()) {
        front = newNode;
        rear = newNode;
    } else {
        rear.next = newNode;
        rear = newNode;
    }
    size++;
}

出队操作的实现: 出队操作会从队列的头部移除一个元素。如果队列为空,则抛出异常;否则,将头部节点的指针更新为下一个节点,并更新队列的大小。如果队列为空,同时更新尾部节点的指针。

public T dequeue() {
    if (isEmpty()) {
        throw new NoSuchElementException("Queue is empty");
    }
    T data = front.data;
    front = front.next;
    size--;

    if (isEmpty()) {
        rear = null;
    }
    return data;
}

其他操作的实现: 查看队列头部元素,判断队列是否为空以及获取队列的大小的操作都比较简单,使用相应的属性或方法即可实现。

链式队列的应用: 链式队列在实际应用中非常常见,特别适用于需要频繁进行插入和删除操作的场景。例如:

  1. 任务调度:多线程环境中,线程池使用队列来管理待执行的任务。
  2. 消息队列:在消息中间件中,链式队列用于存储和传递消息。
  3. 计算机网络:路由器使用队列来存储待发送的数据包。

总之,链式队列是一种常用的数据结构,它通过链表的方式实现了队列的功能,能够高效地处理入队和出队操作,并具备动态调整队列大小的能力。

5. 三者比较

顺序队列、循环队列和链表队列是队列的三种常见实现方式,它们在存储结构和操作方式上有一些区别,下面详细介绍它们的区别:

  1. 顺序队列(ArrayQueue):

    • 存储结构:使用数组作为底层存储结构。
    • 特点:队列的大小固定,需要提前指定队列的容量。
    • 入队操作:元素依次存储在数组中,rear 指针不断向后移动。
    • 出队操作:front 指针向后移动,表示出队元素。
    • 优点:简单直观,内存连续,访问速度快。
    • 缺点:容量固定,可能会出现空间浪费或溢出的问题。
  2. 循环队列(CircularQueue):

    • 存储结构:同样使用数组作为底层存储结构。
    • 特点:在顺序队列的基础上做了改进,通过循环利用数组空间。
    • 入队操作:rear 指针在达到数组末尾时循环回到数组开头。
    • 出队操作:front 指针也可以循环回到数组开头。
    • 优点:解决了顺序队列空间浪费和溢出的问题,循环队列的空间利用率更高。
    • 缺点:需要额外的处理逻辑,可能使代码复杂一些。
  3. 链表队列(LinkedListQueue):

    • 存储结构:使用链表作为底层存储结构。
    • 特点:队列的大小可以动态调整,不需要提前指定容量。
    • 入队操作:在链表尾部插入新节点。
    • 出队操作:从链表头部删除节点。
    • 优点:灵活性高,不会出现空间浪费或溢出的问题。
    • 缺点:访问速度相对较慢,因为需要遍历链表来访问特定位置的元素。

总结:

  • 顺序队列适合对空间要求较为严格的场景,循环队列适合需要高效利用空间的场景,链表队列适合需要动态调整大小的场景。
  • 在实际应用中,根据具体需求选择合适的队列实现方式,以达到最佳的性能和空间利用率。
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值