队列详解:定义、分类、应用场景及与栈的区别(C/C++ 代码讲解)

1. 什么是队列

队列(Queue)是一种先进先出(FIFO,First In First Out)的线性数据结构。数据从一端(队尾)进入,从另一端(队头)离开,就像日常生活中的排队一样:先来的人先被服务,后来的人排在队尾等待。

队列只允许在队尾进行插入操作(入队,enqueue),在队头进行删除操作(出队,dequeue)。这种受限的访问方式保证了数据处理的公平性和顺序性。

队列的核心操作包括:

  • 入队(enqueue):在队尾添加一个元素。
  • 出队(dequeue):从队头移除一个元素。
  • 队头(front):查看队头元素,但不移除。
  • 队尾(rear):查看队尾元素,但不移除。
  • 判空(isEmpty):判断队列是否为空。
  • 队列长度(size):返回队列中元素的个数。

2. 队列的分类

根据底层实现和功能扩展,队列主要分为以下几类:

2.1 顺序队列(数组实现)

顺序队列使用数组作为底层存储结构,通过两个指针(队头指针 front 和队尾指针 rear)来标记队列的边界。入队时 rear 后移,出队时 front 后移。

顺序队列的缺点是存在假溢出问题:当 rear 到达数组末尾时,即使数组前部还有空闲空间,也无法继续入队。

2.2 循环队列(环形队列)

循环队列是对顺序队列的改进,它将数组首尾相连,形成一个环形结构。当 rear 到达数组末尾时,如果数组前部有空闲空间,rear 会绕回到数组开头继续使用,从而解决假溢出问题。

循环队列通常牺牲一个存储单元来区分队空和队满状态,或者使用一个计数器来记录元素个数。

2.3 链式队列(链表实现)

链式队列使用链表作为底层存储结构,每个节点包含数据和指向下一个节点的指针。链式队列不存在容量限制,可以动态增长,但每个节点需要额外的指针存储空间。

2.4 双端队列(Deque)

双端队列(Double-Ended Queue)允许在队头和队尾两端都进行插入和删除操作,兼具栈和队列的特性。双端队列又分为输出受限和输入受限两种变体。

2.5 优先队列(Priority Queue)

优先队列不再遵循严格的 FIFO 原则,而是根据元素的优先级决定出队顺序。优先级最高的元素最先出队。优先队列通常使用堆(Heap)数据结构实现,入队和出队的时间复杂度为 O(log n)。

2.6 阻塞队列(Blocking Queue)

阻塞队列是并发编程中常用的线程安全队列。当队列为空时,出队操作会被阻塞,直到有元素入队;当队列满时,入队操作会被阻塞,直到有元素出队。阻塞队列常用于生产者-消费者模型。

3. 各类队列的作用与应用场景

3.1 顺序队列和循环队列

顺序队列和循环队列适用于元素数量可预估操作频繁的场景。由于数组具有连续内存和缓存友好的特性,在元素数量不大时性能优于链式队列。

典型应用场景:

  • 操作系统中的进程调度队列。
  • 网络数据包的缓冲队列。
  • 消息队列的底层存储。

3.2 链式队列

链式队列适用于元素数量动态变化大无法预估上限的场景。它不受固定容量限制,可以灵活扩展。

典型应用场景:

  • 任务调度系统中的待处理任务队列。
  • 图的广度优先搜索(BFS)中使用的队列。
  • 日志系统中的异步写入队列。

3.3 双端队列

双端队列适用于需要在两端灵活操作的场景,例如实现撤销/重做功能、滑动窗口算法等。

典型应用场景:

  • 浏览器的前进后退功能。
  • 滑动窗口最大值问题。
  • 实现栈和队列的混合数据结构。

3.4 优先队列

优先队列适用于需要按优先级处理任务的场景,例如任务调度、事件驱动模拟等。

典型应用场景:

  • 操作系统的进程调度(按优先级)。
  • Dijkstra 最短路径算法。
  • 哈夫曼编码。
  • 实时系统中的紧急任务处理。

3.5 阻塞队列

阻塞队列适用于多线程并发场景,用于线程间的安全数据传递和任务分配。

典型应用场景:

  • 生产者-消费者模型。
  • 线程池的任务队列。
  • 分布式系统中的消息中间件。

4. 队列与栈的区别

栈(Stack)是一种后进先出(LIFO,Last In First Out)的线性数据结构,而队列是先进先出(FIFO)。两者的核心区别如下:

对比维度 队列(Queue) 栈(Stack)
数据访问顺序 先进先出(FIFO) 后进先出(LIFO)
插入操作 入队(enqueue),在队尾插入 入栈(push),在栈顶插入
删除操作 出队(dequeue),从队头删除 出栈(pop),从栈顶删除
操作端 两端操作(队头删除、队尾插入) 单端操作(只在栈顶操作)
典型应用 任务调度、消息队列、BFS 函数调用、表达式求值、DFS
底层实现 数组或链表 数组或链表

简单来说,队列是先进先出 ,栈是后进先出。队列适合处理需要按顺序公平处理的场景,而栈适合处理需要回溯或嵌套的场景。

5. C/C++ 代码实现

5.1 顺序队列(C 语言实现)

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 100

typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
} SeqQueue;

// 初始化队列
void initQueue(SeqQueue *q) {
    q->front = 0;
    q->rear = 0;
}

// 判断队列是否为空
bool isEmpty(SeqQueue *q) {
    return q->front == q->rear;
}

// 判断队列是否已满
bool isFull(SeqQueue *q) {
    return q->rear == MAX_SIZE;
}

// 入队
bool enqueue(SeqQueue *q, int value) {
    if (isFull(q)) {
        printf("队列已满,无法入队\n");
        return false;
    }
    q->data[q->rear++] = value;
    return true;
}

// 出队
bool dequeue(SeqQueue *q, int *value) {
    if (isEmpty(q)) {
        printf("队列为空,无法出队\n");
        return false;
    }
    *value = q->data[q->front++];
    return true;
}

// 查看队头元素
bool peek(SeqQueue *q, int *value) {
    if (isEmpty(q)) {
        printf("队列为空\n");
        return false;
    }
    *value = q->data[q->front];
    return true;
}

int main() {
    SeqQueue q;
    initQueue(&q);

    enqueue(&q, 10);
    enqueue(&q, 20);
    enqueue(&q, 30);

    int value;
    while (dequeue(&q, &value)) {
        printf("出队元素: %d\n", value);
    }
    return 0;
}

5.2 循环队列(C 语言实现)

c 复制代码
#include <stdio.h>
#include <stdbool.h>

#define MAX_SIZE 5

typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
} CircularQueue;

// 初始化循环队列
void initQueue(CircularQueue *q) {
    q->front = 0;
    q->rear = 0;
}

// 判断队列是否为空
bool isEmpty(CircularQueue *q) {
    return q->front == q->rear;
}

// 判断队列是否已满(牺牲一个存储单元)
bool isFull(CircularQueue *q) {
    return (q->rear + 1) % MAX_SIZE == q->front;
}

// 入队
bool enqueue(CircularQueue *q, int value) {
    if (isFull(q)) {
        printf("循环队列已满,无法入队\n");
        return false;
    }
    q->data[q->rear] = value;
    q->rear = (q->rear + 1) % MAX_SIZE;
    return true;
}

// 出队
bool dequeue(CircularQueue *q, int *value) {
    if (isEmpty(q)) {
        printf("循环队列为空,无法出队\n");
        return false;
    }
    *value = q->data[q->front];
    q->front = (q->front + 1) % MAX_SIZE;
    return true;
}

int main() {
    CircularQueue q;
    initQueue(&q);

    enqueue(&q, 1);
    enqueue(&q, 2);
    enqueue(&q, 3);
    enqueue(&q, 4);

    int value;
    while (dequeue(&q, &value)) {
        printf("出队元素: %d\n", value);
    }
    return 0;
}

5.3 链式队列(C++ 实现)

cpp 复制代码
#include <iostream>

template <typename T>
class LinkedQueue {
private:
    struct Node {
        T data;
        Node *next;
        Node(const T &value) : data(value), next(nullptr) {}
    };
    Node *front;
    Node *rear;
    int count;

public:
    LinkedQueue() : front(nullptr), rear(nullptr), count(0) {}

    ~LinkedQueue() {
        while (!isEmpty()) {
            T value;
            dequeue(value);
        }
    }

    bool isEmpty() const {
        return front == nullptr;
    }

    int size() const {
        return count;
    }

    void enqueue(const T &value) {
        Node *newNode = new Node(value);
        if (isEmpty()) {
            front = rear = newNode;
        } else {
            rear->next = newNode;
            rear = newNode;
        }
        count++;
    }

    bool dequeue(T &value) {
        if (isEmpty()) {
            return false;
        }
        Node *temp = front;
        value = temp->data;
        front = front->next;
        if (front == nullptr) {
            rear = nullptr;
        }
        delete temp;
        count--;
        return true;
    }

    bool peek(T &value) const {
        if (isEmpty()) {
            return false;
        }
        value = front->data;
        return true;
    }
};

int main() {
    LinkedQueue<int> q;
    q.enqueue(100);
    q.enqueue(200);
    q.enqueue(300);

    int value;
    while (q.dequeue(value)) {
        std::cout << "出队元素: " << value << std::endl;
    }
    return 0;
}

5.4 双端队列(C++ STL deque)

cpp 复制代码
#include <iostream>
#include <deque>

int main() {
    std::deque<int> dq;

    // 在队尾插入
    dq.push_back(10);
    dq.push_back(20);

    // 在队头插入
    dq.push_front(5);
    dq.push_front(1);

    std::cout << "队头元素: " << dq.front() << std::endl;
    std::cout << "队尾元素: " << dq.back() << std::endl;

    // 从队头删除
    dq.pop_front();
    // 从队尾删除
    dq.pop_back();

    std::cout << "当前队列大小: " << dq.size() << std::endl;
    return 0;
}

5.5 优先队列(C++ STL priority_queue)

cpp 复制代码
#include <iostream>
#include <queue>
#include <vector>

int main() {
    // 默认是大顶堆,优先级最高的元素(最大值)先出队
    std::priority_queue<int> pq;

    pq.push(30);
    pq.push(10);
    pq.push(50);
    pq.push(20);

    std::cout << "优先队列出队顺序(大顶堆): ";
    while (!pq.empty()) {
        std::cout << pq.top() << " ";
        pq.pop();
    }
    std::cout << std::endl;

    // 小顶堆:使用 greater 比较器
    std::priority_queue<int, std::vector<int>, std::greater<int>> minPq;
    minPq.push(30);
    minPq.push(10);
    minPq.push(50);
    minPq.push(20);

    std::cout << "优先队列出队顺序(小顶堆): ";
    while (!minPq.empty()) {
        std::cout << minPq.top() << " ";
        minPq.pop();
    }
    std::cout << std::endl;
    return 0;
}

5.6 阻塞队列(C++ 实现,基于互斥锁和条件变量)

cpp 复制代码
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

template <typename T>
class BlockingQueue {
private:
    std::queue<T> queue;
    std::mutex mtx;
    std::condition_variable notEmpty;
    std::condition_variable notFull;
    size_t capacity;

public:
    BlockingQueue(size_t cap) : capacity(cap) {}

    void push(const T &value) {
        std::unique_lock<std::mutex> lock(mtx);
        notFull.wait(lock, [this]() { return queue.size() < capacity; });
        queue.push(value);
        notEmpty.notify_one();
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx);
        notEmpty.wait(lock, [this]() { return !queue.empty(); });
        T value = queue.front();
        queue.pop();
        notFull.notify_one();
        return value;
    }

    bool empty() {
        std::lock_guard<std::mutex> lock(mtx);
        return queue.empty();
    }
};

int main() {
    BlockingQueue<int> bq(3);

    // 生产者线程
    std::thread producer([&bq]() {
        for (int i = 1; i <= 5; i++) {
            bq.push(i);
            std::cout << "生产者入队: " << i << std::endl;
        }
    });

    // 消费者线程
    std::thread consumer([&bq]() {
        for (int i = 1; i <= 5; i++) {
            int value = bq.pop();
            std::cout << "消费者出队: " << value << std::endl;
        }
    });

    producer.join();
    consumer.join();
    return 0;
}

6. 总结

队列是一种先进先出的线性数据结构,根据底层实现和功能扩展可分为顺序队列、循环队列、链式队列、双端队列、优先队列和阻塞队列等类型。每种队列都有其特定的适用场景:顺序队列和循环队列适合容量可预估的场景,链式队列适合动态增长的场景,双端队列适合两端操作的场景,优先队列适合按优先级处理的场景,阻塞队列适合多线程并发场景。

队列与栈的核心区别在于数据访问顺序:队列是先进先出(FIFO),栈是后进先出(LIFO)。理解这两种数据结构的区别,有助于在实际开发中根据业务需求选择合适的数据结构。

相关推荐
CPETW16 分钟前
USB TO I2C_(Excel)_Scan ---- 1000KHz总线速率测试_A
c语言·开发语言·网络·科技·单片机
Navigator_Z17 分钟前
LeetCode //C - 1221. Split a String in Balanced Strings
c语言·算法·leetcode
2601_9622035121 分钟前
【SpringBoot3】面向切面 AspectJ AOP 使用详解
开发语言·前端·python
程序员大雄学编程23 分钟前
微积分43. 无穷积分入门:从概念到Python实战可视化
开发语言·python·学习·微积分
何以解忧,唯有..23 分钟前
LangChain 中 Pydantic 格式输出:结构化输出的完整指南
java·开发语言·langchain
郝学胜-神的一滴32 分钟前
C++11 工程级应用 06:自己造一个类似Python的Range迭代器
开发语言·c++·windows·python·程序人生·microsoft
zh_xuan43 分钟前
c++ jthread
开发语言·c++·jthread
旧梦952744 分钟前
Java 捕获多个异常:从基础语法到最佳实践
java·开发语言
桃蹊、1 小时前
FreeRTOS(六)之队列:任务间通信的基石
freertos·队列