一、栈的基本概念
栈(Stack)是一种特殊的线性表,其特殊性在于只能在固定的一端进行操作。这种限制使得栈呈现出"后进先出"(LIFO, Last In First Out)的逻辑特性。
栈的核心特点
- 栈顶:可以进行插入删除的一端
- 栈底:栈顶的对端,不可操作
- 入栈(Push):将节点插入栈顶之上
- 出栈(Pop):将节点从栈顶删除
- 取栈顶(Top):取得栈顶元素但不出栈
生活中的栈实例
- 堆叠的盘子(最后放上去的盘子最先被拿走)
- 弹匣(最后压入的子弹最先射出)
- 电梯中的人们(最后进入的人最先出来)
- 函数调用栈(最后调用的函数最先返回)
二、栈的存储形式
栈可以使用两种存储方式实现:
- 顺序栈:基于数组实现,使用顺序存储
- 链式栈:基于链表实现,使用链式存储
三、栈的基本操作与实现
1. 顺序栈实现
数据结构定义:
typedef struct sequential_stack {
datatype_p data_p; // 指向顺序栈内存的指针
int capacity; // 顺序栈的容量
int top; // 顺序栈的栈顶指针
} sq_stack_t, *sq_stack_p;
关键操作:
// 初始化顺序栈
sq_stack_p SEQUENTIAL_STACK_Init(int cap_size) {
sq_stack_p p = malloc(sizeof(sq_stack_t));
if (p != NULL) {
p->data_p = malloc(sizeof(datatype) * cap_size);
p->capacity = cap_size;
p->top = -1; // 初始时栈为空
}
return p;
}
// 判断栈是否为空
bool SEQUENTIAL_STACK_IfEmpty(sq_stack_p p) {
return (p->top == -1);
}
// 判断栈是否已满
bool SEQUENTIAL_STACK_IfFull(sq_stack_p p) {
return (p->top == p->capacity - 1);
}
// 入栈操作
int SEQUENTIAL_STACK_Push(sq_stack_p p, datatype data) {
if (SEQUENTIAL_STACK_IfFull(p)) return -1;
p->top++;
p->data_p[p->top] = data;
return 0;
}
// 出栈操作
int SEQUENTIAL_STACK_Pop(sq_stack_p p) {
if (SEQUENTIAL_STACK_IfEmpty(p)) return -1;
p->top--;
return 0;
}
2. 链式栈实现
数据结构定义:
// 节点结构
typedef struct node {
datatype data;
struct node *next_p;
} node_t, *node_p;
// 栈管理结构
typedef struct link_cir_stack {
node_p top_p; // 栈顶指针
int num; // 元素个数
} lc_stack_t, *lc_stack_p;
关键操作:
// 初始化链式栈
lc_stack_p LINK_CIR_STACK_Init(void) {
lc_stack_p p = malloc(sizeof(lc_stack_t));
if (p != NULL) {
p->top_p = malloc(sizeof(node_t)); // 创建头节点
p->top_p->next_p = p->top_p; // 循环指向自己
p->num = 0;
}
return p;
}
// 入栈操作(头插法)
void LINK_CIR_STACK_Push(lc_stack_p p, node_p new_node) {
node_p head_node = p->top_p;
new_node->next_p = head_node->next_p;
head_node->next_p = new_node;
p->num++;
}
四、队列的基本概念
队列(Queue)是另一种特殊的线性表,其特殊性在于只能在固定的两端进行操作:一端插入,另一端删除。这种限制使得队列呈现出"先进先出"(FIFO, First In First Out)的逻辑特性。
队列的核心特点
- 队头:可以删除节点的一端
- 队尾:可以插入节点的一端
- 入队(EnQueue):将节点插入到队尾之后
- 出队(OutQueue):将队头节点从队列中删除
- 取队头(Front):取得队头元素但不出队
五、队列的存储形式
队列可以使用两种存储方式实现:
- 顺序队列:基于数组实现,使用顺序存储
- 链式队列:基于链表实现,使用链式存储
六、队列的基本操作与实现
1. 顺序队列(循环队列)实现
数据结构定义:
typedef struct sequential_queue {
datatype_p data_p; // 指向顺序队列内存的指针
int capacity; // 顺序队列的容量
int front; // 队头指针
int rear; // 队尾指针
} sq_queue_t, *sq_queue_p;
关键操作:
// 初始化顺序队列
sq_queue_p SEQUENTIAL_QUEUE_Init(int cap_size) {
sq_queue_p p = malloc(sizeof(sq_queue_t));
if (p != NULL) {
p->data_p = malloc(sizeof(datatype) * cap_size);
p->capacity = cap_size;
p->front = 0;
p->rear = 0;
}
return p;
}
// 判断队列是否为空
bool SEQUENTIAL_QUEUE_IfEmpty(sq_queue_p p) {
return (p->front == p->rear);
}
// 判断队列是否已满
bool SEQUENTIAL_QUEUE_IfFull(sq_queue_p p) {
return ((p->rear + 1) % p->capacity == p->front);
}
// 入队操作
int SEQUENTIAL_QUEUE_EnQueue(sq_queue_p p, datatype data) {
if (SEQUENTIAL_QUEUE_IfFull(p)) return -1;
p->data_p[p->rear] = data;
p->rear = (p->rear + 1) % p->capacity; // 循环利用数组空间
return 0;
}
2. 链式队列实现
数据结构定义:
// 节点结构
typedef struct node {
datatype data;
struct node *next_p;
} node_t, *node_p;
// 队列管理结构
typedef struct link_cir_queue {
node_p front_p; // 队头指针
node_p rear_p; // 队尾指针
int num; // 元素个数
} lc_queue_t, *lc_queue_p;
关键操作:
// 初始化链式队列
lc_queue_p LINK_CIR_QUEUE_Init(void) {
lc_queue_p p = malloc(sizeof(lc_queue_t));
if (p != NULL) {
node_p head_node = malloc(sizeof(node_t));
head_node->next_p = head_node; // 循环指向自己
p->front_p = head_node;
p->rear_p = head_node;
p->num = 0;
}
return p;
}
// 入队操作(尾插法)
void LINK_CIR_QUEUE_EnQueue(lc_queue_p p, node_p new_node) {
p->rear_p->next_p = new_node;
new_node->next_p = p->front_p; // 指向头节点,形成循环
p->rear_p = new_node;
p->num++;
}
七、栈与队列的对比
| 特性 | 栈(Stack) | 队列(Queue) |
|---|---|---|
| 操作原则 | LIFO(后进先出) | FIFO(先进先出) |
| 操作端 | 仅一端(栈顶) | 两端(队头删除,队尾插入) |
| 基本操作 | Push, Pop, Top | EnQueue, OutQueue, Front |
| 应用场景 | 函数调用、表达式求值、回溯算法 | 消息队列、任务调度、广度优先搜索 |
八、常见应用场景
栈的应用
- 函数调用栈:记录函数调用关系和局部变量
- 表达式求值:中缀表达式转后缀表达式,以及后缀表达式求值
- 括号匹配:检查代码中的括号是否正确匹配
- 浏览器前进后退:使用两个栈实现网页浏览历史记录
队列的应用
- 消息队列:系统间异步通信
- 任务调度:CPU调度、打印机任务排队
- 广度优先搜索:图中按层次遍历节点
- 缓存实现:FIFO缓存淘汰策略
九、学习重点与常见错误
学习重点
- 理解栈的LIFO特性和队列的FIFO特性
- 掌握顺序和链式两种存储方式的实现
- 熟悉基本操作的时间复杂度分析
- 理解循环队列如何解决"假溢出"问题
常见错误
- 栈空时执行Pop操作:需要先判断栈是否为空
- 栈满时执行Push操作:需要先判断栈是否已满
- 队列空时执行出队操作:需要先判断队列是否为空
- 未正确处理循环队列的边界条件:特别是队满的判断条件
十、总结
栈和队列是两种基础但非常重要的数据结构,它们分别体现了LIFO和FIFO两种不同的数据处理原则。
关键记忆点:
- 栈是后进先出(LIFO)结构,只能在栈顶进行操作
- 队列是先进先出(FIFO)结构,在队尾插入,在队头删除
- 两者都可以用顺序或链式方式实现
- 循环队列通过取模运算实现数组空间的循环利用
- 栈和队列在算法和系统设计中有着广泛的应用
掌握栈和队列的实现原理和操作特性,对于理解更复杂的数据结构和算法具有重要意义。
练习题:
- 使用栈实现一个简单的表达式求值器
- 使用队列实现一个简单的消息队列系统
- 比较顺序实现和链式实现的性能差异
- 尝试实现一个双栈队列(使用两个栈实现队列功能)

被折叠的 条评论
为什么被折叠?



