网站建设用什么软件好百度app浏览器下载
一、队列基础概念
队列是一种 先进先出(FIFO) 的线性结构,操作受限,仅允许在队尾插入(入队),队头删除(出队)。
二、顺序队列
1. 结构定义
- 存储方式:基于数组,
front
和rear
分别指向队头和队尾。 - 代码实现:
#define MAX_SIZE 100 typedef struct {int data[MAX_SIZE];int front; // 队头指针int rear; // 队尾指针 } SeqQueue;
2. 优缺点
- 优点:实现简单,访问速度快(数组随机访问)。
- 缺点:固定大小导致空间浪费或溢出;出队需移动元素,时间复杂度高。
3. 核心操作代码
-
入队(时间复杂度O(1)):
int EnQueue(SeqQueue *q, int x) {if (q->rear == MAX_SIZE) {printf("队列已满");return 0;}q->data[q->rear++] = x;return 1; }
-
出队(时间复杂度O(n),需移动元素):
int DeQueue(SeqQueue *q, int *x) {if (q->front == q->rear) {printf("队列为空");return 0;}*x = q->data[0];for (int i = 0; i < q->rear - 1; i++) q->data[i] = q->data[i + 1]; // 元素前移q->rear--;return 1; }
三、循环队列
1. 结构定义
- 核心思想:数组首尾相连,通过取模运算实现循环,解决“假溢出”。
- 判空/满条件:
- 队空:
front == rear
- 队满:
(rear + 1) % MAX_SIZE == front
(牺牲一个存储单元)。
- 队空:
2. 优缺点
- 优点:空间利用率高,入队/出队O(1)。
- 缺点:容量固定,实现复杂(需处理模运算)。
3. 核心操作代码
typedef struct {int data[MAX_SIZE];int front, rear;
} CircularQueue;// 入队
int EnQueue(CircularQueue *q, int x) {if ((q->rear + 1) % MAX_SIZE == q->front) {printf("队列已满");return 0;}q->data[q->rear] = x;q->rear = (q->rear + 1) % MAX_SIZE;return 1;
}// 出队
int DeQueue(CircularQueue *q, int *x) {if (q->front == q->rear) {printf("队列为空");return 0;}*x = q->data[q->front];q->front = (q->front + 1) % MAX_SIZE;return 1;
}
四、双向队列(Deque)
1. 结构定义
- 特点:允许两端插入和删除,结合队列和栈的特性。
- 实现方式:数组或链表(常用循环数组)。
2. 优缺点
- 优点:操作灵活,支持随机访问(数组实现)。
- 缺点:数组实现需处理扩容,链表实现需额外指针开销。
3. 核心操作代码(数组实现)
#define DEQUE_SIZE 100
typedef struct {int data[DEQUE_SIZE];int front, rear;
} Deque;// 队头插入
int PushFront(Deque *dq, int x) {if ((dq->front - 1 + DEQUE_SIZE) % DEQUE_SIZE == dq->rear) {printf("队列已满");return 0;}dq->front = (dq->front - 1 + DEQUE_SIZE) % DEQUE_SIZE;dq->data[dq->front] = x;return 1;
}// 队尾删除
int PopRear(Deque *dq, int *x) {if (dq->front == dq->rear) {printf("队列为空");return 0;}*x = dq->data[(dq->rear - 1 + DEQUE_SIZE) % DEQUE_SIZE];dq->rear = (dq->rear - 1 + DEQUE_SIZE) % DEQUE_SIZE;return 1;
}
五、时间复杂度分析
操作 | 顺序队列 | 循环队列 | 双向队列(数组) |
---|---|---|---|
入队(尾部) | O(1) | O(1) | O(1) |
出队(头部) | O(n) | O(1) | O(1) |
随机访问 | O(1) | O(1) | O(1) |
解释:顺序队列出队需移动元素,故为O(n);循环队列通过模运算避免移动,所有操作O(1)。
六、考研高频考点
- 循环队列的判空/满条件(如:
(rear + 1) % size == front
)。 - 双端队列的应用:滑动窗口、页面置换算法。
- 队列与层次遍历(BFS) :求二叉树层数、最短路径。
七、经典算法题解析(补充至3题)
题目1:用队列实现栈(LeetCode 225)
问题描述:
使用两个队列实现一个后进先出(LIFO)的栈,并支持普通栈的全部操作(push
, pop
, top
, empty
)。
解法思路:
- 核心矛盾:队列是FIFO,而栈是LIFO。
- 关键操作:每次
push
新元素时,先将元素加入辅助队列,再将主队列中的元素依次移入辅助队列,最后交换两个队列的角色。 - 时间复杂度:
push
为O(n),其他操作为O(1)。
C语言代码(基于循环队列实现):
#include <stdlib.h>
#define MAX_SIZE 100typedef struct {CircularQueue q1; // 主队列CircularQueue q2; // 辅助队列
} MyStack;MyStack* myStackCreate() {MyStack *stack = (MyStack*)malloc(sizeof(MyStack));stack->q1.front = stack->q1.rear = 0;stack->q2.front = stack->q2.rear = 0;return stack;
}void myStackPush(MyStack* obj, int x) {// 先将元素加入辅助队列q2EnQueue(&obj->q2, x);// 将q1中的元素全部转移到q2while (!QueueEmpty(&obj->q1)) {int val;DeQueue(&obj->q1, &val);EnQueue(&obj->q2, val);}// 交换q1和q2的角色CircularQueue temp = obj->q1;obj->q1 = obj->q2;obj->q2 = temp;
}int myStackPop(MyStack* obj) {int val;DeQueue(&obj->q1, &val);return val;
}int myStackTop(MyStack* obj) {return obj->q1.data[obj->q1.front];
}int myStackEmpty(MyStack* obj) {return QueueEmpty(&obj->q1);
}
易错点:
- 队列判空条件错误:未正确实现
QueueEmpty
函数,导致逻辑混乱。 - 未及时交换队列:在
push
操作后必须交换队列,否则后续操作会出错。
题目2:二叉树的层序遍历(LeetCode 102)
问题描述:
给定一个二叉树,返回其按层序遍历得到的节点值(即逐层从左到右访问所有节点)。
解法思路:
- BFS框架:用队列存储每层的节点,每次处理一层节点时,记录当前队列长度(即该层节点数),依次出队并将子节点入队。
- 时间复杂度:O(n),每个节点进出队列一次。
C语言代码(需结合队列结构):
typedef struct TreeNode {int val;struct TreeNode *left, *right;
} TreeNode;int** levelOrder(TreeNode* root, int* returnSize, int** returnColumnSizes) {if (!root) return NULL;// 初始化队列CircularQueue q;q.front = q.rear = 0;EnQueue(&q, root);int**res = (int**)malloc(sizeof(int*) * MAX_SIZE);*returnColumnSizes = (int*)malloc(sizeof(int) * MAX_SIZE);*returnSize = 0;while (!QueueEmpty(&q)) {int level_size = (q.rear - q.front + MAX_SIZE) % MAX_SIZE;res[*returnSize] = (int*)malloc(sizeof(int) * level_size);(*returnColumnSizes)[*returnSize] = level_size;for (int i = 0; i < level_size; i++) {TreeNode* node;DeQueue(&q, &node); // 出队当前层节点res[*returnSize][i] = node->val;if (node->left) EnQueue(&q, node->left); // 左子节点入队if (node->right) EnQueue(&q, node->right); // 右子节点入队}(*returnSize)++;}return res;
}
关键点解析:
- 记录每层大小:
level_size
用于确定当前层的节点数量,确保结果数组正确分配内存。 - 子节点入队条件:需判断子节点非空再入队,避免队列中混入空指针。
题目3:设计循环双端队列(LeetCode 641)
问题描述:
设计实现一个循环双端队列,支持以下操作:
insertFront()
:在队头添加元素。insertLast()
:在队尾添加元素。deleteFront()
:删除队头元素。deleteLast()
:删除队尾元素。getFront()
:获取队头元素。getRear()
:获取队尾元素。isEmpty()
:检查队列是否为空。isFull()
:检查队列是否已满。
解法思路:
- 循环数组实现:利用取模运算处理索引循环,牺牲一个存储单元区分队空和队满。
- 边界处理:插入和删除时需分别处理
front
和rear
的移动方向。
C语言代码:
typedef struct {int *data;int front, rear;int capacity;
} MyCircularDeque;MyCircularDeque* myCircularDequeCreate(int k) {MyCircularDeque* dq = (MyCircularDeque*)malloc(sizeof(MyCircularDeque));dq->data = (int*)malloc(sizeof(int) * (k + 1)); // 多分配一个空间用于判满dq->front = dq->rear = 0;dq->capacity = k + 1;return dq;
}int myCircularDequeInsertFront(MyCircularDeque* obj, int value) {if (myCircularDequeIsFull(obj)) return 0;obj->front = (obj->front - 1 + obj->capacity) % obj->capacity; // 队头前移obj->data[obj->front] = value;return 1;
}int myCircularDequeInsertLast(MyCircularDeque* obj, int value) {if (myCircularDequeIsFull(obj)) return 0;obj->data[obj->rear] = value;obj->rear = (obj->rear + 1) % obj->capacity; // 队尾后移return 1;
}int myCircularDequeDeleteFront(MyCircularDeque* obj) {if (myCircularDequeIsEmpty(obj)) return 0;obj->front = (obj->front + 1) % obj->capacity;return 1;
}int myCircularDequeDeleteLast(MyCircularDeque* obj) {if (myCircularDequeIsEmpty(obj)) return 0;obj->rear = (obj->rear - 1 + obj->capacity) % obj->capacity;return 1;
}int myCircularDequeGetFront(MyCircularDeque* obj) {return myCircularDequeIsEmpty(obj) ? -1 : obj->data[obj->front];
}int myCircularDequeGetRear(MyCircularDeque* obj) {return myCircularDequeIsEmpty(obj) ? -1 : obj->data[(obj->rear - 1 + obj->capacity) % obj->capacity];
}int myCircularDequeIsEmpty(MyCircularDeque* obj) {return obj->front == obj->rear;
}int myCircularDequeIsFull(MyCircularDeque* obj) {return (obj->rear + 1) % obj->capacity == obj->front;
}
易错点:
- 索引计算错误:在队头插入时,
front
应前移(front - 1
),而非后移。 - 判满条件错误:必须牺牲一个存储单元,否则队空和队满条件会冲突。
八、易错点总结
- 循环队列的判满条件:必须牺牲一个存储单元,否则与判空条件冲突。
- 指针初始值:
front
和rear
通常初始化为0,但需根据实现方式调整。 - 边界检查:操作前必须检查队列是否为空/满,避免溢出或空指针。
九、解题方法论
- 选择合适结构:空间固定用循环队列,动态扩展用链队列。
- BFS模板:队列维护当前层节点,逐层扩展。
- 滑动窗口:双端队列维护极值,减少重复计算。
附:考研复习建议
- 重点掌握:循环队列的判空判满、双端队列的应用。
- 典型习题:设计循环队列(LeetCode 622)、二叉树的右视图(BFS应用)。
- 易错题:循环队列长度计算
(rear - front + size) % size
。
通过系统梳理和代码实践,可深入理解队列的核心思想与应用场景。