【数据结构详解】栈与队列的核心原理、实现及应用场景

一、栈(Stack):先进后出的线性表

1.1 栈的核心定义与特性

  • 定义 :栈是限定仅在表尾(栈顶) 进行插入和删除操作的线性表,另一端(栈底)不允许任何操作。
  • 核心特性:先进后出(FILO)/ 后进先出(LIFO)。
  • 关键术语
    • 栈顶:允许插入(入栈)、删除(出栈)的一端;
    • 栈底:固定的、不允许操作的一端;
    • 入栈(Push):向栈顶添加元素;
    • 出栈(Pop):从栈顶移除元素。

1.2 栈的分类与存储方式

(1)按存储方式分类
存储方式 实现方式 优点 缺点
顺序存储(数组栈) 基于数组实现 访问速度快、实现简单 容量固定,易溢出
链式存储(链栈) 基于链表实现 容量动态扩展,无溢出风险 额外的指针内存开销
(2)按栈顶指针行为分类(顺序栈特有)

栈的指针行为直接影响元素存储逻辑,是面试 / 开发中的高频考点:

  • 空增栈 :top 指针指向新元素待插入的位置(空栈时 top=0),入栈时先存元素再top++
  • 空减栈 :top 指针指向新元素待插入的位置,入栈时先top--再存元素;
  • 满增栈 :top 指针指向最后入栈的元素(空栈时 top=-1),入栈时先top++再存元素;
  • 满减栈 :top 指针指向最后入栈的元素,入栈时先存元素再top--

核心区别:

  • 空栈:top 指向 "待插入位置";
  • 满栈:top 指向 "最后一个元素位置";
  • 增栈:入栈后 top 地址变大;
  • 减栈:入栈后 top 地址变小。

1.3 系统栈 vs 数据结构栈

很多开发者会混淆 "系统栈" 和 "数据结构栈",二者原理一致但应用场景不同:

维度 系统栈 数据结构栈
内存位置 进程地址空间 0~3G(默认 8M) 堆空间(malloc 动态分配)
存储内容 函数调用关系、局部变量、参数、返回地址 自定义业务数据
管理方式 操作系统自动管理 开发者手动管理(创建 / 销毁)
适用场景 函数调用、递归执行 算法(回溯、表达式计算)、业务逻辑

1.4 链栈的完整实现(LinkStack.h)

基于链表实现的栈(链式存储),支持动态扩容,无溢出风险:

c

运行

复制代码
#ifndef _LINKSTACK_H_
#define _LINKSTACK_H_

// 自定义业务数据类型(以坐标/字符为例)
typedef struct person
{
    char c;
    int row;
    int col;
} DATATYPE;

// 栈节点结构
typedef struct stacknode
{
    DATATYPE data;          // 节点数据
    struct stacknode *next; // 指向下一节点(栈底方向)
} LinkStackNode;

// 栈管理结构
typedef struct
{
    LinkStackNode *top; // 栈顶指针
    int clen;           // 栈中有效元素数
} LinkStack;

// 核心操作函数声明
LinkStack* CreateLinkStack();          // 创建栈
int PushLinkStack(LinkStack*ls,DATATYPE*newdata); // 入栈
int PopLinkStack(LinkStack*ls);        // 出栈
DATATYPE* GetTopLinkStack(LinkStack*ls);// 获取栈顶元素(不弹出)
int GetSizeLinkStack(LinkStack*ls);    // 获取栈大小
int IsEmptyLinkStack(LinkStack*ls);    // 判断栈是否为空
int DestroyLinkStack(LinkStack*ls);    // 销毁栈

#endif

1.5 栈的典型应用场景

  • 递归调用(系统栈自动实现);
  • 回溯算法(如迷宫求解、全排列);
  • 表达式计算(如四则运算、括号匹配);
  • 编辑器撤销 / 重做功能;
  • 浏览器前进 / 后退功能;
  • 优先级相关问题(如运算符优先级处理)。

二、队列(Queue):先进先出的线性表

2.1 队列的核心定义与特性

  • 定义:队列是只允许在一端(队尾)插入、另一端(队头)删除的线性表。
  • 核心特性:先进先出(FIFO)。
  • 关键术语
    • 队头(Head):允许删除的一端;
    • 队尾(Tail):允许插入的一端;
    • 入队(EnQueue):向队尾添加元素;
    • 出队(DeQueue):从队头移除元素。
  • 核心应用:解决 "速度不匹配" 问题(如缓冲区、消息队列、任务调度)。

2.2 队列的分类

类型 实现方式 核心特点
顺序队列 基于数组实现 简单但易出现 "假溢出"
循环队列 基于数组 + 取余运算 解决假溢出,空间利用率高
链式队列 基于链表实现 容量动态扩展,无溢出风险

2.3 循环队列:解决顺序队列的假溢出问题

(1)假溢出问题

顺序队列的队头(head)和队尾(tail)均向后递增,当 tail 到达数组末尾时,即使数组前端有空余空间,也无法继续入队,这种现象称为 "假溢出"。

(2)循环队列的核心原理

通过对 head/tail 做 "队列总大小取余",让指针在数组中循环移动,核心规则:

  • 队空条件:head == tail
  • 队满条件:(tail + 1) % tlen == head(牺牲一个位置,避免队空 / 队满歧义);
  • 入队:tail = (tail + 1) % tlen
  • 出队:head = (head + 1) % tlen
(3)循环队列定义(SeqQueue)

c

运行

复制代码
typedef int DATATYPE; // 自定义数据类型
typedef struct queue {
    DATATYPE *ptr;   // 队列数组指针
    int tlen;        // 队列总容量
    int head;        // 队头指针
    int tail;        // 队尾指针
}SeqQueue;

// 核心操作函数声明
int DestroySeqQueue(SeqQueue *queue);       // 销毁队列
DATATYPE QuitSeqQueue(SeqQueue *queue);     // 出队
int EnterSeqQueue(SeqQueue *queue, DATATYPE data); // 入队
int IsEmptySeqQueue(SeqQueue *queue);       // 判断队空
int IsFullSeqQueue(SeqQueue *queue);        // 判断队满
SeqQueue *CreateSeqQueue(int len);          // 创建队列

2.4 链式队列:动态扩容的队列实现

基于链表的队列(链式存储),无需考虑容量限制,适合数据量不固定的场景:

c

运行

复制代码
// 自定义业务数据类型(以学生信息为例)
typedef struct person
{
    char name[32];
    char sex;
    int age;
    int score;
} DATATYPE;

// 队列节点结构
typedef struct quenode
{
    DATATYPE data;          // 节点数据
    struct quenode *next;   // 指向下一节点
} LinkQueNode;

// 队列管理结构
typedef struct
{
    LinkQueNode *head; // 队头指针
    LinkQueNode *tail; // 队尾指针
    int clen;          // 有效元素数
} LinkQue;

// 核心操作函数声明
LinkQue * CreateLinkQue();                  // 创建队列
int EnterLinkQue(LinkQue *lq,DATATYPE* newnode); // 入队
int QuitLinkQue(LinkQue*lq);                // 出队
DATATYPE* GetHeadLinkQue(LinkQue *lq);      // 获取队头元素
int GetSizeLinkQue(LinkQue *lq);            // 获取队列大小
int IsEmptyLinkQue(LinkQue *lq);            // 判断队空
int DestroyLinkQue(LinkQue * lq);           // 销毁队列

2.5 队列的典型应用场景

  • 消息队列(如 MQ、异步任务处理);
  • 缓冲区(如网络通信的数据包缓冲);
  • 任务调度(如线程池、进程调度);
  • 广度优先搜索(BFS)算法;
  • 打印机任务队列、售票排队系统。

三、栈与队列的核心区别

维度 队列
操作端 仅栈顶允许插入 / 删除 队尾插入、队头删除
核心特性 先进后出(FILO) 先进先出(FIFO)
存储优化 链栈无溢出,顺序栈有容量限制 循环队列解决顺序队列假溢出
典型应用 递归、回溯、表达式计算 缓冲、调度、BFS 算法
指针行为 仅维护栈顶指针 循环队列维护 head/tail,链式队列维护 head/tail

四、开发注意事项

4.1 栈的开发要点

  1. 链栈需注意内存泄漏:出栈 / 销毁时必须释放节点内存;
  2. 顺序栈需处理栈满 / 栈空异常,避免数组越界;
  3. 栈顶指针的行为(空增 / 满增)需提前定义,避免逻辑混乱。

4.2 队列的开发要点

  1. 循环队列的队满 / 队空条件需严格遵循(tail+1)%tlen == headhead==tail
  2. 链式队列入队时需更新队尾指针,出队时需处理 "队空" 和 "只剩一个节点" 的边界;
  3. 队列销毁时需遍历释放所有节点,避免内存泄漏。
相关推荐
CQ_YM9 小时前
数据结构之单向链表
c语言·数据结构·链表
im_AMBER11 小时前
算法笔记 18 二分查找
数据结构·笔记·学习·算法
C雨后彩虹12 小时前
机器人活动区域
java·数据结构·算法·华为·面试
苏小瀚12 小时前
[算法]---路径问题
数据结构·算法·leetcode
前端之虎陈随易13 小时前
MoonBit内置数据结构详解
数据结构·数据库·redis
CodeByV15 小时前
【算法题】双指针(二)
数据结构·算法
Jasmine_llq17 小时前
《P3811 【模板】模意义下的乘法逆元》
数据结构·算法·线性求逆元算法·递推求模逆元
虹科网络安全17 小时前
艾体宝干货 | Redis Java 开发系列#2 数据结构
java·数据结构·redis