网站首页 > 文章精选 正文
栈(Stack)和队列(Queue)是两种基础且广泛应用的线性数据结构。它们都用于存储数据集合,但其主要区别在于元素的存取方式:栈遵循后进先出(LIFO, Last-In, First-Out)原则,而队列遵循先进先出(FIFO, First-In, First-Out)原则。
本教程将介绍如何使用C语言实现栈和队列,包括基于数组的实现和基于链表的实现,并探讨它们的应用场景。
1. 栈 (Stack)
栈就像一叠盘子,你只能在最上面放盘子,也只能从最上面取盘子。
栈的基本操作
- Push:将元素添加到栈顶。
- Pop:从栈顶移除并返回元素。
- Peek/Top:查看栈顶元素但不移除。
- IsEmpty:检查栈是否为空。
- IsFull (对于基于数组的定长栈):检查栈是否已满。
a. 基于数组的栈实现
使用一个固定大小的数组来存储栈元素,并用一个整数 top 来追踪栈顶位置。
#include <stdio.h>
#include <stdlib.h>
#include <limits.h> // For INT_MIN
#define MAX_STACK_SIZE 100
// 数组栈结构定义
typedef struct ArrayStack {
int items[MAX_STACK_SIZE];
int top; // 指向栈顶元素的索引,-1表示栈空
} ArrayStack;
// 初始化栈
ArrayStack* create_array_stack() {
ArrayStack *stack = (ArrayStack*)malloc(sizeof(ArrayStack));
if (!stack) {
perror("Failed to create array stack");
return NULL;
}
stack->top = -1; // 初始化为空栈
return stack;
}
// 检查栈是否已满
int is_array_stack_full(ArrayStack *stack) {
return stack->top == MAX_STACK_SIZE - 1;
}
// 检查栈是否为空
int is_array_stack_empty(ArrayStack *stack) {
return stack->top == -1;
}
// Push操作
void array_stack_push(ArrayStack *stack, int item) {
if (is_array_stack_full(stack)) {
printf("Stack Overflow! Cannot push %d.\n", item);
return;
}
stack->items[++(stack->top)] = item;
// printf("%d pushed to stack.\n", item);
}
// Pop操作
int array_stack_pop(ArrayStack *stack) {
if (is_array_stack_empty(stack)) {
printf("Stack Underflow! Cannot pop.\n");
return INT_MIN; // 返回一个错误指示值
}
return stack->items[(stack->top)--];
}
// Peek/Top操作
int array_stack_peek(ArrayStack *stack) {
if (is_array_stack_empty(stack)) {
printf("Stack is empty! Cannot peek.\n");
return INT_MIN;
}
return stack->items[stack->top];
}
// 释放栈 (对于数组栈,主要是释放结构体本身)
void free_array_stack(ArrayStack *stack) {
if (stack) {
free(stack);
}
}
// 数组栈使用示例
void demo_array_stack() {
printf("--- Array Stack Demo ---\n");
ArrayStack *stack = create_array_stack();
if (!stack) return;
array_stack_push(stack, 10);
array_stack_push(stack, 20);
array_stack_push(stack, 30);
printf("Top element is %d\n", array_stack_peek(stack)); // 30
printf("%d popped from stack\n", array_stack_pop(stack)); // 30
printf("%d popped from stack\n", array_stack_pop(stack)); // 20
array_stack_push(stack, 40);
printf("Top element is %d\n", array_stack_peek(stack)); // 40
printf("%d popped from stack\n", array_stack_pop(stack)); // 40
printf("%d popped from stack\n", array_stack_pop(stack)); // 10
printf("%d popped from stack\n", array_stack_pop(stack)); // Stack Underflow
free_array_stack(stack);
}
b. 基于链表的栈实现
使用单向链表来实现栈,链表的头节点即为栈顶。
// (需要之前单向链表的 SNode 定义)
// typedef struct SNode { int data; struct SNode *next; } SNode;
// 链式栈结构定义 (实际上只需要一个头指针)
typedef struct LinkedStack {
SNode *top; // 指向栈顶节点 (链表头)
int count; // 可选:记录元素数量
} LinkedStack;
// 初始化链式栈
LinkedStack* create_linked_stack() {
LinkedStack *stack = (LinkedStack*)malloc(sizeof(LinkedStack));
if (!stack) {
perror("Failed to create linked stack");
return NULL;
}
stack->top = NULL;
stack->count = 0;
return stack;
}
// 检查链式栈是否为空
int is_linked_stack_empty(LinkedStack *stack) {
return stack->top == NULL; // 或者 stack->count == 0
}
// Push操作 (相当于在链表头部插入)
void linked_stack_push(LinkedStack *stack, int item) {
SNode *new_node = (SNode*)malloc(sizeof(SNode)); // 使用SNode的创建函数更好
if (!new_node) {
perror("Failed to allocate node for push");
return;
}
new_node->data = item;
new_node->next = stack->top;
stack->top = new_node;
stack->count++;
// printf("%d pushed to linked stack.\n", item);
}
// Pop操作 (相当于删除链表头节点)
int linked_stack_pop(LinkedStack *stack) {
if (is_linked_stack_empty(stack)) {
printf("Linked Stack Underflow! Cannot pop.\n");
return INT_MIN;
}
SNode *temp = stack->top;
int popped_item = temp->data;
stack->top = stack->top->next;
free(temp);
stack->count--;
return popped_item;
}
// Peek/Top操作
int linked_stack_peek(LinkedStack *stack) {
if (is_linked_stack_empty(stack)) {
printf("Linked Stack is empty! Cannot peek.\n");
return INT_MIN;
}
return stack->top->data;
}
// 释放链式栈 (释放所有节点和栈结构体本身)
void free_linked_stack(LinkedStack *stack) {
if (!stack) return;
SNode *current = stack->top;
SNode *next_node;
while (current != NULL) {
next_node = current->next;
free(current);
current = next_node;
}
free(stack);
}
// 链式栈使用示例
void demo_linked_stack() {
printf("--- Linked Stack Demo ---\n");
LinkedStack *stack = create_linked_stack();
if (!stack) return;
linked_stack_push(stack, 100);
linked_stack_push(stack, 200);
linked_stack_push(stack, 300);
printf("Top element is %d\n", linked_stack_peek(stack)); // 300
printf("%d popped from linked stack\n", linked_stack_pop(stack)); // 300
printf("Top element is %d\n", linked_stack_peek(stack)); // 200
linked_stack_push(stack, 400);
printf("%d popped from linked stack\n", linked_stack_pop(stack)); // 400
printf("%d popped from linked stack\n", linked_stack_pop(stack)); // 200
printf("%d popped from linked stack\n", linked_stack_pop(stack)); // 100
printf("%d popped from linked stack\n", linked_stack_pop(stack)); // Underflow
free_linked_stack(stack);
}
栈的应用
- 函数调用栈:操作系统用栈来管理函数调用、局部变量和返回地址。
- 表达式求值:中缀表达式转后缀表达式,以及后缀表达式的求值。
- 括号匹配:检查代码或文本中的括号是否成对匹配。
- 深度优先搜索 (DFS):在图或树的遍历中,DFS算法通常使用栈(显式或隐式通过递归)。
- 撤销/重做操作:编辑器或软件中的撤销功能常用栈来实现。
- 浏览器历史记录的“后退”按钮。
2. 队列 (Queue)
队列就像排队买票,先来的人先买到票离开。
队列的基本操作
- Enqueue (Enq):将元素添加到队尾(rear)。
- Dequeue (Deq):从队头(front)移除并返回元素。
- Peek/Front:查看队头元素但不移除。
- IsEmpty:检查队列是否为空。
- IsFull (对于基于数组的定长队列):检查队列是否已满。
a. 基于数组的队列实现 (循环队列)
普通数组实现队列时,出队操作会导致所有后续元素前移(O(n)),效率低下。循环队列通过将数组视为环形来解决这个问题,使用 front 和 rear 两个指针(索引)以及一个 count 或特定逻辑来区分队空和队满。
#define MAX_QUEUE_SIZE 5 // 小一点方便演示循环
// 循环队列结构定义
typedef struct ArrayQueue {
int items[MAX_QUEUE_SIZE];
int front; // 指向队头元素的前一个位置或队头元素本身 (不同实现方式)
int rear; // 指向队尾元素的实际位置
int count; // 当前队列中的元素数量
} ArrayQueue;
// 初始化循环队列
ArrayQueue* create_array_queue() {
ArrayQueue *queue = (ArrayQueue*)malloc(sizeof(ArrayQueue));
if (!queue) {
perror("Failed to create array queue");
return NULL;
}
queue->front = 0; // 或 -1, 根据具体实现调整
queue->rear = -1; // 初始时 rear 在 front 之前表示空
queue->count = 0;
return queue;
}
// 检查队列是否已满
int is_array_queue_full(ArrayQueue *queue) {
return queue->count == MAX_QUEUE_SIZE;
}
// 检查队列是否为空
int is_array_queue_empty(ArrayQueue *queue) {
return queue->count == 0;
}
// Enqueue操作
void array_queue_enqueue(ArrayQueue *queue, int item) {
if (is_array_queue_full(queue)) {
printf("Queue Overflow! Cannot enqueue %d.\n", item);
return;
}
queue->rear = (queue->rear + 1) % MAX_QUEUE_SIZE; // 循环移动rear指针
queue->items[queue->rear] = item;
queue->count++;
// printf("%d enqueued to queue.\n", item);
}
// Dequeue操作
int array_queue_dequeue(ArrayQueue *queue) {
if (is_array_queue_empty(queue)) {
printf("Queue Underflow! Cannot dequeue.\n");
return INT_MIN;
}
int item = queue->items[queue->front];
queue->front = (queue->front + 1) % MAX_QUEUE_SIZE; // 循环移动front指针
queue->count--;
return item;
}
// Peek/Front操作
int array_queue_front(ArrayQueue *queue) {
if (is_array_queue_empty(queue)) {
printf("Queue is empty! Cannot peek front.\n");
return INT_MIN;
}
return queue->items[queue->front];
}
// 释放队列
void free_array_queue(ArrayQueue *queue) {
if (queue) {
free(queue);
}
}
// 循环队列使用示例
void demo_array_queue() {
printf("--- Array Queue (Circular) Demo ---\n");
ArrayQueue *queue = create_array_queue();
if (!queue) return;
array_queue_enqueue(queue, 10);
array_queue_enqueue(queue, 20);
array_queue_enqueue(queue, 30);
printf("Front element is %d\n", array_queue_front(queue)); // 10
printf("%d dequeued from queue\n", array_queue_dequeue(queue)); // 10
array_queue_enqueue(queue, 40);
array_queue_enqueue(queue, 50);
// Queue: 20, 30, 40, 50 (front at 20, rear at 50)
printf("Front element is %d\n", array_queue_front(queue)); // 20
array_queue_enqueue(queue, 60); // Queue is now full
// Queue: 20, 30, 40, 50, 60
printf("Front element is %d\n", array_queue_front(queue)); // 20
array_queue_enqueue(queue, 70); // Overflow
printf("%d dequeued from queue\n", array_queue_dequeue(queue)); // 20
array_queue_enqueue(queue, 70); // Now can enqueue 70
// Queue: 30, 40, 50, 60, 70
printf("Dequeuing all: ");
while (!is_array_queue_empty(queue)) {
printf("%d ", array_queue_dequeue(queue));
}
printf("\n");
array_queue_dequeue(queue); // Underflow
free_array_queue(queue);
}
b. 基于链表的队列实现
使用单向链表,并维护指向队头(front)和队尾(rear)的两个指针。 Enqueue 操作在链表尾部进行,Dequeue 操作在链表头部进行。
// (需要之前单向链表的 SNode 定义)
// 链式队列结构定义
typedef struct LinkedQueue {
SNode *front; // 指向队头节点
SNode *rear; // 指向队尾节点
int count; // 可选:元素数量
} LinkedQueue;
// 初始化链式队列
LinkedQueue* create_linked_queue() {
LinkedQueue *queue = (LinkedQueue*)malloc(sizeof(LinkedQueue));
if (!queue) {
perror("Failed to create linked queue");
return NULL;
}
queue->front = NULL;
queue->rear = NULL;
queue->count = 0;
return queue;
}
// 检查链式队列是否为空
int is_linked_queue_empty(LinkedQueue *queue) {
return queue->front == NULL; // 或者 queue->count == 0
}
// Enqueue操作 (在链表尾部添加)
void linked_queue_enqueue(LinkedQueue *queue, int item) {
SNode *new_node = (SNode*)malloc(sizeof(SNode)); // 使用SNode的创建函数更好
if (!new_node) {
perror("Failed to allocate node for enqueue");
return;
}
new_node->data = item;
new_node->next = NULL;
if (is_linked_queue_empty(queue)) { // 如果队列为空
queue->front = new_node;
queue->rear = new_node;
} else {
queue->rear->next = new_node; // 原队尾指向新节点
queue->rear = new_node; // 更新队尾指针
}
queue->count++;
// printf("%d enqueued to linked queue.\n", item);
}
// Dequeue操作 (从链表头部移除)
int linked_queue_dequeue(LinkedQueue *queue) {
if (is_linked_queue_empty(queue)) {
printf("Linked Queue Underflow! Cannot dequeue.\n");
return INT_MIN;
}
SNode *temp = queue->front;
int dequeued_item = temp->data;
queue->front = queue->front->next;
if (queue->front == NULL) { // 如果出队后队列为空
queue->rear = NULL;
}
free(temp);
queue->count--;
return dequeued_item;
}
// Peek/Front操作
int linked_queue_front_val(LinkedQueue *queue) { // Renamed to avoid conflict
if (is_linked_queue_empty(queue)) {
printf("Linked Queue is empty! Cannot peek front.\n");
return INT_MIN;
}
return queue->front->data;
}
// 释放链式队列
void free_linked_queue(LinkedQueue *queue) {
if (!queue) return;
SNode *current = queue->front;
SNode *next_node;
while (current != NULL) {
next_node = current->next;
free(current);
current = next_node;
}
free(queue);
}
// 链式队列使用示例
void demo_linked_queue() {
printf("--- Linked Queue Demo ---\n");
LinkedQueue *queue = create_linked_queue();
if (!queue) return;
linked_queue_enqueue(queue, 1000);
linked_queue_enqueue(queue, 2000);
linked_queue_enqueue(queue, 3000);
printf("Front element is %d\n", linked_queue_front_val(queue)); // 1000
printf("%d dequeued from linked queue\n", linked_queue_dequeue(queue)); // 1000
printf("Front element is %d\n", linked_queue_front_val(queue)); // 2000
linked_queue_enqueue(queue, 4000);
// Queue: 2000, 3000, 4000
printf("%d dequeued from linked queue\n", linked_queue_dequeue(queue)); // 2000
printf("%d dequeued from linked queue\n", linked_queue_dequeue(queue)); // 3000
printf("%d dequeued from linked queue\n", linked_queue_dequeue(queue)); // 4000
printf("%d dequeued from linked queue\n", linked_queue_dequeue(queue)); // Underflow
free_linked_queue(queue);
}
队列的应用
- 任务调度:操作系统中的进程调度、打印机任务队列。
- 广度优先搜索 (BFS):在图或树的遍历中,BFS算法使用队列来存储待访问的节点。
- 缓冲区:在数据生产者和消费者之间用作缓冲区,例如I/O操作、网络数据包处理。
- 消息队列:在分布式系统中用于组件间的异步通信。
- 模拟排队系统:如银行排队、呼叫中心等待。
3. 比较与选择
特性 | 数组实现 (栈/队列) | 链表实现 (栈/队列) |
大小 | 固定大小 (除非使用动态数组,但仍有开销) | 动态大小,按需分配 |
内存 | 连续内存,可能缓存友好;无指针开销 | 非连续内存,缓存不友好;有指针额外开销 |
Overflow | 可能发生 (数组满) | 仅当系统内存耗尽时发生 (malloc失败) |
Underflow | 可能发生 (空时Pop/Dequeue) | 可能发生 (空时Pop/Dequeue) |
实现复杂度 | 循环队列实现略复杂 | 相对直接,但涉及指针操作和动态内存管理 |
Push/Enqueue | O(1) | O(1) |
Pop/Dequeue | O(1) (循环队列也是O(1)) | O(1) |
选择依据:
- 如果元素数量上限已知且不大,或者对内存连续性有要求,数组实现可能更优(尤其是栈)。
- 如果元素数量不确定,或者需要频繁动态增删,链表实现更灵活,避免了固定大小的限制和数据搬移。
- 对于队列,链表实现通常比简单的(非循环)数组实现更高效,而循环数组队列则提供了与链表队列相当的性能,但有大小限制。
总结
栈和队列是构建更复杂算法和系统的基石。理解它们的LIFO和FIFO特性,以及如何通过数组或链表来实现它们,对于C程序员来说非常重要。
- 栈:后进先出,常用于递归模拟、表达式求值、撤销操作等。
- 队列:先进先出,常用于任务调度、BFS、缓冲区等。
选择合适的实现方式取决于具体的应用需求,如数据量、性能要求和内存管理的复杂度。
// Main function to run demos (uncomment SNode definition if needed)
/*
typedef struct SNode { // Make sure SNode is defined if running linked versions
int data;
struct SNode *next;
} SNode;
int main() {
demo_array_stack();
printf("\n");
demo_linked_stack();
printf("\n");
demo_array_queue();
printf("\n");
demo_linked_queue();
return 0;
}
*/
- 上一篇: C++基础知识点总结
- 下一篇: 稳了,一文学会栈和队列!
猜你喜欢
- 2025-05-26 什么是数据类型?
- 2025-05-26 C语言进阶教程:链表(单向、双向、循环)的实现与操作
- 2025-05-26 栈的经典面试题之用两个栈实现一个队列
- 2025-05-26 C# 之门课程系列-11
- 2025-05-26 24道几乎必问的JVM面试题,我只会7道,你能答出几道?
- 2025-05-26 大厂必备技能:数据结构之栈
- 2025-05-26 C++面试笔记--循环链表,队列,栈,堆
- 2025-05-26 Java程序员必须掌握的数据结构
- 2025-05-26 程序员必备:数据结构+算法知识大纲-全面细致,架构师熬夜总结
- 2025-05-26 数组、链表、队列和栈,四大基础数据结构详解
- 最近发表
-
- 面试中常被问到的Hash表,你了解吗
- JAVA面试考点:一文搞懂一致性Hash的原理和实现
- 一次性搞清楚equals和hashCode(hashcode() 与equals()区别,简单说明)
- HashMap.Key的故事:Key为什么出现Hash碰撞及冲突呢?
- hash冲突的几种解决方案对比(hash冲突的解决方式)
- 游戏王LN 无头骑士(无头骑士cv)
- Linux ln、unlink命令用法(linux link命令详解)
- n和l分不清矫正发音方法,这三步就够了
- golang引用私有gitlab项目代码(golang引入当前包下的文件)
- Instamic:录音领域中的 GoPro,让你想录就录,随心所欲
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)
- mysql数据库面试题 (57)