【无标题】

前言

队列和栈一样,属于受限线性表 。栈是后进先出 LIFO,队列则是先进先出 FIFO(First‑In‑First‑Out)。日常开发中消息队列、任务缓冲、广度优先搜索 BFS 都离不开队列。本文按照学习路线:队列概念→链式存储→顺序存储→循环队列力扣真题→双端队列完整梳理

1 队列的基本概念

队列(Queue):只允许一端插入,另一端删除的线性表。

  • 队尾 (rear):执行插入(入队)的一端
  • 队头 (front):执行删除(出队)的一端
  • 规则:先入队的元素,必须先出队。不允许直接访问中间元素。

常见操作:

  1. enqueue(x):入队,队尾插入元素 x
  2. dequeue():出队,删除队头元素
  3. front():读取队头元素
  4. isEmpty():判断队列是否为空
  5. size():获取队列元素数量

应用场景:任务排队、消息缓冲、BFS 广度优先遍历、打印机任务调度。

📌栈:入口出口同一个端;队列:入口出口分开两端。

对于一般的队列我们采用单链表的存储方式

2 队列的链式存储(链队)

链式队列,用单链表实现队列。为了入队出队都做到 O (1) 效率,维护两个指针:

  • front:队头指针,指向链表头部
  • rear:队尾指针,指向链表尾部节点

优点:没有假溢出,动态分配内存,几乎不会队满(内存耗尽除外)

缺点:每个节点需要额外 next 指针,内存开销大;不能随机访问。

代码

cpp 复制代码
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QDataType; // 队列中存储数据元素的类型
// 采用单链表实现队列,链表结点的定义
typedef struct LinkNode {
	QDataType data;
	struct LinkNode* next;
}QNode;
typedef struct {
	QNode* front;
	QNode* rear;
	int size;
}LinkQueue;
// 初始化队列
void QueueInit(LinkQueue* q);

// 销毁队列
void QueueDestroy(LinkQueue* q);
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q);
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q);
// 获取队头元素
QDataType QueueFront(LinkQueue* q);
// 将x入队列
void EnQueue(LinkQueue* q, QDataType x);
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q);
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
// 初始化队列
void QueueInit(LinkQueue* q)
{
	assert(q);
	q->front = q->rear = (QNode*)malloc(sizeof(QNode));
	 
	q->size = 0;
	q->rear->next = NULL;
}

// 销毁队列
void QueueDestroy(LinkQueue* q)
{
	assert(q);
	QNode* cur = q->front;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	cur=NULL;
	q->front = q->rear = NULL;
	q->size = 0;

}
// 检测队列是否为空,空返回真,否则返回假
bool QueueEmpty(LinkQueue* q) {
	assert(q);
	return q->size == 0;
}
// 获取队列中有效元素个数
int QueueSize(LinkQueue* q){
	assert(q);
	return q->size;
}
// 获取队头元素
QDataType QueueFront(LinkQueue* q)
{
	assert(q);
	assert(!QueueEmpty(q));
	return q->front->next->data;
	 
}
// 将x入队列
void EnQueue(LinkQueue* q, QDataType x){
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	newnode->data = x;
	newnode->next = NULL;
	q->rear->next = newnode;
	q->rear = newnode;
	q->size++;

}
// 出队并返回队头数据
QDataType DeQueue(LinkQueue* q){
	assert(q);
	assert(!QueueEmpty(q));
	QDataType ret = q->front->next->data;
	QNode* del = q->front->next;
	q->front->next = del->next;
	free(del);
	q->size--;
	if (q->front->next == NULL)
	{
		q->rear = q->front;
	}
	return ret;
}
cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"queue.h"
int main() {
	LinkQueue lq;
	QueueInit(&lq);
	EnQueue(&lq, 1);
	EnQueue(&lq, 2);
	EnQueue(&lq, 3);
	EnQueue(&lq, 4);
	printf("队列数据个数%d\n", QueueSize(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	EnQueue(&lq, 5);
	EnQueue(&lq, 6);
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("队列数据个数%d\n", QueueSize(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	printf("出队%d\n", DeQueue(&lq));
	QueueDestroy(&lq);
	return 0;
}

3.队列的顺序存储 & 循环队列

顺序队列:底层数组实现,维护 front、rear 下标。 普通顺序队列有严重问题:假溢出。数组前面出队空出一大片空间,但 rear 已经走到数组末尾,明明还有空闲空间,却不能入队。

解决方案:循环队列 。逻辑上把数组头尾相连形成环,用取模%运算实现下标回绕。

循环队列两个经典方案

方案 1:牺牲一个存储单元区分空 / 满(考研重点)
  • 判空:front == rear
  • 判满:(rear + 1) % MaxSize == front
  • 队列有效元素个数公式:(rear - front + MaxSize) % MaxSize

数组实际最多存放 MaxSize‑1 个元素,留一个空位用来区分队空和队满两种状态,避免逻辑歧义。

方案 2:增加 size 计数器(工程更常用)

结构体增加size记录元素个数:

  • 判空:size == 0
  • 判满:size == MaxSize 不需要牺牲数组空间,逻辑简单,LeetCode622 就可以用这种写法。

循环队列入队:rear = (rear +1)%MaxSize 循环队列出队:front = (front +1)%MaxSize

优点:数组实现,访问速度快,空间利用率高,解决假溢出。 缺点:容量固定。

如果采用牺牲一格版本,数组开辟 k+1 大小,判满条件(rear+1)%cap == front,不需要 size 变量

622. 设计循环队列 - 力扣(LeetCode)622. 设计循环队列 - 设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。你的实现应该支持如下操作: * MyCircularQueue(k): 构造器,设置队列长度为 k 。 * Front: 从队首获取元素。如果队列为空,返回 -1 。 * Rear: 获取队尾元素。如果队列为空,返回 -1 。 * enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。 * deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。 * isEmpty(): 检查循环队列是否为空。 * isFull(): 检查循环队列是否已满。 示例:MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3circularQueue.enQueue(1); // 返回 truecircularQueue.enQueue(2); // 返回 truecircularQueue.enQueue(3); // 返回 truecircularQueue.enQueue(4); // 返回 false,队列已满circularQueue.Rear(); // 返回 3circularQueue.isFull(); // 返回 truecircularQueue.deQueue(); // 返回 truecircularQueue.enQueue(4); // 返回 truecircularQueue.Rear(); // 返回 4 提示: * 所有的值都在 0 至 1000 的范围内; * 操作数将在 1 至 1000 的范围内; * 请不要使用内置的队列库。https://leetcode.cn/problems/design-circular-queue/

cpp 复制代码
class MyCircularQueue {
public:
    /**
     * Your MyCircularQueue object will be instantiated and called as such:
     * MyCircularQueue* obj = new MyCircularQueue(k);
     * bool param_1 = obj->enQueue(value);
     * bool param_2 = obj->deQueue();
     * int param_3 = obj->Front();
     * int param_4 = obj->Rear();
     * bool param_5 = obj->isEmpty();
     * bool param_6 = obj->isFull();
     */
    MyCircularQueue(int k) {
        _ringqueue.assign(k, 0);
        _size = 0;
        _front=_rear=0;
        _cap = k;
    }

    bool isEmpty() { return _size == 0; }

    bool isFull() { return _size == _cap ; }
    bool enQueue(int value) {
        if (isFull())
            return false;
        _ringqueue[_rear] = value;
        _rear = (_rear + 1) % _cap;
        _size++;
        return true;
    }

    bool deQueue() {
        if (isEmpty())
            return false;
        _front = (_front + 1 + _cap) % _cap;
        _size--;
        return true;
    }

    int Front() {
        if (isEmpty())
            return -1;
        int ret = _ringqueue[_front];

        return ret;
    }

    int Rear() {
        if (isEmpty())
            return -1;
        int tmp = (_rear - 1 + _cap) % _cap;
        int ret = _ringqueue[tmp];

        return ret;
    }

private:
    int _size;
    vector<int> _ringqueue;
    int _front;
    int _rear;
    int _cap;
};
  1. Rear()获取队尾元素:rear 指向下一个要写入的空位 ,真实队尾是(rear‑1+capacity)%capacity加 capacity 防止负数取模出错。
  2. 所有下标移动必须做取模,实现环形回绕。
  3. 函数返回 bool,操作失败返回 false;空队列取元素返回‑1。
存储方式 优点 缺点 判空 判满
链式队列 动态扩容,无假溢出 指针开销 front==rear 内存耗尽才满
普通顺序队列 数组速度快 假溢出 front==rear rear 到达数组末尾
循环队列 数组、解决假溢出 容量固定 front==rear / size==0 (rear+1)%max==front / size==max
相关推荐
m0_739312871 小时前
【自动驾驶和机器人控制】万向锁(Gimbal Lock)原理详解 + 和欧拉角 / 四元数 / SO(3)对比
算法·机器人·自动驾驶
Felven2 小时前
A. Eating Game
算法
血小板要健康2 小时前
队列 + 宽搜(BFS):二叉树层序遍历 算法总结
java·数据结构·笔记·算法·leetcode·宽度优先
Felven3 小时前
A. Riptide
算法·c 算法
m0_739312873 小时前
四元数、李群SO(3)/李代数so(3)的作用及应用场景
算法·机器人·自动驾驶
artificiali3 小时前
880 第4章多元
人工智能·算法
Felven3 小时前
B. Deja Vu
数据结构·算法
小玮看世界4 小时前
[Python]螺旋遍历 vs 最短路径:方向控制类算法的“同源异流”
开发语言·python·算法
月华路4 小时前
《模型不玄学》第14章 标签、损失与样本权重
人工智能·算法·机器学习