深入理解队列(Queue):从原理到实践的完整指南

引言

你是否在超市排队结账时想过,这个简单的"排队"行为,恰恰体现了计算机科学中一个非常重要的数据结构------队列(Queue)?今天,将通过生动的例子和C++代码实现,全面掌握队列这一基础数据结构。

一、什么是队列?

队列是一种线性数据结构,遵循**先进先出(FIFO, First In First Out)**的原则。想象一下这些生活中的场景:

生活中的队列例子

🎬 电影院售票窗口

  • 第一个来的人先买到票
  • 后来的人在队伍末尾等待
  • 没有人能插队(除非你想被群殴😄)

🍔 麦当劳点餐

  • 点餐的顺序就是出餐的顺序
  • 厨房按照订单先后制作食物
  • 先点的人先拿到汉堡

🚗 单行道收费站

  • 车辆依次通过
  • 前面的车先通过,后面的车等待
  • 不能倒车,不能超车

这些场景都完美诠释了队列的核心思想:先到先得

二、队列的基本结构

复制代码
     出队方向 ←              ← 入队方向
   ┌────────────────────────────────┐
   │ Front                Rear      │
   │   ↓                    ↓       │
   │ ┌───┐  ┌───┐  ┌───┐  ┌───┐     │
   │ │ 1 │→ │ 2 │→ │ 3 │→ │ 4 │     │
   │ └───┘  └───┘  └───┘  └───┘     │
   └────────────────────────────────┘

关键术语:

  • Front(队首):第一个元素的位置,出队的地方
  • Rear(队尾):最后一个元素的位置,入队的地方
  • Enqueue(入队):在队尾添加元素
  • Dequeue(出队):从队首删除元素

三、队列的核心操作

1. 入队操作(Enqueue)

将新元素添加到队列的末尾。

cpp 复制代码
初始状态: [1] → [2] → [3]
执行 enqueue(4)
结果: [1] → [2] → [3] → [4]
       ↑                 ↑
      Front             Rear

2. 出队操作(Dequeue)

移除并返回队首元素。

cpp 复制代码
初始状态: [1] → [2] → [3] → [4]
执行 dequeue()  // 返回 1
结果: [2] → [3] → [4]
       ↑           ↑
     Front       Rear

3. 查看队首(Peek/Front)

查看但不删除队首元素。

cpp 复制代码
初始状态: [5] → [6] → [7]
执行 peek()  // 返回 5
结果: [5] → [6] → [7]  // 队列不变

四、C++实现队列

方法一:使用数组实现(循环队列)

cpp 复制代码
#include <iostream>
using namespace std;

class Queue {
private:
    int* arr;
    int frontIdx;
    int rearIdx;
    int capacity;
    int count;

public:
    // 构造函数
    Queue(int size) {
        arr = new int[size];
        capacity = size;
        frontIdx = 0;
        rearIdx = -1;
        count = 0;
    }

    // 析构函数
    ~Queue() {
        delete[] arr;
    }

    // 入队操作
    void enqueue(int value) {
        if (isFull()) {
            cout << "队列已满!无法入队 " << value << endl;
            return;
        }
        rearIdx = (rearIdx + 1) % capacity;  // 循环利用空间
        arr[rearIdx] = value;
        count++;
        cout << "✓ 入队成功: " << value << endl;
    }

    // 出队操作
    int dequeue() {
        if (isEmpty()) {
            cout << "队列为空!无法出队" << endl;
            return -1;
        }
        int value = arr[frontIdx];
        frontIdx = (frontIdx + 1) % capacity;  // 循环移动
        count--;
        cout << "✓ 出队成功: " << value << endl;
        return value;
    }

    // 查看队首元素
    int peek() {
        if (isEmpty()) {
            cout << "队列为空!" << endl;
            return -1;
        }
        return arr[frontIdx];
    }

    // 检查队列是否为空
    bool isEmpty() {
        return count == 0;
    }

    // 检查队列是否已满
    bool isFull() {
        return count == capacity;
    }

    // 获取队列大小
    int size() {
        return count;
    }

    // 显示队列内容
    void display() {
        if (isEmpty()) {
            cout << "队列为空" << endl;
            return;
        }
        cout << "队列内容: ";
        int idx = frontIdx;
        for (int i = 0; i < count; i++) {
            cout << arr[idx] << " ";
            idx = (idx + 1) % capacity;
        }
        cout << endl;
        cout << "队首: " << arr[frontIdx] << ", 队尾: " << arr[rearIdx] << endl;
    }
};

方法二:使用链表实现

cpp 复制代码
#include <iostream>
using namespace std;

// 链表节点
struct Node {
    int data;
    Node* next;
    
    Node(int val) : data(val), next(nullptr) {}
};

class LinkedQueue {
private:
    Node* frontPtr;
    Node* rearPtr;
    int count;

public:
    LinkedQueue() {
        frontPtr = nullptr;
        rearPtr = nullptr;
        count = 0;
    }

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

    void enqueue(int value) {
        Node* newNode = new Node(value);
        
        if (isEmpty()) {
            frontPtr = rearPtr = newNode;
        } else {
            rearPtr->next = newNode;
            rearPtr = newNode;
        }
        count++;
        cout << "✓ 入队成功: " << value << endl;
    }

    int dequeue() {
        if (isEmpty()) {
            cout << "队列为空!无法出队" << endl;
            return -1;
        }
        
        Node* temp = frontPtr;
        int value = temp->data;
        frontPtr = frontPtr->next;
        
        if (frontPtr == nullptr) {
            rearPtr = nullptr;
        }
        
        delete temp;
        count--;
        cout << "✓ 出队成功: " << value << endl;
        return value;
    }

    int peek() {
        if (isEmpty()) {
            cout << "队列为空!" << endl;
            return -1;
        }
        return frontPtr->data;
    }

    bool isEmpty() {
        return frontPtr == nullptr;
    }

    int size() {
        return count;
    }

    void display() {
        if (isEmpty()) {
            cout << "队列为空" << endl;
            return;
        }
        
        cout << "队列内容: ";
        Node* current = frontPtr;
        while (current != nullptr) {
            cout << current->data << " → ";
            current = current->next;
        }
        cout << "NULL" << endl;
    }
};

方法三:使用STL(最简单)

cpp 复制代码
#include <iostream>
#include <queue>
using namespace std;

int main() {
    queue<int> q;
    
    // 入队
    q.push(10);
    q.push(20);
    q.push(30);
    
    cout << "队列大小: " << q.size() << endl;
    cout << "队首元素: " << q.front() << endl;
    cout << "队尾元素: " << q.back() << endl;
    
    // 出队
    q.pop();
    cout << "出队后队首: " << q.front() << endl;
    
    return 0;
}

五、完整示例:模拟银行排队系统

让我们用一个实际的例子来理解队列的应用:

cpp 复制代码
#include <iostream>
#include <queue>
#include <string>
using namespace std;

struct Customer {
    int id;
    string name;
    int serviceTime;  // 所需服务时间(分钟)
    
    Customer(int i, string n, int t) : id(i), name(n), serviceTime(t) {}
};

class BankQueue {
private:
    queue<Customer> customerQueue;
    int totalCustomers;
    int totalWaitTime;

public:
    BankQueue() : totalCustomers(0), totalWaitTime(0) {}

    // 客户到达,加入队列
    void customerArrives(string name, int serviceTime) {
        totalCustomers++;
        Customer newCustomer(totalCustomers, name, serviceTime);
        customerQueue.push(newCustomer);
        
        cout << "📥 客户 " << name << " (编号: " << totalCustomers 
             << ") 到达,需要服务时间: " << serviceTime << "分钟" << endl;
        cout << "   当前排队人数: " << customerQueue.size() << endl;
        cout << "-----------------------------------" << endl;
    }

    // 服务下一位客户
    void serveNextCustomer() {
        if (customerQueue.empty()) {
            cout << "❌ 没有客户在等待!" << endl;
            return;
        }

        Customer current = customerQueue.front();
        customerQueue.pop();
        
        cout << "✅ 正在服务客户: " << current.name 
             << " (编号: " << current.id << ")" << endl;
        cout << "   服务时间: " << current.serviceTime << "分钟" << endl;
        cout << "   剩余排队人数: " << customerQueue.size() << endl;
        cout << "-----------------------------------" << endl;
        
        totalWaitTime += current.serviceTime;
    }

    // 查看下一位客户
    void peekNextCustomer() {
        if (customerQueue.empty()) {
            cout << "没有客户在等待" << endl;
            return;
        }
        
        Customer next = customerQueue.front();
        cout << "👀 下一位客户: " << next.name 
             << " (编号: " << next.id << ")" << endl;
    }

    // 显示统计信息
    void showStatistics() {
        cout << "\n📊 银行统计信息" << endl;
        cout << "总服务客户数: " << totalCustomers << endl;
        cout << "当前排队人数: " << customerQueue.size() << endl;
        cout << "总服务时间: " << totalWaitTime << "分钟" << endl;
        if (totalCustomers > 0) {
            cout << "平均服务时间: " 
                 << (double)totalWaitTime / totalCustomers << "分钟" << endl;
        }
    }
};

int main() {
    BankQueue bank;
    
    cout << "🏦 银行排队系统模拟开始" << endl;
    cout << "===================================\n" << endl;
    
    // 客户陆续到达
    bank.customerArrives("张三", 5);
    bank.customerArrives("李四", 3);
    bank.customerArrives("王五", 7);
    bank.customerArrives("赵六", 4);
    
    cout << "\n开始服务客户...\n" << endl;
    
    // 服务前两位客户
    bank.peekNextCustomer();
    bank.serveNextCustomer();
    
    bank.peekNextCustomer();
    bank.serveNextCustomer();
    
    // 又来了新客户
    cout << "\n新客户到达...\n" << endl;
    bank.customerArrives("钱七", 6);
    
    // 继续服务
    bank.serveNextCustomer();
    bank.serveNextCustomer();
    bank.serveNextCustomer();
    
    // 显示统计
    bank.showStatistics();
    
    return 0;
}

运行结果:

复制代码
🏦 银行排队系统模拟开始
===================================

📥 客户 张三 (编号: 1) 到达,需要服务时间: 5分钟
   当前排队人数: 1
-----------------------------------
📥 客户 李四 (编号: 2) 到达,需要服务时间: 3分钟
   当前排队人数: 2
-----------------------------------
📥 客户 王五 (编号: 3) 到达,需要服务时间: 7分钟
   当前排队人数: 3
-----------------------------------
📥 客户 赵六 (编号: 4) 到达,需要服务时间: 4分钟
   当前排队人数: 4
-----------------------------------

开始服务客户...

👀 下一位客户: 张三 (编号: 1)
✅ 正在服务客户: 张三 (编号: 1)
   服务时间: 5分钟
   剩余排队人数: 3
-----------------------------------
...

六、队列的实际应用场景

1. 操作系统进程调度

cpp 复制代码
// 简化的进程调度模拟
struct Process {
    int pid;
    string name;
    int priority;
};

queue<Process> readyQueue;  // 就绪队列
// 进程按照到达顺序被CPU执行

2. 广度优先搜索(BFS)

cpp 复制代码
// 图的BFS遍历
void BFS(Graph g, int start) {
    queue<int> q;
    bool visited[MAX_VERTICES] = {false};
    
    q.push(start);
    visited[start] = true;
    
    while (!q.empty()) {
        int current = q.front();
        q.pop();
        cout << current << " ";
        
        // 将所有未访问的邻接节点入队
        for (int neighbor : g.getNeighbors(current)) {
            if (!visited[neighbor]) {
                q.push(neighbor);
                visited[neighbor] = true;
            }
        }
    }
}

3. 打印机任务队列

cpp 复制代码
struct PrintJob {
    string fileName;
    int pages;
    string user;
};

queue<PrintJob> printerQueue;
// 文档按照提交顺序打印

4. 消息队列系统

cpp 复制代码
// 生产者-消费者模式
queue<Message> messageQueue;

// 生产者
void producer() {
    Message msg = createMessage();
    messageQueue.push(msg);  // 发送消息
}

// 消费者
void consumer() {
    if (!messageQueue.empty()) {
        Message msg = messageQueue.front();
        messageQueue.pop();  // 处理消息
        processMessage(msg);
    }
}

七、队列的变种

1. 循环队列(Circular Queue)

优点:避免"假溢出",充分利用数组空间。

cpp 复制代码
rearIdx = (rearIdx + 1) % capacity;
frontIdx = (frontIdx + 1) % capacity;

2. 双端队列(Deque)

两端都可以进行插入和删除操作。

cpp 复制代码
deque<int> dq;
dq.push_front(1);  // 队首插入
dq.push_back(2);   // 队尾插入
dq.pop_front();    // 队首删除
dq.pop_back();     // 队尾删除

3. 优先队列(Priority Queue)

元素按优先级出队,而非先进先出。

cpp 复制代码
priority_queue<int> pq;
pq.push(30);
pq.push(10);
pq.push(20);
cout << pq.top();  // 输出 30(最大值)

八、性能分析

操作 时间复杂度 空间复杂度
Enqueue O(1) O(1)
Dequeue O(1) O(1)
Peek O(1) O(1)
Search O(n) O(1)
空间占用 - O(n)

数组 vs 链表实现对比:

特性 数组实现 链表实现
内存分配 连续 分散
大小 固定 动态
缓存友好性 较差
内存开销 高(需要指针)

九、常见面试题

题目1:用两个栈实现队列

cpp 复制代码
class QueueUsingStacks {
private:
    stack<int> s1, s2;

public:
    void enqueue(int x) {
        s1.push(x);
    }

    int dequeue() {
        if (s2.empty()) {
            while (!s1.empty()) {
                s2.push(s1.top());
                s1.pop();
            }
        }
        int val = s2.top();
        s2.pop();
        return val;
    }
};

题目2:实现循环队列

cpp 复制代码
class MyCircularQueue {
private:
    vector<int> data;
    int front, rear, size, capacity;

public:
    MyCircularQueue(int k) {
        data.resize(k);
        front = rear = size = 0;
        capacity = k;
    }

    bool enQueue(int value) {
        if (isFull()) return false;
        data[rear] = value;
        rear = (rear + 1) % capacity;
        size++;
        return true;
    }

    bool deQueue() {
        if (isEmpty()) return false;
        front = (front + 1) % capacity;
        size--;
        return true;
    }

    bool isFull() { return size == capacity; }
    bool isEmpty() { return size == 0; }
};

十、总结

队列是一种简单但强大的数据结构,其"先进先出"的特性使其在计算机科学的各个领域都有广泛应用。通过本文,我们学习了:

✅ 队列的基本概念和生活中的类比

✅ 三种C++实现方式:数组、链表、STL

✅ 实际应用案例:银行排队系统

✅ 队列的变种和高级应用

✅ 性能分析和面试常见题目

掌握队列不仅能帮助你解决实际编程问题,更能加深对算法和数据结构的理解。记住:队列就像生活中的排队,先到先得,公平合理!

练习题

  1. 实现一个队列,支持获取队列中的最大值(要求时间复杂度O(1))
  2. 设计一个击鼓传花游戏的模拟程序
  3. 使用队列实现二叉树的层序遍历
  4. 实现一个滑动窗口的最大值算法

💡 建议:动手实现本文中的代码,运行看看效果。理论结合实践,才能真正掌握队列!

📚 下一步学习:栈(Stack)、链表(Linked List)、哈希表(Hash Table)

相关推荐
蒙奇D索大4 小时前
【数据结构】考研数据结构核心考点:二叉排序树(BST)全方位详解与代码实现
数据结构·笔记·学习·考研·算法·改行学it
洲覆5 小时前
C++ 模板、泛型与 auto 关键字
开发语言·数据结构·c++
MoRanzhi12035 小时前
15. Pandas 综合实战案例(零售数据分析)
数据结构·python·数据挖掘·数据分析·pandas·matplotlib·零售
WIN赢7 小时前
【二叉树的递归算法与层序遍历算法】
数据结构
Zzzzmo_7 小时前
【Java】杨辉三角、洗牌算法
java·数据结构·算法
岑梓铭9 小时前
《考研408数据结构》第四章(串和串的算法)复习笔记
数据结构·笔记·考研·算法
胖咕噜的稞达鸭10 小时前
缝合怪deque如何综合list和vector实现及仿函数模板如何优化priority_queue实现
数据结构·c++·算法·链表·list
暴力求解11 小时前
数据结构---栈和队列详解(下)
数据结构
jinmo_C++11 小时前
数据结构_哈夫曼编码(Huffman)完整指南:从原理到实现,附考研真题详解
数据结构·考研