C语言进阶教程:栈和队列的实现与应用
yund56 2025-05-28 23:43 9 浏览
栈(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;
}
*/
相关推荐
- 没有获得Windows 10 20H2升级通知,怎样直接升级
-
微软公司已经正式发布Windows1020H2操作系统,在正常情况下,微软只会首先推送到少量电脑,然后一边推送一边采集遥测数据。收集遥测数据可以确定哪些电脑可以更新,哪些电脑在更新后可能会失败,微...
- 不想让人随便卸载你安装的程序,用这四招,他将无计可施
-
Windows10不提供设置删除应用程序限制的功能,有几种间接方法可以防止用户删除操作系统中的程序和游戏。一、WindowsInstaller服务使用Windows工具,可以部分限制用户的权限。如...
- 一文看懂苹果全球开发者大会 五大系统全面升级
-
来源:环球网【环球网智能报道记者张阳】北京时间6月23日凌晨1点,苹果全球开发者大会(WWDC2020)如期举行,还是那个熟悉的乔布斯剧院,依旧是高水准的视频展示,但是这届WWDC,却是苹果历史...
- 无需等待微软分批推送,23H2可借助注册表快速获取Win11 24H2更新
-
IT之家10月15日消息,Windows1124H2正在分批推送,但由于存在多种Bug,微软已经开始放缓其推送节奏。WindowsLatest发现,Windows1123H2...
- 办公小技巧:剑走偏锋 PPT中打造动态图表
-
年底到了少不了又要制作各种总结报表,为了让自己的报表与众不同,我们可以借助PowerPoint动画组件+报表的方式,打造出更为出彩的动态图表。下面以PowerPoint2016为例,介绍如何使用三维...
- 文档表格 版本差异何在
-
在办公过程中,对文档或表格的修改是司空见惯的事。那么,一份文档做了内容改动,如何知道差异在哪里?一份表格改动部分数据,如何知道哪些有所变动?不要说审阅和修订功能,因为不是所有人都会用这些功能来标注的,...
- Excel VBA自制日历组件16色可选 完美替代VBA日期控件
-
本日期组件可跟随单元格跟随窗体中ActiveX文本框组合框控件16种配色可选私信回复880日历可体验效果使用说明1打开自己需要应用日历面板的Excel表,注意必须是启用VBA的格式2在...
- 如何从交互角度读懂产品需求文档
-
作为设计师,理解产品经理提供的需求文档是交互设计工作的重要前提与起点,然而对于很多设计师来说,需求文档内容通常非常复杂,设计师们需要花费大量时间去消化、理解和归纳。本文作者结合公司示例,分析设计师如何...
- 植入让文档变得更强大
-
有效地利用文档置入技术,会让我们的常用文档功能变得更加强大,实现更加高效或有趣的应用。1.写字板文档嵌入其他文档有时,我们要组织一个大型的文档,但是这些文档的内容可能来自于不同种类的文档编辑器,比如...
- Office 2016滚动文本框 顺手就来
-
【电脑报在线】如果一页PPT内容较多无法在完全显示,就需要用到滚动文本框,在PPT2016中借助控件即可快速制作滚动文本框。在“告诉我你想要做什么”输入“文本框控件”,在搜索结果点击“文本框(Acti...
- Axure的多状态复选树
-
本文将详细介绍如何在Axure中实现一种增强型的多状态复选树组件,它不仅支持全选、半选和未选等状态,还具备动态加载、关键字筛选等高级功能。多状态复选树(Multi-StateCheckboxTre...
- 办公小技巧:PPT中控件图表巧联动
-
在利用PPT进行图表演示时,操作者有可能要与图表进行交互联动,比如通过输入数据来预测产品的生产情况等,这时就需要用到“开发工具”中的控件了。几个控件配合几句VBA代码,就可以轻松实现上述交互联动效果(...
- 用好插件——找回火狐的旧功能
-
现在的软件,特别是浏览器类软件,更新换代速度都很快,而且无论是外观界面还是系统组件都会有较大的变化,这样会让很多朋友无所适从。以大家常用的火狐浏览器为例,它就已经升级到了最新的35版,而且在新版中对很...
- 重新认识控件(二)
-
图片和文字,都是一种数据形式。我平时对文本框的录入,报错和提交的设计比较多。最近涉及到图片控件的设计,细细琢磨一下,这玩意还有一些平时没太注意的细节点,感觉对于其他控件的设计有指导意义,特此总结一下传...
- JSA宏教程——在文档中添加复合框控件
-
上一期,我们初步认识了控件Control,本节我们将继续控件的相关内容。这几期我们将逐一介绍相关控制。本节先介绍复合框(也叫组合框)Combobox。复合框的作用复合框就是一个下拉选项框,一次显示一个...
- 一周热门
- 最近发表
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- java技术的电子书去哪看 (33)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 亲测源码 (36)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)
- 表格乘法的公式怎么设置 (34)
- sumif函数的例子 (34)