深入理解数据结构(3):栈和队列详解

标头风景图片


  • 文章主题:顺序表和链表详解🌱
  • 所属专栏:深入理解数据结构📘
  • 作者简介:更新有关深入理解数据结构知识的博主一枚,记录分享自己对数据结构的深入解读。😄
  • 个人主页:[₽]的个人主页🔥🔥

栈和队列详解

  • 前言
    • 栈的概念及结构
    • 栈的实现
  • 队列
    • 队列的概念及结构
    • 队列的实现
  • 结语

前言

上文我们已经讲完了线性表中最基本的、最常用的顺序表和链表,这一次博主为大家带来基于顺序表和链表实现的线性表中也是十分常用的数据结构栈和队列的详解,希望能够让你对数据结构有一个更加深入的理解。


栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈:栈的删除操作叫做出栈/弹栈。出数据也在栈顶
栈的后进先出

栈的实现

栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小,最简单。
栈的数组实现
静态数组栈
下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈

// 下面是定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈
typedef int STDataType;
#define N 10
typedef struct Stack
{STDataType _a[N];int _top; // 栈顶
}Stack;

动态数组栈
Stack.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{STDataType* _a;int _top;		// 栈顶int _capacity;  // 容量 
}Stack;
// 初始化栈 
void StackInit(Stack* ps);
// 入栈 
void StackPush(Stack* ps, STDataType data);
// 出栈 
void StackPop(Stack* ps);
// 获取栈顶元素 
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数 
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0 
int StackEmpty(Stack* ps);
// 销毁栈 
void StackDestroy(Stack* ps);

Stack.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void StackInit(Stack* ps)
{assert(ps);// 栈区数组未创建,赋成NULLps->_a = NULL;ps->_capacity = 0;// 表示top指向栈顶元素ps->_top = -1;// 表示top指向栈顶元素的下一个//ps->_top = 0
}
void StackPush(Stack* ps, STDataType data)
{assert(ps);// 栈区数据已满,扩容if (ps->_top + 1 == ps->_capacity){// 确定扩容后的新容量,如果是 0 就扩容为 4 ,如果已经扩过容了就将容量在增加一个原来这么多,即扩大为原来的两倍int newcapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;// 扩容STDataType* tmp = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));// 扩容失败打印失败原因后返回if (tmp == NULL){perror("realloc fail");return;}// 未失败将容量更新成新容量,数据指针更新成新指针ps->_capacity = newcapacity;ps->_a = tmp;}// 插入数据ps->_top++;ps->_a[ps->_top] = data;
}
void StackPop(Stack* ps)
{assert(ps);// 栈不为空时才能删除数据assert(ps->_top + 1 > 0);// 栈顶元素坐标下移,删除数据ps->_top--;
}
STDataType StackTop(Stack* ps)
{assert(ps);// 不为空,为空弹不出数据来进行访问,并且访问会导致数组越界assert(ps->_top + 1 > 0);// 返回栈顶元素值return ps->_a[ps->_top];
}
int StackSize(Stack* ps)
{assert(ps);// 返回栈中有效元素个数(栈顶元素 +1 即为栈中有效元素个数)return ps->_top + 1;
}
int StackEmpty(Stack* ps)
{assert(ps);// 为空则返回非 0 的真return ps->_top + 1 == 0;
}
void StackDestroy(Stack* ps)
{assert(ps);// 数据内存释放,再将记录数据的各值清空free(ps->_a);ps->_a = NULL;ps->_capacity = 0;ps->_top = -1;
}

Test.c

#include "Stack.h"
void StackTest1()
{// 初始化栈Stack S1;StackInit(&S1);// 数据入栈StackPush(&S1, 1);StackPush(&S1, 2);StackPush(&S1, 3);StackPush(&S1, 4);StackPush(&S1, 5);// 销毁栈StackDestroy(&S1);// 检测是否销毁成功if (S1._a == NULL && S1._capacity == 0 && S1._top == -1)printf("销毁成功!\n");elseprintf("销毁失败。\n");
}
void StackTest2()
{// 初始化栈Stack S1;StackInit(&S1);// 数据入栈StackPush(&S1, 1);StackPush(&S1, 2);StackPush(&S1, 3);StackPush(&S1, 4);StackPush(&S1, 5);// 从栈顶读取数据后弹出栈中数据while (!StackEmpty(&S1)){printf("%d ", StackTop(&S1));StackPop(&S1);}printf("\n");// 销毁栈StackDestroy(&S1);
}
void StackTest3()
{// 初始化栈Stack S1;StackInit(&S1);// 栈为空时也弹出数据StackPop(&S1);// 销毁栈StackDestroy(&S1);
}
int main()
{StackTest3();return 0;
}

队列

队列的概念及结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
入队:队列的插入操作叫做入队,入数据在队尾
出队:队列的删除操作叫做出队。出数据在队头
(栈和对列都是将出数据的地方叫作顶/头)
队列的出队入队

队列的实现

队列也可以数组和链表的结构实现,使用链表(带头的单链表)的结构实现更优一些(时间复杂度O(1),比数组来说在队头删除数据更加简洁),因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低(时间复杂度为O(N))。
带头单链表实现的队列
队列
Queue.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int QDataType;
// 链式结构:表示队列 
typedef struct QListNode
{struct QListNode* _next;QDataType _data;
}QNode;// 队列的结构 
typedef struct Queue
{QNode* _front;QNode* _rear;int _size;
}Queue;// 初始化队列 
void QueueInit(Queue* q);
// 销毁队列 
void QueueDestroy(Queue* q);
// 队尾入队列 
void QueuePush(Queue* q, QDataType data);
// 队头出队列 
void QueuePop(Queue* q);
// 获取队列头部元素 
QDataType QueueFront(Queue* q);
// 获取队列队尾元素 
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数 
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
int QueueEmpty(Queue* q);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* q)
{assert(q);// 首尾指针都赋成NULL,元素个数赋0q->_front = q->_rear = NULL;q->_size = 0;
}
void QueueDestroy(Queue* q)
{assert(q);// 释放队列数据内存QNode* cur = q->_front;while (cur){QNode* tmp = cur;cur = cur->_next;free(tmp);}// 将队列中的首尾指针数据赋成NULL,队列元素个数也置为0q->_front = q->_rear = NULL;q->_size = 0;
}
void QueuePush(Queue* q, QDataType data)
{assert(q);// 创建新节点QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("maloc fail");return;}newnode->_data = data;newnode->_next = NULL;// 无节点if (q->_front == NULL){q->_front = q->_rear = newnode;}// 有节点else{q->_rear->_next = newnode;q->_rear = newnode;}// 队尾入数据时总元素个数加一q->_size++;
}
void QueuePop(Queue* q)
{assert(q);// 无节点assert(q->_front);// 有节点QNode* tmp = q->_front;q->_front = q->_front->_next;free(tmp);// 一个节点时,尾指针因为释放节点变成野指针,需要置空if (q->_front == NULL)q->_rear = NULL;// 队头出数据时总元素个数减一q->_size--;
}
QDataType QueueFront(Queue* q)
{assert(q);// 无节点assert(q->_front);// 有节点return q->_front->_data;
}
QDataType QueueBack(Queue* q)
{assert(q);// 无节点assert(q->_front);// 有节点return q->_rear->_data;
}
int QueueSize(Queue* q)
{assert(q);// 直接将储存队列主要数据的结构体中的元素个数拿出来返回return q->_size;
}
int QueueEmpty(Queue* q)
{assert(q);return q->_size == 0;
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueTest1()
{// 队列初始化Queue Q1;QueueInit(&Q1);// 队尾入数据QueuePush(&Q1, 1);QueuePush(&Q1, 2);QueuePush(&Q1, 3);QueuePush(&Q1, 4);QueuePush(&Q1, 5);// 销毁队列QueueDestroy(&Q1);if (Q1._front == NULL && Q1._rear == NULL && Q1._size == 0)printf("销毁成功!\n");elseprintf("销毁失败。\n");
}void QueueTest2()
{// 队列初始化Queue Q1;QueueInit(&Q1);// 队尾入数据QueuePush(&Q1, 1);QueuePush(&Q1, 2);QueuePush(&Q1, 3);QueuePush(&Q1, 4);QueuePush(&Q1, 5);// 打印队列中的数据printf("Queue: \n");while (!QueueEmpty(&Q1)){printf("%d ", QueueFront(&Q1));QueuePop(&Q1);}printf("\n");// 销毁队列QueueDestroy(&Q1);
}void QueueTest3()
{// 队列初始化Queue Q1;QueueInit(&Q1);// 队尾入数据QueuePush(&Q1, 1);QueuePush(&Q1, 2);QueuePush(&Q1, 3);QueuePush(&Q1, 4);QueuePush(&Q1, 5);// 打印队列中的数据printf("Queue: \n");while (!QueueEmpty(&Q1)){printf("%d ", QueueFront(&Q1));QueuePop(&Q1);}printf("\n");// 无节点从队列中弹出数据QueuePop(&Q1);// 销毁队列QueueDestroy(&Q1);
}void QueueTest4()
{// 队列初始化Queue Q1;QueueInit(&Q1);// 队尾入数据QueuePush(&Q1, 1);QueuePush(&Q1, 2);QueuePush(&Q1, 3);QueuePush(&Q1, 4);QueuePush(&Q1, 5);// 打印队尾中的数据printf("BackNum: \n%d\n", QueueBack(&Q1));// 打印队列中的数据printf("Queue: \n");while (!QueueEmpty(&Q1)){printf("%d ", QueueFront(&Q1));QueuePop(&Q1);}printf("\n");// 销毁队列QueueDestroy(&Q1);
}void QueueTest5()
{// 队列初始化Queue Q1;QueueInit(&Q1);// 队尾入数据QueuePush(&Q1, 1);QueuePush(&Q1, 2);QueuePush(&Q1, 3);QueuePush(&Q1, 4);QueuePush(&Q1, 5);// 打印队列中的数据个数printf("QueueSize: \n%d\n", QueueSize(&Q1));// 打印队列中的数据printf("Queue: \n");while (!QueueEmpty(&Q1)){printf("%d ", QueueFront(&Q1));QueuePop(&Q1);}printf("\n");// 销毁队列QueueDestroy(&Q1);
}int main()
{QueueTest5();return 0;
}

环形队列
另外扩展了解一下,实际中我们有时还会使用一种队列叫循环队列。如操作系统中的生产者消费者模型中就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现(差别不是很大,并且环形队列的元素个数是固定的,但也可以不是在栈区的静态数组实现的,其可用在动态区用长度不变的数组来实现内存大小不变的效果,可理解成动态数组大小不变所实现的仿静态效果,并且虽逻辑结构上两种环形队列都相接,数组型的环形队列物理结构上首尾不相接,链表物理结构上相接)。
环形队列逻辑图
通常为区分队头和队尾,会在数组中多加一个元素来既防止数组越界,又能够解决因空和满索引条件相同而造成无法判断的假溢出问题(此时环形队列容量为数组总元素个数减一):
数组环形队列实现的底层逻辑图


结语

以上就是博主对栈和队列的详解,😄希望对你的数据结构的学习有所帮助!看都看到这了,点个小小的赞或者关注一下吧(当然三连也可以~),你的支持就是博主更新最大的动力!让我们一起成长,共同进步!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/782990.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

瑞吉外卖实战学习--7、员工信息分页查询

员工信息分页查询 前言1、配置公共分页组件2、创建接口并查看接收的参数3、使用分页构造器并添加查询条件和排序条件4、测试结果 前言 1、配置公共分页组件 package com.example.ruiji_demo.config;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;…

sharemore少数派提示词或许需要工程,但提问一定需要智慧

Matrix 首页推荐 Matrix 是少数派的写作社区&#xff0c;我们主张分享真实的产品体验&#xff0c;有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章&#xff0c;展示来自用户的最真实的体验和观点。 文章代表作者个人观点&#xff0c;少数派仅对标题和排版略作…

代码随想录算法训练营第二十五天| 216.组合总和III,17.电话号码的字母组合

题目与题解 216.组合总和III 题目链接&#xff1a;216.组合总和III 代码随想录题解&#xff1a;216.组合总和III 视频讲解&#xff1a;和组合问题有啥区别&#xff1f;回溯算法如何剪枝&#xff1f;| LeetCode&#xff1a;216.组合总和III_哔哩哔哩_bilibili 解题思路&#xf…

数据结构和算法:图

图 图&#xff08;graph&#xff09;是一种非线性数据结构&#xff0c;由顶点&#xff08;vertex&#xff09;和边&#xff08;edge&#xff09;组成。可以将图 &#x1d43a; 抽象地表示为一组顶点 &#x1d449; 和一组边 &#x1d438; 的集合。 如果将顶点看作节点&#…

9.2-源码分析:Dubbo Remoting 层 Buffer 缓冲区

Buffer 是一种字节容器&#xff0c;在 Netty 等 NIO 框架中都有类似的设计&#xff0c;例如&#xff0c;Java NIO 中的ByteBuffer、Netty4 中的 ByteBuf。Dubbo 抽象出了 ChannelBuffer 接口对底层 NIO 框架中的 Buffer 设计进行统一&#xff0c;其子类如下图所示&#xff1a; …

Oracle数据库——子查询五

14.1子查询语法 子查询 (内查询) 在主查询之前一次执行完成。子查询的结果被主查询(外查询)使用 。范例一:谁的工资比 Abel 高? 第一:查询Abel的工资是多少。第二:比较大于这个工资的人数。 注意事项: 子查询要包含在括号内。将子查询放在比较条件的右侧。</

文件名目录名或卷标语法不正确:数据恢复策略与预防措施

一、文件名目录名或卷标语法不正确的现象 在日常使用电脑或移动设备时&#xff0c;我们经常会遇到“文件名目录名或卷标语法不正确”的错误提示。这种错误通常发生在尝试访问、修改或删除文件、目录或卷标时&#xff0c;系统会提示无法完成操作&#xff0c;因为文件名、目录名…

C++格式化输入和输出

格式化输入与输出 除了条件状态外&#xff0c;每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。 格式状态控制格式化的某些方面&#xff0c;如整型值是几进制、浮点值的精度、一个输出元素的宽度等。 标准库定义了一组操纵符来修改流的格式状态。 一个操纵符…

电商系列之商详

> 插&#xff1a;AI时代&#xff0c;程序员或多或少要了解些人工智能&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 坚持不懈&#xff0c;越努力越幸运&#xff0c;大家…

java解数独(力扣Leetcode37)

数独问题 力扣原题链接 问题描述 数独的解法需遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 数独部分空格内已填入了数字&#xff0c;空白格用.表示。 示例 示例&…

微服务监控:确保分布式系统的可观察性与稳定性

码到三十五 &#xff1a; 个人主页 心中有诗画&#xff0c;指尖舞代码&#xff0c;目光览世界&#xff0c;步履越千山&#xff0c;人间尽值得 ! 目录 一、前言二、微服务监控的重要性三、关键监控指标四、常用监控工具五、最佳实践六、结论 一、前言 在当前的软件开发领域&a…

const在指针中的作用以及*p在各种写法中分别代表什么含义

const在指针中起固定的作用&#xff0c;在不同的写法中其效果也有所区别&#xff0c;具体如下&#xff1a; 1、int* const p固定的是指针p指向的地址。 2、int const *p固定的是指针p指向地址中储存的内容。 例&#xff1a; 以上操作在编译器中执行不了&#xff0c;会报错。…

flutter生成二维码并截图保存到图库

引入库&#xff1a;flutter_screenutil、image_gallery_saver、qr_flutter弹窗布局 import dart:async; import dart:typed_data; import package/generated/l10n.dart; import package:jade/configs/PathConfig.dart; import package:jade/utils/ImageWaterMarkUtil.dart; im…

瑞吉外卖实战学习--8、人员禁用和启用

前言 1、通过前端页面查看接口 会发现请求方式是put 请求接口是employee 2、检查页面传值 根据浏览器的请求可以看到传值为id和status 2、写put请求&#xff0c;添加修改时间和修改人的id然后传回给后台 /*** 启用和禁用员工账号* param request* param employee* return…

【Consul】Linux安装Consul保姆级教程

【Consul】Linux安装Consul保姆级教程 大家好 我是寸铁&#x1f44a; 总结了一篇【Consul】Linux安装Consul保姆级教程✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天要把编写的go程序放到linux上进行测试Consul服务注册与发现&#xff0c;那怎么样才能实现这一过程&am…

25Ramdisk 启动模式简介

Ramdisk 启动模式简介 ramdisk是一种虚拟磁盘技术,我们的PE系统几乎都是使用ramdisk方式从计算机启动的.那么,ramdisk有哪些特点呢? Ramdisk 将内存虚拟为一个磁盘 Ramdisk技术会将你的一部分内存虚拟成一块磁盘分区.使用U盘启动pe系统时,打开pe系统里的文件资源管理器,你会看…

CKS之安全沙箱运行容器:gVisor

目录 一、gVisor介绍 二、gVisor架构 三、gVisor使用前置条件 四、Docker中使用gVisor 五、containerd中使用gVisor 六、Kubernetes结合gVisor使用 一、gVisor介绍 gVisor是Google开源的一种容器沙箱技术&#xff0c;其设计初衷是在提供较高安全性的同时&#xff0c;尽量…

从输入url到页面展示的过程

唠唠叨&#xff1a;我不想误人子弟&#xff0c;我这篇算是搬运工&#xff0c;加上自己的理解做点总结&#xff0c;所以还请大家科学上网去看这篇&#xff1a;https://aws.amazon.com/cn/blogs/mobile/what-happens-when-you-type-a-url-into-your-browser/ 是这六个步骤&#…

杰发科技——Jlink插件使用

0. 简介 杰发自带的烧录工具是ATCLink&#xff0c;基于DapLink适配。个人不太喜欢ATCLink&#xff0c;推荐使用Jlink&#xff0c;毕竟自己买&#xff0c;不用问原厂要&#xff0c;而且带Jlink&#xff0c;至少5Mhz以上。 V9烧录器使用7.50以下版本驱动。 V11烧录器可以使用7…

热烈祝贺阿里云PolarDB登顶2024最新一期中国数据库流行榜

热烈祝贺阿里云PolarDB登顶2024最新一期中国数据库流行榜 墨天轮墨天轮国产数据库流行度排行PolarDB首度夺魁关于话题的讨论数据库流行度排行榜会影响你的数据库选型吗&#xff1f;对于 PolarDB 的本次登顶&#xff0c;你认为关键因素是什么&#xff1f;PolarDB“三层分离”新版…