当前位置: 首页 > news >正文

数据结构从零开始③:栈和队列——操作受限的线性表,一篇搞懂

一、引言

我们已经学习了顺序表和链表,今天要学的栈和队列在逻辑结构上也是线性的

不同于线性表,栈和队列都只能在一端或两端进行操作。限制了操作范围后,代码自然也变得简单、安全、高效,因此当我们只需要对线性结构数据两端进行操作时,可以使用栈和队列

二、栈(stack)

栈是一种只允许在一端(栈顶)进行插入和删除的线性表,遵循后进先出(LIFO)原则。

举个例子,比如我们生活中放盘子,放在最上面的盘子肯定会被最先使用,栈也是同理。

我们目前已经学了两种底层方法(数组、链表)去实现一些的数据结构,大家可以先思考一下,对于这样一种数据结构我们应该使用哪种方法实现。

答案是都可以。只要能够实现这种数据结构无论使用哪种方法实现都是可以的。
对于数组来说,对头部的操作时间复杂度为O(n),对尾部的操作时间复杂度为O(1),我们只需要将数组尾部当作栈顶来操作即可。
对于链表来说,对头部操作时间复杂度反而为O(1),因此我们将链表头部当作栈顶即可。

这里我们用数组来实现,相比于链表来说,数据有个好处就是空间开销会小一些。例如我们增加一个整型,数组只需要增加4个字节空间,链表却要申请一整个结点。

当我们仔细完成了前面顺序表和链表的实现,栈和队列的实现就会变得非常简单了。下面我直接给大家把我实现好的代码附上,以供大家借鉴参考。

// 栈的结构 typedef int StackDataType; typedef struct Stack { StackDataType* arr; int top; int capacity; }Stack; // 入栈 void StackPush(Stack* Stack, StackDataType x) { assert(Stack); // 判断空间是否足够 if (Stack->capacity == Stack->top) { int newCapacity = Stack->capacity == 0 ? 4 : 2 * Stack->capacity; StackDataType* newArr = (StackDataType*)realloc(Stack->arr, newCapacity * sizeof(StackDataType)); if (newArr == NULL) { perror("realloc failed"); exit(1); } Stack->arr = newArr; Stack->capacity = newCapacity; } Stack->arr[Stack->top] = x; Stack->top++; } // 出栈 void StackPop(Stack* Stack) { assert(Stack && Stack->arr && Stack->top); Stack->top--; } // 取栈顶元素 StackDataType StackTop(Stack* Stack) { assert(Stack && Stack->arr && Stack->top); return Stack->arr[Stack->top - 1]; } // 销毁 void StackDestroy(Stack* Stack) { assert(Stack); if (Stack->arr) { free(Stack->arr); Stack->arr = NULL; } Stack->top = Stack->capacity = 0; }

从栈的实现就能发现,栈的实现大多数都是O(1)的时间复杂度,并且只能对一端进行操作,这样就保证了即安全又高效。

下面我们就用栈来实践一道 leetcode 上的题:有效的括号https://leetcode.cn/problems/valid-parentheses/

解题思路:

1. 遍历字符串

2. 遇到左括号就入栈,包括 "(","[","{"

3. 遇到右括号就取栈顶元素来判断是否为配对的括号

4. 若不匹配或此时栈为空就返回 false

5. 遍历完之后,若栈为空则返回 true,否则返回 false

具体代码如下:

bool isValid(char* s) { char* i = s; // 创建并初始化一个栈 Stack stack = {NULL, 0, 0}; // 遍历字符串 while(*i != '\0') { // 这里我用ASCII码来匹配括号 // 若为左括号就入栈 if(*i == 40 || *i == 91 || *i == 123) { StackPush(&stack, *i); } // 若为右括号就判断 else if(*i == 41 || *i == 93 || *i == 125) { // 如果此时栈为空,则返回 false if(stack.top == 0) { StackDestroy(&stack); return false; } // 若不为空,就判断栈顶括号是否和右括号匹配 else { // 若匹配成功,就出栈一个左括号 if(StackTop(&stack) == 40 && *i == 41 || StackTop(&stack) == 91 && *i == 93 || StackTop(&stack) == 123 && *i == 125) { StackPop(&stack); } // 匹配失败直接返回 false else { StackDestroy(&stack); return false; } } } i++; } // 遍历完字符串判断栈是否为空,为空返回true,不为空返回false if(stack.top == 0) StackDestroy(&stack); return true; else StackDestroy(&stack); return false; }

三、队列

队列是一种只允许在一端(队尾)插入,另一端(队头)删除的线性表,遵循先进先出(FIFO)原则。

这个就与我们生活中的队列相同,排队只能在队尾排(当然,有插队的人另说),先排的人先处理。

队列通常用链表的方式去实现,由于队列要对两端进行操作,普通链表无法实现以时间复杂度O(1)来对尾端进行操作,因此我们需要在普通链表的基础上进行改进:
定义两个结构体,一个表示结点(包含数据data和下一个结点next),一个表示整个队列(包含首结点head和尾结点tail)。这样改进之后有了一个专门指向队尾的tail,就能以O(1)的时间复杂度从队尾入队。

具体实现代码如下(依然是仅供参考,希望兄弟们能写出属于自己的队列):

typedef int QueueDataType; // 队列结点结构 typedef struct QueueNode { QueueDataType data; QueueNode* next; }QueueNode; // 队列结构 typedef struct Queue { QueueNode* head; QueueNode* tail; }Queue; // 入队 void QueuePush(Queue* queue, QueueDataType x) { assert(queue); QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode)); if (newNode == NULL) { perror("malloc failed"); exit(1); } newNode->data = x; newNode->next = NULL; if (queue->head == NULL) { queue->head == queue->tail == newNode; } else { queue->tail->next = newNode; queue->tail = newNode; } } // 出队 void QueuePop(Queue* queue) { assert(queue && queue->head); if (queue->head == queue->tail) { free(queue->head); queue->head = queue->tail = NULL; } else { QueueNode* del = queue->head; queue->head = queue->head->next; free(del); del = NULL; } } // 销毁队列 void QueueDestroy(Queue* queue) { assert(queue); QueueNode* del = queue->head; while (del) { queue->head = queue->head->next; free(del); del = queue->head; } queue->head = queue->tail = NULL; } // 取队首元素 QueueDataType QueueFront(Queue* queue) { assert(queue && queue->head); return queue->head->data; } // 取队尾元素 QueueDataType QueueBack(Queue* queue) { assert(queue && queue->head); return queue->tail->data; }

还有一个计算队列元素个数的方法我没有实现,如果以我这种方式写,那就变成O(n)了,如果需要可以在Queue中再添加一个size成员,这样计算更加方便。

http://www.gsyq.cn/news/1439534.html

相关文章:

  • mongodb数据库服务器内存过高分析处理
  • 企业资产管理软件选型全攻略:选对不选贵,落地是核心
  • 构建实时事件驱动AI预测系统:从流处理到模型服务的架构实践
  • 3分钟掌握Codeforces实时评分预测:Carrot浏览器扩展深度解析
  • Node.js技术周刊 2026年第20周
  • 2026 江苏扬州市(全区域服务)本地人必选彩钢瓦金属屋面防水防腐公司避坑指南 TOP5 推荐 - 本地便民网
  • MATLAB雷达CFAR检测实操包:CA-CFAR算法仿真+参数调优视频讲解
  • 二维材料薄片自动化处理:机器学习与光学显微镜结合方案
  • 孤独数据:人的一生,绝大部分时间都是独自一人
  • 深州GEO优化公司|企业知识库升级维护,深州AI搜索优化服务商选择指南 - 招财兔数字员工
  • 涿州GEO优化公司|企业知识库升级维护,涿州AI搜索优化服务商选择指南 - 招财兔数字员工
  • 乐清虹桥家长亲测:双语幼儿园的真实品质标尺 - 奔跑123
  • 打卡信奥刷题(3348)用C++实现信奥题 P9505 『MGOI』Simple Round I | D. 魔法环
  • 2026年最新德阳市金银首饰回收+金条金币+铂金K金 高价回收;实体老店回收黄金 多年口碑 交易放心;TOP5实力权威排行榜推荐+联系方式 - 亦辰小黄鸭
  • 2026年5月广州除甲醛公司推荐:靠谱品牌TOP榜单深度测评解析 - 品牌推荐
  • 如何快速突破百度网盘限速:3步实现免费高速下载的完整方案
  • 别再用裸机死循环了!用STM32CubeMX+FreeRTOS实现多任务切换,保姆级配置流程(Keil仿真)
  • 避坑指南:OV9281调试中HTS/VTS与曝光时间的那些‘坑’(附计算工具与排查思路)
  • 从Arduino到3D打印机:手把手教你用TB6600HG驱动42步进电机(含电流调节与散热指南)
  • AI招聘全流程应用指南:从人才寻源到智能决策的实践与风险应对
  • 从GUI Guide迁移到APP Designer:老用户避坑指南与一个完整数据绘图App实战
  • 神经网络似然估计加速引力波数据分析
  • ESP32-S3内存爆了?手把手教你用TVM和ESP-DL部署YOLOX-Nano(含PSRAM优化避坑指南)
  • 从行为主义到认知理解:AI为何难以跨越“理解”鸿沟
  • 别再裸机点灯了!用STM32CubeMX快速给你的项目加上FreeRTOS实时系统
  • 告别Burpsuite?试试这款国产一体化渗透测试工具Yakit的安装与初体验
  • 在安卓手机上用LXC跑Ubuntu并部署Docker,我踩过的那些坑(附完整修复脚本)
  • 量子混沌控制:理论与实验突破
  • 智能视觉孪生内核,引领行业视频孪生技术革新
  • 告别报错!Win10下Autodock Vina 1.2.3完整安装与避坑指南(附批量脚本)