网站首页 > 技术文章 正文
栈(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;
}
*/
猜你喜欢
- 2025-06-13 C++之类和对象(c++中类和对象的区别)
- 2025-06-13 C语言进阶教程:数据结构 - 哈希表的基本原理与实现
- 2025-06-13 C语言实现见缝插圆游戏!零基础代码思路+源码分享
- 2025-06-13 Windows 10下使用编译并使用openCV
- 2025-06-13 C语言这些常见标准文件该如何使用?很基础也很重要
- 2025-06-13 C语言 vs C++:谁才是编程界的“全能王者”?
- 2025-06-13 C语言无锁编程指南(c语言锁机代码)
- 2025-06-13 什么时候用C而不用C++?(c语言中什么时候用char)
- 2025-06-13 C++中使用new申请内存来实现动态数组
- 2025-06-13 C|地域设置改变程序的语言环境,字符分类及字符判断函数
- 06-13C++之类和对象(c++中类和对象的区别)
- 06-13C语言进阶教程:数据结构 - 哈希表的基本原理与实现
- 06-13C语言实现见缝插圆游戏!零基础代码思路+源码分享
- 06-13Windows 10下使用编译并使用openCV
- 06-13C语言进阶教程:栈和队列的实现与应用
- 06-13C语言这些常见标准文件该如何使用?很基础也很重要
- 06-13C语言 vs C++:谁才是编程界的“全能王者”?
- 06-13C语言无锁编程指南(c语言锁机代码)
- 最近发表
- 标签列表
-
- cmd/c (64)
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- sqlset (64)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)