1. 栈与队列:数据结构中的两大基石
第一次接触栈和队列时,我脑海中浮现的是自助餐厅的餐盘架和超市收银台前的队伍。这种生活化的联想帮助我快速理解了这两种基础数据结构的特点。作为线性表的两种特殊表现形式,栈和队列在计算机科学中的地位,就像螺丝刀和扳手在工具箱里的地位一样基础而重要。
栈遵循LIFO(后进先出)原则,就像叠放的餐盘,你总是取走最上面那个;而队列遵循FIFO(先进先出)原则,就像排队结账,先来的人先离开。这两种数据结构在算法实现、系统设计、编译器构造等场景中无处不在。比如函数调用栈管理着程序执行流程,消息队列处理着系统间的异步通信。
理解它们的实现原理和适用场景,是每个程序员必须打好的基本功。本文将带你从零开始,用C语言实现这两种数据结构,并探讨它们的典型应用和性能特点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 栈的定义与实现
2.1 栈的基本概念
栈(Stack)是一种操作受限的线性表,只允许在表的一端(称为栈顶)进行插入和删除操作。这种限制赋予了栈独特的"后进先出"特性,就像手枪的弹匣,最后压入的子弹总是最先被击发。
栈的两个基本操作:
- 压栈(Push):将元素放入栈顶
- 出栈(Pop):移除并返回栈顶元素
此外,我们通常还需要:
- 获取栈顶元素(Peek/Top):查看但不移除栈顶元素
- 判断栈是否为空(isEmpty)
- 获取栈中元素数量(size)
2.2 栈的顺序存储实现
用数组实现栈是最直观的方式。我们需要维护一个数组和一个指向栈顶的指针(或索引)。
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int top; // 栈顶指针
} SeqStack;
// 初始化栈
void InitStack(SeqStack *S) {
S->top = -1; // 空栈时top为-1
}
// 判断栈空
int IsEmpty(SeqStack *S) {
return S->top == -1;
}
// 判断栈满
int IsFull(SeqStack *S) {
return S->top == MAX_SIZE - 1;
}
// 压栈
int Push(SeqStack *S, int value) {
if (IsFull(S)) {
return 0; // 栈满,操作失败
}
S->data[++S->top] = value;
return 1;
}
// 出栈
int Pop(SeqStack *S, int *value) {
if (IsEmpty(S)) {
return 0; // 栈空,操作失败
}
*value = S->data[S->top--];
return 1;
}
// 获取栈顶元素
int GetTop(SeqStack *S, int *value) {
if (IsEmpty(S)) {
return 0;
}
*value = S->data[S->top];
return 1;
}
注意:数组实现的栈需要预先分配固定大小的内存空间,可能造成空间浪费或栈溢出。在实际工程中,通常会实现动态扩容机制。
2.3 栈的链式存储实现
当栈的大小不可预知时,链表实现更为灵活:
c复制typedef struct StackNode {
int data;
struct StackNode *next;
} StackNode;
typedef struct {
StackNode *top; // 栈顶指针
int size; // 栈大小
} LinkStack;
// 初始化栈
void InitStack(LinkStack *S) {
S->top = NULL;
S->size = 0;
}
// 压栈
void Push(LinkStack *S, int value) {
StackNode *newNode = (StackNode*)malloc(sizeof(StackNode));
newNode->data = value;
newNode->next = S->top;
S->top = newNode;
S->size++;
}
// 出栈
int Pop(LinkStack *S, int *value) {
if (S->top == NULL) {
return 0; // 栈空
}
StackNode *temp = S->top;
*value = temp->data;
S->top = temp->next;
free(temp);
S->size--;
return 1;
}
链式栈的优点是无需预先确定栈的大小,可以动态增长,但每个元素需要额外的指针空间,且操作略慢于数组实现。
2.4 栈的应用场景
-
函数调用栈:程序执行时,每次函数调用都会在栈中压入一个栈帧,存储局部变量、返回地址等信息。
-
表达式求值:处理括号匹配、中缀表达式转后缀表达式等问题。
-
浏览器的前进后退:用两个栈分别存储已访问和已后退的页面。
-
撤销操作:许多编辑器的撤销功能就是用栈实现的。
-
深度优先搜索(DFS):图的DFS算法通常使用栈来实现。
3. 队列的定义与实现
3.1 队列的基本概念
队列(Queue)是另一种操作受限的线性表,只允许在表的一端(队尾)插入,在另一端(队首)删除。这种"先进先出"的特性使其成为处理有序请求的理想结构,就像银行叫号系统,先取号的人先获得服务。
队列的基本操作:
- 入队(Enqueue):在队尾添加元素
- 出队(Dequeue):移除并返回队首元素
- 获取队首元素(Front)
- 判断队列是否为空(isEmpty)
- 获取队列中元素数量(size)
3.2 队列的顺序存储实现
用数组实现队列时,我们需要维护队首(front)和队尾(rear)两个指针:
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front; // 队首指针
int rear; // 队尾指针
} SeqQueue;
// 初始化队列
void InitQueue(SeqQueue *Q) {
Q->front = Q->rear = 0;
}
// 判断队列空
int IsEmpty(SeqQueue *Q) {
return Q->front == Q->rear;
}
// 判断队列满
int IsFull(SeqQueue *Q) {
return (Q->rear + 1) % MAX_SIZE == Q->front;
}
// 入队
int Enqueue(SeqQueue *Q, int value) {
if (IsFull(Q)) {
return 0; // 队列满
}
Q->data[Q->rear] = value;
Q->rear = (Q->rear + 1) % MAX_SIZE; // 循环利用空间
return 1;
}
// 出队
int Dequeue(SeqQueue *Q, int *value) {
if (IsEmpty(Q)) {
return 0; // 队列空
}
*value = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX_SIZE;
return 1;
}
注意:这种实现方式会浪费一个存储单元来区分队空和队满的情况。另一种方法是增加一个size变量记录当前元素数量。
3.3 队列的链式存储实现
链式队列可以避免顺序队列的空间限制问题:
c复制typedef struct QNode {
int data;
struct QNode *next;
} QNode;
typedef struct {
QNode *front; // 队首指针
QNode *rear; // 队尾指针
int size; // 队列大小
} LinkQueue;
// 初始化队列
void InitQueue(LinkQueue *Q) {
Q->front = Q->rear = NULL;
Q->size = 0;
}
// 入队
void Enqueue(LinkQueue *Q, int value) {
QNode *newNode = (QNode*)malloc(sizeof(QNode));
newNode->data = value;
newNode->next = NULL;
if (Q->rear == NULL) { // 空队列
Q->front = Q->rear = newNode;
} else {
Q->rear->next = newNode;
Q->rear = newNode;
}
Q->size++;
}
// 出队
int Dequeue(LinkQueue *Q, int *value) {
if (Q->front == NULL) {
return 0; // 队列空
}
QNode *temp = Q->front;
*value = temp->data;
Q->front = temp->next;
if (Q->front == NULL) { // 队列已空
Q->rear = NULL;
}
free(temp);
Q->size--;
return 1;
}
链式队列的优点是空间利用率高,可以动态增长,但每个元素需要额外的指针空间。
3.4 队列的应用场景
-
消息队列:系统间异步通信,如RabbitMQ、Kafka等消息中间件。
-
任务调度:操作系统中的进程调度、线程池任务队列。
-
广度优先搜索(BFS):图的BFS算法通常使用队列来实现。
-
打印机任务队列:多个打印请求按顺序处理。
-
数据缓冲:生产者和消费者速度不匹配时的缓冲机制。
4. 循环队列的实现与优化
4.1 循环队列的概念
顺序队列存在"假溢出"问题:当队尾指针到达数组末端,但数组前端仍有空闲空间时,无法继续入队。循环队列通过将数组视为环形结构来解决这个问题,当指针到达数组末端时,自动绕回到数组开头。
循环队列的关键点:
- 队空条件:front == rear
- 队满条件:(rear + 1) % MAX_SIZE == front
- 指针移动:front = (front + 1) % MAX_SIZE
4.2 循环队列的实现
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} CircularQueue;
// 初始化
void InitQueue(CircularQueue *Q) {
Q->front = Q->rear = 0;
}
// 判断空
int IsEmpty(CircularQueue *Q) {
return Q->front == Q->rear;
}
// 判断满
int IsFull(CircularQueue *Q) {
return (Q->rear + 1) % MAX_SIZE == Q->front;
}
// 入队
int Enqueue(CircularQueue *Q, int value) {
if (IsFull(Q)) {
return 0;
}
Q->data[Q->rear] = value;
Q->rear = (Q->rear + 1) % MAX_SIZE;
return 1;
}
// 出队
int Dequeue(CircularQueue *Q, int *value) {
if (IsEmpty(Q)) {
return 0;
}
*value = Q->data[Q->front];
Q->front = (Q->front + 1) % MAX_SIZE;
return 1;
}
// 获取队首元素
int GetFront(CircularQueue *Q, int *value) {
if (IsEmpty(Q)) {
return 0;
}
*value = Q->data[Q->front];
return 1;
}
// 获取队列元素数量
int QueueSize(CircularQueue *Q) {
return (Q->rear - Q->front + MAX_SIZE) % MAX_SIZE;
}
4.3 循环队列的优化
- 增加size变量:可以区分队空和队满的情况,避免浪费一个存储单元。
c复制typedef struct {
int data[MAX_SIZE];
int front;
int rear;
int size; // 当前元素数量
} CircularQueue;
// 判断空
int IsEmpty(CircularQueue *Q) {
return Q->size == 0;
}
// 判断满
int IsFull(CircularQueue *Q) {
return Q->size == MAX_SIZE;
}
// 入队
int Enqueue(CircularQueue *Q, int value) {
if (IsFull(Q)) {
return 0;
}
Q->data[Q->rear] = value;
Q->rear = (Q->rear + 1) % MAX_SIZE;
Q->size++;
return 1;
}
- 动态扩容:当队列满时,自动扩大存储空间。
c复制int ResizeQueue(CircularQueue *Q, int newSize) {
if (newSize <= Q->size) {
return 0; // 新大小不能小于当前元素数量
}
int *newData = (int*)malloc(newSize * sizeof(int));
if (!newData) {
return 0;
}
// 将元素复制到新数组
int i = 0;
while (!IsEmpty(Q)) {
Dequeue(Q, &newData[i++]);
}
free(Q->data);
Q->data = newData;
Q->front = 0;
Q->rear = Q->size;
Q->capacity = newSize;
return 1;
}
5. 栈与队列的常见问题与解决方案
5.1 栈的常见问题
-
栈溢出(Stack Overflow)
- 原因:递归调用过深或大量局部变量
- 解决方案:改用迭代算法或增加栈空间
-
内存泄漏(链式栈)
- 原因:出栈时未释放节点内存
- 解决方案:确保Pop操作中释放内存
-
多线程安全问题
- 原因:多个线程同时操作栈
- 解决方案:使用互斥锁保护共享栈
5.2 队列的常见问题
-
假溢出(顺序队列)
- 原因:队尾到达数组末端但前端有空闲
- 解决方案:使用循环队列
-
队列满处理
- 解决方案:动态扩容或实现阻塞/非阻塞策略
-
优先级队列需求
- 解决方案:实现优先队列(堆结构)
5.3 典型算法问题
- 用栈实现队列
- 思路:使用两个栈,一个用于入队,一个用于出队
c复制typedef struct {
SeqStack inStack;
SeqStack outStack;
} StackQueue;
void Enqueue(StackQueue *Q, int value) {
Push(&Q->inStack, value);
}
int Dequeue(StackQueue *Q, int *value) {
if (IsEmpty(&Q->outStack)) {
// 将inStack的元素全部转移到outStack
while (!IsEmpty(&Q->inStack)) {
int temp;
Pop(&Q->inStack, &temp);
Push(&Q->outStack, temp);
}
}
return Pop(&Q->outStack, value);
}
- 用队列实现栈
- 思路:使用两个队列,保持一个队列为空
c复制typedef struct {
SeqQueue q1;
SeqQueue q2;
} QueueStack;
void Push(QueueStack *S, int value) {
// 总是向非空队列添加元素
if (!IsEmpty(&S->q1)) {
Enqueue(&S->q1, value);
} else {
Enqueue(&S->q2, value);
}
}
int Pop(QueueStack *S, int *value) {
SeqQueue *nonEmpty, *empty;
if (!IsEmpty(&S->q1)) {
nonEmpty = &S->q1;
empty = &S->q2;
} else if (!IsEmpty(&S->q2)) {
nonEmpty = &S->q2;
empty = &S->q1;
} else {
return 0; // 栈空
}
// 将非空队列的元素转移到空队列,留下最后一个
while (QueueSize(nonEmpty) > 1) {
int temp;
Dequeue(nonEmpty, &temp);
Enqueue(empty, temp);
}
return Dequeue(nonEmpty, value);
}
- 最小栈问题
- 要求:设计一个能在O(1)时间内获取最小元素的栈
- 解决方案:使用辅助栈同步存储最小值
c复制typedef struct {
SeqStack dataStack;
SeqStack minStack; // 同步存储当前最小值
} MinStack;
void Push(MinStack *S, int value) {
Push(&S->dataStack, value);
int currentMin;
if (IsEmpty(&S->minStack) || value < GetTop(&S->minStack, ¤tMin)) {
Push(&S->minStack, value);
} else {
Push(&S->minStack, currentMin);
}
}
int Pop(MinStack *S, int *value) {
int temp;
Pop(&S->minStack, &temp);
return Pop(&S->dataStack, value);
}
int GetMin(MinStack *S, int *value) {
return GetTop(&S->minStack, value);
}
6. 性能分析与比较
6.1 时间复杂度比较
| 操作 | 栈(数组/链表) | 队列(数组/链表) | 循环队列 |
|---|---|---|---|
| 插入 | O(1) | O(1) | O(1) |
| 删除 | O(1) | O(1) | O(1) |
| 访问顶部 | O(1) | O(1) | O(1) |
| 搜索 | O(n) | O(n) | O(n) |
6.2 空间效率比较
-
数组实现:
- 优点:内存连续,访问速度快
- 缺点:大小固定,可能浪费空间或不够用
-
链表实现:
- 优点:动态增长,空间利用率高
- 缺点:每个元素需要额外指针空间,访问速度稍慢
-
循环队列:
- 优点:解决了顺序队列的假溢出问题
- 缺点:实现稍复杂,仍可能有空间浪费
6.3 选择建议
-
选择栈的情况:
- 需要后进先出的访问顺序
- 需要快速访问最近添加的元素
- 实现递归算法的非递归版本
-
选择队列的情况:
- 需要先进先出的访问顺序
- 处理异步消息或任务
- 实现广度优先搜索
-
选择循环队列的情况:
- 队列大小相对固定
- 需要高效利用连续内存空间
- 避免频繁内存分配释放
7. 实际工程中的扩展与优化
7.1 线程安全实现
在多线程环境下,需要对栈和队列的操作加锁:
c复制#include <pthread.h>
typedef struct {
SeqStack stack;
pthread_mutex_t lock;
} ThreadSafeStack;
void InitTSStack(ThreadSafeStack *S) {
InitStack(&S->stack);
pthread_mutex_init(&S->lock, NULL);
}
int TSPush(ThreadSafeStack *S, int value) {
pthread_mutex_lock(&S->lock);
int result = Push(&S->stack, value);
pthread_mutex_unlock(&S->lock);
return result;
}
int TSPop(ThreadSafeStack *S, int *value) {
pthread_mutex_lock(&S->lock);
int result = Pop(&S->stack, value);
pthread_mutex_unlock(&S->lock);
return result;
}
7.2 无锁队列实现
对于高性能场景,可以使用CAS(Compare-And-Swap)操作实现无锁队列:
c复制#include <stdatomic.h>
typedef struct {
_Atomic int *data;
_Atomic int front;
_Atomic int rear;
int capacity;
} LockFreeQueue;
int LFEnqueue(LockFreeQueue *Q, int value) {
int currentRear = atomic_load(&Q->rear);
int nextRear = (currentRear + 1) % Q->capacity;
if (nextRear == atomic_load(&Q->front)) {
return 0; // 队列满
}
Q->data[currentRear] = value;
atomic_store(&Q->rear, nextRear);
return 1;
}
7.3 阻塞队列实现
当队列为空时,出队操作阻塞直到有元素可用;当队列满时,入队操作阻塞直到有空间可用:
c复制#include <pthread.h>
#include <semaphore.h>
typedef struct {
int *data;
int front;
int rear;
int capacity;
pthread_mutex_t lock;
sem_t notEmpty;
sem_t notFull;
} BlockingQueue;
void InitBlockingQueue(BlockingQueue *Q, int capacity) {
Q->data = (int*)malloc(capacity * sizeof(int));
Q->front = Q->rear = 0;
Q->capacity = capacity;
pthread_mutex_init(&Q->lock, NULL);
sem_init(&Q->notEmpty, 0, 0);
sem_init(&Q->notFull, 0, capacity);
}
void Enqueue(BlockingQueue *Q, int value) {
sem_wait(&Q->notFull); // 等待有空间
pthread_mutex_lock(&Q->lock);
Q->data[Q->rear] = value;
Q->rear = (Q->rear + 1) % Q->capacity;
pthread_mutex_unlock(&Q->lock);
sem_post(&Q->notEmpty); // 通知有元素可用
}
int Dequeue(BlockingQueue *Q, int *value) {
sem_wait(&Q->notEmpty); // 等待有元素
pthread_mutex_lock(&Q->lock);
*value = Q->data[Q->front];
Q->front = (Q->front + 1) % Q->capacity;
pthread_mutex_unlock(&Q->lock);
sem_post(&Q->notFull); // 通知有空间可用
return 1;
}
7.4 双端队列(Deque)实现
双端队列允许在两端进行插入和删除操作,结合了栈和队列的特性:
c复制#define MAX_SIZE 100
typedef struct {
int data[MAX_SIZE];
int front;
int rear;
} Deque;
// 初始化
void InitDeque(Deque *D) {
D->front = D->rear = 0;
}
// 前端插入
int PushFront(Deque *D, int value) {
if ((D->front - 1 + MAX_SIZE) % MAX_SIZE == D->rear) {
return 0; // 队列满
}
D->front = (D->front - 1 + MAX_SIZE) % MAX_SIZE;
D->data[D->front] = value;
return 1;
}
// 后端插入
int PushBack(Deque *D, int value) {
if ((D->rear + 1) % MAX_SIZE == D->front) {
return 0; // 队列满
}
D->data[D->rear] = value;
D->rear = (D->rear + 1) % MAX_SIZE;
return 1;
}
// 前端删除
int PopFront(Deque *D, int *value) {
if (D->front == D->rear) {
return 0; // 队列空
}
*value = D->data[D->front];
D->front = (D->front + 1) % MAX_SIZE;
return 1;
}
// 后端删除
int PopBack(Deque *D, int *value) {
if (D->front == D->rear) {
return 0; // 队列空
}
D->rear = (D->rear - 1 + MAX_SIZE) % MAX_SIZE;
*value = D->data[D->rear];
return 1;
}
8. 现代编程语言中的栈与队列
8.1 C++ STL中的实现
C++标准模板库提供了完善的栈和队列实现:
cpp复制#include <stack>
#include <queue>
void STLDemo() {
// 栈
std::stack<int> s;
s.push(1); // 压栈
int top = s.top(); // 获取栈顶
s.pop(); // 出栈
// 队列
std::queue<int> q;
q.push(1); // 入队
int front = q.front(); // 获取队首
q.pop(); // 出队
// 双端队列
std::deque<int> dq;
dq.push_front(1); // 前端插入
dq.push_back(2); // 后端插入
dq.pop_front(); // 前端删除
dq.pop_back(); // 后端删除
}
8.2 Java集合框架中的实现
Java提供了多种栈和队列的实现:
java复制import java.util.Stack;
import java.util.Queue;
import java.util.LinkedList;
import java.util.ArrayDeque;
public class CollectionDemo {
public static void main(String[] args) {
// 栈(Java官方建议使用Deque代替Stack)
Stack<Integer> stack = new Stack<>();
stack.push(1);
int top = stack.peek();
stack.pop();
// 队列
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队
int head = queue.peek(); // 获取队首
queue.poll(); // 出队
// 双端队列
Deque<Integer> deque = new ArrayDeque<>();
deque.offerFirst(1); // 前端插入
deque.offerLast(2); // 后端插入
deque.pollFirst(); // 前端删除
deque.pollLast(); // 后端删除
}
}
8.3 Python中的实现
Python使用列表实现栈,collections.deque实现队列:
python复制from collections import deque
# 栈
stack = []
stack.append(1) # 压栈
top = stack[-1] # 获取栈顶
stack.pop() # 出栈
# 队列
queue = deque()
queue.append(1) # 入队
front = queue[0] # 获取队首
queue.popleft() # 出队
# 双端队列
dq = deque()
dq.appendleft(1) # 前端插入
dq.append(2) # 后端插入
dq.popleft() # 前端删除
dq.pop() # 后端删除
9. 算法实战:栈与队列的典型应用
9.1 栈的应用:括号匹配
检查表达式中的括号是否匹配:
c复制int IsBracketMatch(const char *expr) {
SeqStack S;
InitStack(&S);
for (int i = 0; expr[i] != '\0'; i++) {
if (expr[i] == '(' || expr[i] == '[' || expr[i] == '{') {
Push(&S, expr[i]);
} else if (expr[i] == ')' || expr[i] == ']' || expr[i] == '}') {
if (IsEmpty(&S)) {
return 0; // 栈空但遇到右括号
}
char top;
Pop(&S, &top);
if (!((top == '(' && expr[i] == ')') ||
(top == '[' && expr[i] == ']') ||
(top == '{' && expr[i] == '}'))) {
return 0; // 括号不匹配
}
}
}
return IsEmpty(&S); // 栈空则匹配,否则不匹配
}
9.2 队列的应用:二叉树的层次遍历
使用队列实现二叉树的广度优先遍历:
c复制typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
void LevelOrderTraversal(TreeNode *root) {
if (root == NULL) return;
LinkQueue Q;
InitQueue(&Q);
Enqueue(&Q, (int)root); // 存储指针的整数值
while (!IsEmpty(&Q)) {
int value;
Dequeue(&Q, &value);
TreeNode *node = (TreeNode*)value;
printf("%d ", node->data);
if (node->left != NULL) {
Enqueue(&Q, (int)node->left);
}
if (node->right != NULL) {
Enqueue(&Q, (int)node->right);
}
}
}
9.3 循环队列应用:生产者消费者问题
使用循环队列实现生产者消费者模型:
c复制#define BUFFER_SIZE 10
typedef struct {
CircularQueue buffer;
pthread_mutex_t mutex;
sem_t empty;
sem_t full;
} PCBuffer;
void Producer(PCBuffer *pc) {
int item;
while (1) {
item = produce_item(); // 生产数据
sem_wait(&pc->empty); // 等待空位
pthread_mutex_lock(&pc->mutex);
Enqueue(&pc->buffer, item); // 放入缓冲区
pthread_mutex_unlock(&pc->mutex);
sem_post(&pc->full); // 通知有数据可用
}
}
void Consumer(PCBuffer *pc) {
int item;
while (1) {
sem_wait(&pc->full); // 等待数据
pthread_mutex_lock(&pc->mutex);
Dequeue(&pc->buffer, &item); // 取出数据
consume_item(item); // 消费数据
pthread_mutex_unlock(&pc->mutex);
sem_post(&pc->empty); // 通知有空位
}
}
9.4 单调栈应用:下一个更大元素
使用单调栈高效查找数组中每个元素的下一个更大元素:
c复制void NextGreaterElement(int nums[], int n, int result[]) {
SeqStack S;
InitStack(&S);
for (int i = n - 1; i >= 0; i--) {
// 弹出栈中比当前元素小的元素
while (!IsEmpty(&S) && GetTop(&S, &temp) && temp <= nums[i]) {
Pop(&S, &temp);
}
// 栈顶就是下一个更大元素
if (IsEmpty(&S)) {
result[i] = -1; // 没有更大的元素
} else {
GetTop(&S, &result[i]);
}
// 当前元素入栈
Push(&S, nums[i]);
}
}
10. 性能调优与最佳实践
10.1 栈的性能调优
-
预分配足够空间:根据应用场景预估最大栈深度,避免频繁扩容。
-
批量操作优化:支持批量压栈/出栈操作,减少函数调用开销。
-
内存池技术:对于链式栈,使用内存池管理节点,减少malloc/free调用。
10.2 队列的性能调优
-
合适的大小:循环队列的大小最好是2的幂次,可以用位运算代替取模:
c复制#define QUEUE_SIZE 1024 // 2^10 #define MASK (QUEUE_SIZE - 1) rear = (rear + 1) & MASK; // 代替 (rear + 1) % QUEUE_SIZE -
缓存友好设计:让经常一起访问的元素位于同一缓存行。
-
无锁实现:在高并发场景下,考虑无锁队列实现。
10.3 选择数据结构的最佳实践
-
栈的使用场景:
- 需要撤销/重做功能
- 深度优先搜索
- 语法分析、表达式求值
- 函数调用管理
-
队列的使用场景:
- 消息传递系统
- 任务调度
- 广度优先搜索
- 缓冲区和流量控制
-
双端队列的特殊优势:
- 滑动窗口问题
- 实现栈和队列的所有功能
- 需要频繁在两端操作的场景
10.4 内存管理技巧
-
数组实现的注意事项:
- 初始大小要合理
- 实现动态扩容策略
- 考虑缩容以避免内存浪费
-
链表实现的注意事项:
- 使用内存池管理节点
- 注意释放所有节点内存
- 考虑使用哨兵节点简化边界条件处理
-
混合实现策略:
- 小数据量时使用数组实现
- 大数据量时切换为链表实现
- 结合两者的优势
11. 测试与验证
11.1 栈的单元测试
编写全面的测试用例验证栈的实现:
c复制void TestStack() {
SeqStack S;
InitStack(&S);
// 测试栈空
assert(IsEmpty(&S));
// 测试压栈
for (int i = 0; i < MAX_SIZE; i++) {
assert(Push(&S, i));
}
assert(!Push(&S, MAX_SIZE)); // 栈满应失败
// 测试获取栈顶
int top;
assert(GetTop(&S, &top) && top == MAX_SIZE - 1);
// 测试出栈
for (int i = MAX_SIZE - 1; i >= 0; i--) {
int value;
assert(Pop(&S, &value) && value == i);
}
assert(IsEmpty(&S));
// 测试栈空出栈
int value;
assert(!Pop(&S, &value));
printf("Stack tests passed!\n");
}
11.2 队列的单元测试
验证队列实现的正确性:
c复制void TestQueue() {
SeqQueue Q;
InitQueue(&Q);
// 测试队列空
assert(IsEmpty(&Q));
// 测试入队
for (int i = 0; i < MAX_SIZE - 1; i++) {
assert(Enqueue(&Q, i));
}
assert(!Enqueue(&Q, MAX_SIZE - 1)); // 队列满应失败
// 测试出队
for (int i = 0; i < MAX_SIZE - 1; i++) {
int value;
assert(Dequeue(&Q, &value) && value == i);
}
assert(IsEmpty(&Q));
// 测试队列空出队
int value;
assert(!Dequeue(&Q, &value));
// 测试循环特性
for (int i = 0; i < MAX_SIZE / 2; i++) {
assert(Enqueue(&Q, i));
}
for (int i = 0; i < MAX_SIZE / 4; i++) {
int value;
assert(Dequeue(&Q, &value));
}
for (int i = 0; i < MAX_SIZE / 2; i++) {
assert(Enqueue(&Q, i));
}
printf("Queue tests passed!\n");
}
11.3 性能测试
比较不同实现的性能差异:
c复制#include <time.h>
void PerformanceTest() {
const int TEST_SIZE = 1000000;
clock_t start, end;
// 测试顺序栈
SeqStack stack;
InitStack(&stack);
start = clock();
