Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【初探数据结构】线性表——栈与队列(代码实现与详解)

【初探数据结构】线性表——栈与队列(代码实现与详解)

作者头像
我想吃余
发布于 2025-03-31 08:30:30
发布于 2025-03-31 08:30:30
17500
代码可运行
举报
运行总次数:0
代码可运行

引言

栈(Stack)和队列(Queue)是计算机科学中最基础且重要的数据结构。它们分别遵循**后进先出(LIFO)先进先出(FIFO)**的原则,广泛应用于算法设计、系统底层实现(如函数调用栈)、任务调度等场景。本文将从概念、实现到常见面试题,带你全面掌握这两种数据结构。


一、栈(Stack)

1. 概念与特性
  • 定义:栈是一种只允许在**固定一端(栈顶)**进行插入(压栈)和删除(出栈)操作的线性表。
  • 核心原则:后进先出(LIFO)。类比生活中的叠盘子,最后放上去的盘子会被最先拿走。
  • 操作
    • 压栈(Push):在栈顶插入元素。
    • 出栈(Pop):删除栈顶元素。
    • 获取栈顶元素(Top):查看但不删除栈顶元素。
2. 实现方式
一、栈结构设计

动态栈通过动态数组实现,包含以下核心结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#define INICAPA 4  // 初始容量

typedef struct Stack {
    STDataType* a;      // 动态数组
    int top;            // 栈顶指针(指向下一个待插入位置)
    int capacity;       // 当前容量
} ST;
  • a:动态数组,存储栈元素。
  • top:栈顶指针,表示栈中元素个数,也指向下一个插入位置。
  • capacity:数组当前容量,支持动态扩容。

二、核心函数实现解析
1. 初始化栈
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void STInit(ST* stack) {
    assert(stack);
    stack->a = (STDataType*)malloc(sizeof(STDataType) * INICAPA);
    if (stack->a == NULL) {
        perror("malloc fail");
        exit(-1);
    }
    stack->capacity = INICAPA;
    stack->top = 0;  // 初始时栈空
}
  • 功能:分配初始容量(4)的数组,初始化栈顶指针。
  • 注意
    • 使用 assert 确保传入指针非空。
    • 内存分配失败时直接终止程序。

2. 压栈操作(动态扩容)
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void STPush(ST* stack, STDataType x) {
    assert(stack);
    // 栈满时扩容至2倍
    if (stack->top == stack->capacity) {
        STDataType* tmp = (STDataType*)realloc(
            stack->a, 
            sizeof(STDataType) * stack->capacity * 2
        );
        if (tmp == NULL) {
            perror("realloc fail");
            exit(-1);
        }
        stack->a = tmp;
        stack->capacity *= 2;
    }
    // 插入数据并更新栈顶
    stack->a[stack->top] = x;
    stack->top++;
}
  • 步骤
    1. 检查栈是否已满(top == capacity)。
    2. 若满,通过 realloc 扩容至原容量的2倍。
    3. 插入新元素到 top 位置,并递增栈顶指针。
  • 关键点
    • 扩容策略为翻倍,均摊时间复杂度为 (O(1))。
    • 扩容失败时直接终止程序。

3. 出栈操作
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void STPop(ST* stack) {
    assert(stack);
    assert(!STEmpty(stack));  // 栈不能为空
    stack->top--;
}
  • 逻辑:仅需将 top 减1,无需实际删除数据。
  • 注意:必须确保栈非空,否则触发断言。

4. 获取栈顶元素
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
STDataType STTop(ST* stack) {
    assert(stack && !STEmpty(stack));
    return stack->a[stack->top - 1];
}
  • 关键:通过 top-1 访问当前栈顶元素。

5. 销毁栈
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void STDestroy(ST* stack) {
    assert(stack);
    free(stack->a);     // 释放动态数组
    stack->a = NULL;    // 避免野指针
    stack->top = 0;
    stack->capacity = 0;
}
  • 作用:释放内存并重置状态。
  • 易错点:忘记将 a 置为 NULL 可能导致后续误用。

6. 辅助函数
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 判空
bool STEmpty(ST* stack) {
    assert(stack);
    return stack->top == 0;
}

// 获取栈大小
int STSize(ST* stack) {
    assert(stack);
    return stack->top;
}

三、关键问题与优化
1. 为什么选择动态数组而非链表?
  • 优势
    • 内存连续,缓存友好。
    • 压栈/出栈操作的时间复杂度为 (O(1))(均摊后)。
  • 劣势:扩容时需要复制数据,但通过翻倍扩容策略可优化均摊成本。
2. top 指针的设计
  • 指向下一个空闲位置:例如,top=3 表示栈中有3个元素(索引0~2),新元素插入索引3。
  • 简化逻辑top 直接表示元素个数,无需额外维护大小字段。
3. 动态扩容的细节
  • 翻倍策略:容量从4→8→16→32…,均摊每次插入操作的时间复杂度为 (O(1))。
  • 内存释放:销毁时必须调用 STDestroy,否则导致内存泄漏。

二、队列(Queue)

1. 概念与特性
  • 定义:队列是一种允许在队尾插入队头删除的线性表。
  • 核心原则:先进先出(FIFO)。类比排队购票,先到的人先买到票。
  • 操作
    • 入队(Enqueue):在队尾插入元素。
    • 出队(Dequeue):删除队头元素。
    • 获取队头元素(Front):查看但不删除队头元素。
2. 实现方式
一、队列结构设计

队列通过链表实现,包含以下核心结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 队列节点
typedef struct QListNode {
    struct QListNode* next;  // 指向下一节点
    QDataType data;          // 节点数据
} QNode;

// 队列管理结构
typedef struct Queue {
    QNode* head;    // 队头指针
    QNode* tail;    // 队尾指针
    int size;       // 队列元素个数
} Queue;
  • head:指向链表的第一个节点(队头),用于出队操作。
  • tail:指向链表的最后一个节点(队尾),用于入队操作。
  • size:记录队列长度,避免遍历链表统计元素。

二、核心函数实现解析
1. 初始化队列
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void QueueInit(Queue* q) {
    assert(q);
    q->head = q->tail = NULL;
    q->size = 0;
}
  • 功能:将队列的头尾指针置空,size 设为0。
  • 注意:需使用 assert 确保传入的队列指针非空。

2. 入队操作
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void QueuePush(Queue* q, QDataType data) {
    assert(q);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL) {
        perror("malloc fail");
        exit(-1);
    }
    newnode->next = NULL;
    newnode->data = data;

    if (q->tail == NULL) {  // 队列为空
        q->tail = q->head = newnode;
    } else {                // 队列非空
        q->tail->next = newnode;
        q->tail = newnode;
    }
    q->size++;
}
  • 步骤
    1. 创建新节点并初始化数据。
    2. 若队列为空,头尾指针均指向新节点。
    3. 若队列非空,将新节点链接到链表尾部,并更新尾指针。
  • 关键点
    • 处理队列为空的边界条件。
    • 内存分配失败时需报错退出。

3. 出队操作
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void QueuePop(Queue* q) {
    assert(q);
    assert(!QueueEmpty(q));

    if (q->head->next == NULL) {  // 队列仅剩一个节点
        free(q->head);
        q->head = q->tail = NULL;
    } else {                     // 队列有多个节点
        QNode* next = q->head->next;
        free(q->head);
        q->head = next;
    }
    q->size--;
}
  • 步骤
    1. 检查队列是否为空(通过 QueueEmpty)。
    2. 若队列只剩一个节点,释放后需将头尾指针均置空。
    3. 若有多个节点,删除头节点并更新头指针。
  • 关键点
    • 必须处理队列只剩一个节点的特殊情况,避免悬空指针。
    • 出队后需同步更新 size

4. 获取队头和队尾元素
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
QDataType QueueFront(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->head->data;
}

QDataType QueueBack(Queue* q) {
    assert(q && !QueueEmpty(q));
    return q->tail->data;
}
  • 注意:需确保队列非空,否则断言触发。

5. 队列销毁
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void QueueDestroy(Queue* q) {
    assert(q);
    QNode* cur = q->head;
    while (cur) {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    q->tail = q->head = NULL;
    q->size = 0;
}
  • 步骤:遍历链表释放所有节点内存,重置头尾指针和 size
  • 易错点:未释放全部节点导致内存泄漏。

三、关键问题与优化
1. 为什么用链表实现队列?
  • 优势:出队时无需移动数据,时间复杂度为 (O(1))。
  • 若用数组实现,出队需整体前移元素,效率低。
2. size 字段的作用
  • 直接通过 q->size 获取队列长度,避免遍历链表((O(1)) 时间复杂度)。
  • 在判空时直接检查 size == 0,提升效率。
3. 边界条件处理
  • 入队:队列为空时需同时更新头尾指针。
  • 出队:队列只剩一个节点时需重置头尾指针。

结语

栈和队列是算法设计的基石,理解其原理和实现是程序员的基本功。建议通过实际编码练习(如实现动态栈、循环队列)加深理解,并多刷相关面试题提升应用能力。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
栈和队列详解
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素————百度百科
用户11029129
2024/06/04
1300
栈和队列详解
【初阶数据结构】详解栈和队列(来自知识星空的一抹流光)
在学习栈和队列中,你是否会被人提问过什么是栈和队列?是否知道栈和队列的特征以及栈和队列的代码实现?
埋头编程
2024/10/16
9600
【初阶数据结构】详解栈和队列(来自知识星空的一抹流光)
【数据结构与算法 经典例题】使用队列实现栈(图文详解)
倔强的石头
2024/12/06
880
【数据结构与算法 经典例题】使用队列实现栈(图文详解)
【数据结构】3道经典面试题带你玩转栈与队列
https://leetcode.cn/problems/valid-parentheses/
修修修也
2024/04/01
1430
【数据结构】3道经典面试题带你玩转栈与队列
【数据结构初阶】队列接口实现及用队列实现栈超详解
概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FlFO(First In First Out)的特点。
fhvyxyci
2024/09/24
1590
【数据结构初阶】队列接口实现及用队列实现栈超详解
【初阶数据结构】——限定性线性表:栈 和 队列详解(C描述)
写完这几个函数,大家可能会想,有的函数这么简单,一句代码就搞定了,为什么还要封装成一个函数,有必要嘛?
YIN_尹
2024/01/23
2260
【初阶数据结构】——限定性线性表:栈 和 队列详解(C描述)
【初阶数据结构】先来后到的秩序:栈和队列
数组可以用来实现栈,但这并不意味着栈的本质就是数组。数组是一种连续存储元素的数据结构,使用数组实现栈时,利用数组的索引来模拟栈的操作
DARLING Zero two
2025/02/18
1060
【初阶数据结构】先来后到的秩序:栈和队列
C语言中的循环队列与栈、队列之间的转换实现
在数据结构的学习中,栈(Stack)和队列(Queue)是两个非常重要的概念。它们分别遵循着后进先出(LIFO)和先进先出(FIFO)的原则。在某些情况下,我们可能需要通过栈来模拟队列,或者通过队列来模拟栈的行为。本文将详细介绍这两种数据结构,并提供相应的C语言实现代码和图解。
小志biubiu
2025/02/27
530
C语言中的循环队列与栈、队列之间的转换实现
栈和队列的实现(详解+图解!文末附完整代码)
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
ahao
2024/03/19
7080
栈和队列的实现(详解+图解!文末附完整代码)
栈和队列详解
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
waves浪游
2024/08/02
900
栈和队列详解
[数据结构]—栈和队列
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
小李很执着
2024/06/15
1280
[数据结构]—栈和队列
【数据结构】栈和队列
栈一般可以用数组或者链表来实现,相对而言数组的结构更优一些。因为数组在尾插上更加方便,而栈也只是在栈顶一端进行操作。当然也可以使用链表进行实现,使用时不需要扩容。
P_M_P
2024/01/18
1550
【数据结构】栈和队列
【数据结构初阶】数组栈和链式队列的实现
栈是一种特殊的线性表,它只允许 在一端插入和删除数据的操作。进行插入和删除数据的一端称为栈顶,另一端什么也不干的称为栈低。
举杯邀明月
2023/04/12
2850
【数据结构初阶】数组栈和链式队列的实现
【数据结构】开卷数据结构~栈和队列详解
目录 前言 栈 栈的实现 接口展示 栈结构创建 栈的初始化 栈的销毁 入栈 出栈 空栈判断 栈顶数据获取 栈存入数据个数 栈测试 队列 队列的实现 接口展示 队列类型创建 队列初始化 队列销毁 入队 出队 队列头结点数据 队列尾结点数据 队列存入数据个数 判断空队列 队列测试 ---- 前言 ---- 本章主要讲解: 数据结构中的栈和队列的知识以及如何实现 栈 ---- 概念及结构 栈,一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作 进行数据插入和删除操作的一端 称为栈
用户9645905
2022/11/30
2560
【数据结构】开卷数据结构~栈和队列详解
数据结构初步(九)- 栈和队列oj练习
,判断字符串是否有效。 有效字符串需满足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。 示例 1: 输入:
怠惰的未禾
2023/04/27
3250
数据结构初步(九)- 栈和队列oj练习
【初阶数据结构】栈和队列(附题目)
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
ZLRRLZ
2024/12/13
1460
【初阶数据结构】栈和队列(附题目)
数据结构——lesson5栈和队列详解
栈: 一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为***栈顶***,另一端称为栈底。栈中的数据元素遵守***后进先出***LIFO(Last In First Out)的原则。
大耳朵土土垚
2024/03/13
1220
数据结构——lesson5栈和队列详解
【数据结构】栈和队列详解&&栈和队列实现
栈中的数据元素遵守后进先出LIFO,(Last In First Out)的原则
用户10925563
2024/06/04
1360
【数据结构】栈和队列详解&&栈和队列实现
数据结构初步(八)- 线性表之栈和队列的解析与实现
顺序表实现栈,需要特别注意一下栈顶下标所在初始的位置,初始位置不同,出栈同一个元素操作也有差别。 栈顶下标初始为-1
怠惰的未禾
2023/04/27
3360
数据结构初步(八)- 线性表之栈和队列的解析与实现
数据结构---栈和队列
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈,出数据也在栈顶。
用户11305458
2024/10/09
1520
推荐阅读
相关推荐
栈和队列详解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验