【数据结构】栈的概念、结构和实现详解

本文来介绍一下数据结构中的栈,以及如何用C语言去实现。
1. 栈的概念及结构
栈:一种特殊的线性表,它只允许在固定的一端进行插入和删除元素的操作。
进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中元素遵循后进先出LIFO(Last In First Out)的原则
压栈:栈的插入操作叫做进栈/入栈/压栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈,出数据也在栈顶。



2. 实现栈的底层方法选择
没有规定栈的哪端是栈顶,只说了数据插入和删除的一端是栈顶,所以我们栈的底层实现可以用链表或者数组 。

虽然数组和单链表都可以实现栈,但是单链表能很好入数据不好删除数据,这里单链表要删除数据就是尾删,尾删需要找到前一个结点,不是很方便。
非要用链表的话有两个解决方法,1.可以用双向链表 2.我们把单链表的头节点当作栈顶,也就是把左边当栈顶,右边当栈底,对单链表进行头插和头删的操作。
现在有3种方法实现栈,数组,单链表,双链表,我们应该如何选?
首先排除双链表,用双链表不如用单链表,双链表因为一个节点存两个指针,比单链表的一个节点多了4个字节或者8个字节。数组实现栈和单链表实现栈有什么区别?基本没区别,都可以,非要说选一个,我们还是更倾向于数组,因为数组的唯一缺点就是内存不足时需要扩容,扩容的影响也不是特别大,最重要的是数组的缓存效率更高。所以我们就用数组实现栈。

3. 栈的实现
提前说明,如果本篇看不太懂可以先看看【数据结构】顺序表-CSDN博客,我们栈的实现和顺序表的实现差不多。
还是一样,新建一个头文件和两个源文件


点开Stack.h文件,在这个文件里面我们要定义栈的结构,以及给类型和栈的结构取别名。
typedef int STDateType;
typedef struct Stack
{STDateType* a;//动态申请空间 调大小int top; //用栈顶记录元素个数int capacity; //数组实现要扩容,记录空间大小
}ST;
栈一共要实现下面这7个接口,我们将一个一个来看.
void STInit(ST* pst);//栈初始化
void STDistroy(ST* pst);//栈的销毁
void STPush(ST* pst, STDateType x);//压栈
void STPop(ST* pst);//出栈
STDateType STTopDate(ST* pst);//获取栈顶元素
bool STEmpty(ST* pst);//判断栈是否为空
int STSize(ST* pst);//获取栈元素个数
这里是会用到的头文件,且标注了是什么会用到,被包含的头文件全放在Stack.h中
#include <stdio.h>
#include <stdlib.h> //空间申请
#include <stdbool.h> //布尔类型
#include <assert.h> //断言
在Stack.c中只需要包含Stack.h
#include "Stack.h"
准别工作做好后我们开始实现栈。
3.1 栈的初始化和销毁
在Stack.h中进行函数的声明。这里的参数需要传指针。
void STInit(ST* pst);//栈初始化
void STDistroy(ST* pst);//栈的销毁
在SeqList.c中进行函数的实现
void STInit(ST* pst)//栈初始化
{assert(pst); //判断pst是否为空pst->capacity = 0;pst->top = 0;pst->a = NULL;
}
void STDistroy(ST* pst)//栈的销毁
{assert(pst);free(pst->a);pst->a = NULL;pst->capacity = pst->top = 0;
}
这里和顺序表差不多,很简单就不多说了。
3.2 压栈和出栈
在Stack.h中进行函数的声明。
void STPush(ST* pst, STDateType x);//压栈
void STPop(ST* pst);//出栈
这里的参数需要传指针,压栈的函数参数还有要插入的数据。因为栈插入数据就是从栈顶插入,这里就没有什么头插尾插的概念,直接就是Push,删除数据也是,直接栈顶删除Pop。
在SeqList.c中进行函数的实现
先说压栈
先分析空间足够的情况,初始化我们把top置为0,放进一个元素,top就是1,但是这个元素在数组中的下标为0,所以栈顶元素数组下标是top-1,而top指向的是栈顶元素的下一个位置,而不是栈顶元素。
所以我们放数据就是直接放下标为top的位置。

void STPush(ST* pst, STDateType x)//压栈
{assert(pst);pst->a[pst->top] = x; //先放数据pst->top++; //然后top++}
然后考虑扩容。capacity是数组大小,分析一下数组满了的情况


应该是top和capacity相等,此时就要扩容。
void STPush(ST* pst, STDateType x)//压栈
{assert(pst);if (pst->top == pst->capacity){int newcapacity = pst->capacity * 2;//原来空间2倍的扩容//扩容STDateType* tmp = (STDateType*)realloc(pst->a, newcapacity * sizeof(STDateType));if (tmp == NULL) //扩容失败{perror("realloc fail");return;}//扩容成功pst->a = tmp;pst->capacity = newcapacity;}pst->a[pst->top] = x;pst->top++;
}
但是我们要注意这句代码 int newcapacity = pst->capacity * 2; 我们一开始capacity初始化为0,0乘任何数都是0,所以这句换我们要改一下,用一个三目操作符就能解决。
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
再说出栈
出栈和顺序表是一样的,直接top--就行了,不理解的可以看看顺序表中数据的尾删
void STPop(ST* pst)//出栈
{assert(pst);assert(pst->top > 0);pst->top--;
}
在test.c中测试一下栈的初始化,压栈,出栈,栈的销毁。
#include "Stack.h"
int main()
{ST s;STInit(&s); //初始化STPush(&s, 1);//压栈STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);for (int i = 0; i < s.top; i++){printf("%d ", s.a[i]);}printf("\n");STPop(&s);//出栈for (int i = 0; i < s.top; i++){printf("%d ", s.a[i] );}printf("\n");STPop(&s);//出栈for (int i = 0; i < s.top; i++){printf("%d ", s.a[i]);}STDistroy(&s);//销毁return 0;
}
遵循后进先出
3.3 获取栈顶元素
在Stack.h中进行函数的声明。
STDateType STTopDate(ST* pst);//获取栈顶元素
在SeqList.c中进行函数的实现
STDateType STTopDate(ST* pst)//获取栈顶元素
{assert(pst);assert(pst->top > 0);return pst->a[pst->top - 1];
}
在test.c中测试一下
#include "Stack.h"
int main()
{ST s;STInit(&s); //初始化STPush(&s, 1);//压栈STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);for (int i = 0; i < s.top; i++){printf("%d ", s.a[i]);}printf("\n");printf("栈顶元素:%d\n", STTopDate(&s));STPop(&s);//出栈STPop(&s);for (int i = 0; i < s.top; i++){printf("%d ", s.a[i] );}printf("\n");printf("栈顶元素:%d\n", STTopDate(&s));STDistroy(&s);//销毁return 0;
}

3.4 判断栈是否为空
在Stack.h中进行函数的声明。
bool STEmpty(ST* pst);//判断栈是否为空
这里用了bool类型,需要包含头文件stdbool.h
在SeqList.c中进行函数的实现
bool STEmpty(ST* pst)//判断栈是否为空
{assert(pst);if (pst->top == 0) //为空,返回真return true;else //不为空,返回假return false;
}
还有一个更简单的写法,如下
bool STEmpty(ST* pst)//判断栈是否为空
{assert(pst);return pst->top == 0;
}
为空,返回真,不为空返回假。
在test.c中测试一下,用栈的后进先出的特点访问
#include "Stack.h"
int main()
{ST s;STInit(&s); //初始化STPush(&s, 1);//压栈STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);//栈标准的后进先出访问方式while (!STEmpty(&s)){printf("%d ", STTopDate(&s));//先访问栈顶元素STPop(&s); //然后把栈顶元素删除}STDistroy(&s);//销毁return 0;
}

3.5 获取栈元素个数
在Stack.h中进行函数的声明。
int STSize(ST* pst);//获取栈元素个数
在SeqList.c中进行函数的实现
因为我们前面的top初始化为0,所以top就是栈的元素个数,直接返回top就行了。
int STSize(ST* pst)//获取栈元素个数
{assert(pst);return pst->top;
}
在test.c中自己测试一下,这里就不测试了
到这里这个栈就实现好了,本篇也就结束啦,拜拜~

相关文章:
【数据结构】栈的概念、结构和实现详解
本文来介绍一下数据结构中的栈,以及如何用C语言去实现。 1. 栈的概念及结构 栈:一种特殊的线性表,它只允许在固定的一端进行插入和删除元素的操作。 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。 栈中元素遵循后进先出…...
LeetCode 每日一题 2024/7/29-2024/8/4
记录了初步解题思路 以及本地实现代码;并不一定为最优 也希望大家能一起探讨 一起进步 目录 7/29 682. 棒球比赛7/30 2961. 双模幂运算7/31 3111. 覆盖所有点的最少矩形数目8/1 LCP 40. 心算挑战8/2 3128. 直角三角形8/3 3143. 正方形中的最多点数8/4 572. 另一棵树…...
Golang死锁vs操作系统死锁
目录 一、死锁 二、Golang死锁场景 2.1 重复上锁 2.2 不会减少的 WaitGroup 2.3 空select 2.4 channel 一、死锁 1.golang中死锁的触发条件: 死锁是当 Goroutine 被阻塞而无法解除阻塞时产生的一种状态。 2.操作系统死锁: 发生死锁时,线…...
c/c++中π怎么定义
c/c中都没有π的专属变量,一般都是自定义。 方法1:#define pi 3.1415926 方法2:使用反三角函数const double pi acos(-1.0);...
基于whisper流式语音识别
为了实现持续监听麦克风并在检测到声音时进行转录,我们可以将流的监听时间设置为无限长。通过使用一个音量门限来检测是否有声音,然后进行转录。 安装依赖 确保安装必要的库: pip install torch torchaudio openai-whisper sounddevice nu…...
Web3 市场暴跌的时候,哪些token跌的少,哪些还涨了? binance 数据爬取及分析
我爬取了 binance 的一千多个币对信息,提取了以 usdt 计价单位的token,然后统计了一下各个 token 的涨跌情况,发现了2个逆势上涨的token,以及一些跌幅比btc,eth少的种类; 跌幅比btc,eth少的种类…...
ffmpeg获得视频的音频文件
要从视频文件中提取音频文件,你可以使用 FFmpeg,这是一个强大的多媒体框架,用于转换、流化以及处理多媒体数据。下面是如何使用 FFmpeg 从视频文件中提取音频的步骤: 1. 确定视频文件的位置: 确保你知道视频文件的完整…...
Robot Operating System——深度解析单线程执行器(SingleThreadedExecutor)执行逻辑
大纲 创建SingleThreadedExecutor新增Nodeadd_nodetrigger_entity_recollectcollect_entities 自旋等待get_next_executablewait_for_workget_next_ready_executableTimerSubscriptionServiceClientWaitableAnyExecutable execute_any_executable 参考资料 在ROS2中,…...
【TS】使用npm全局安装typescript
查看npm安装 npm -v 安装typescript npm i -g typescript 查看安装 tsc 这就是标致着安装完成。...
安全用户角色权限
$PATH 搞系统设置设置⾥头path ⽬标包含mysql 可执⾏⽂件,那么就是由使⽤ 在终端使⽤ ./bin/mysql -h192.168.71.164 -P3306 -uroot -proot 1.远程登录前提条件是mysql.user表中的host属性为%,如果是 localhost就不允许远程登录,update…...
代理模式学习
代理模式 代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象…...
深入理解Go 语言信号量 Semaphore
1. 什么是信号量 信号量的概念是荷兰计算机科学家 1.1 P/V 操作 Dijkstra 在他的论文中为信号量定义了两个操作 : P 和 V 。 1.2 信号量和互斥锁的区别与联系 信号量有两种类型:二元信号量和计数信号量。 2. 信号量的 channel 实现 程序在运行时,…...
VisualStudio2019下载与安装
1.下载 通过百度网盘分享的文件:VisualStudio2019 链接:https://pan.baidu.com/s/16tqm0ZsOkmXTfGmi4LnGbA 提取码:wx60 --来自百度网盘超级会员V3的分享 2.安装...
李宏毅老师机器学习常见英语词汇
目录 1.Regression :回归2.Classification:分类3.local minima:局部最小值4.saddle point:鞍点5.ground truth:它是机器学习算法的参考标准,用于衡量模型的性的和判断模型的准确性6.optimization:优化 1.Regression :回归 2.Clas…...
人工智能时代,程序员如何保持核心竞争力?
人工智能时代,程序员如何保持核心竞争力? 随着AIGC(如chatgpt、midjourney、claude等)大语言模型接二连三的涌现,AI辅助编程工具日益普及,程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…...
WiFi to Ethernet: 树莓派共享无线连接至有线网口,自动通过Captive Poartal网页登录认证
物联网开发系列:物联网开发之旅① WiFi to Ethernet: 树莓派共享无线连接至有线网口,自动通过Captive Poartal验证物联网开发番外篇之 Captive Portal验证原理 文章目录 背景实现工具实现细节一、将无线连接共享到以太网1. 配置静态IP地址2. 启用IP转发3…...
【神软大数据治理平台-高级动态SQL(接口开发)】
1、背景 业务部门需大数据平台按照所提需求提供企业数据接口,基于神软大数据治理平台-高级动态SQL功能,满足业务需求,如下: (1)业务系统需求: 输入: enterpriseName:…...
【Java数据结构】Map和Set超详细两万字讲解(内含搜索树+哈希表)
🔒文章目录: 1.❤️❤️前言~🥳🎉🎉🎉 2. Map和Set的基础概念 3.Map的基础使用 4.Set的基础使用 5. TreeMap的本质——红黑树 5.1二叉搜索树的概念 5.2二叉搜索树的模拟实现 二叉搜索树——查找 二…...
中国制造2025,会抛弃精益生产吗?
时至今日,“精益生产”模式依旧大行其道,它始终支持着中国制造业以最低的成本做出优质产品。我们认为,纵然是中国制造2025成为现实,精益生产模式也仍然是整个制造业的精髓之一。 首先,精益生产模式最重要的一根脊梁就是…...
Rust 循环
Rust 循环 在编程语言中,循环是一种重要的控制结构,它允许我们重复执行一段代码直到满足特定的条件。Rust 语言提供了多种循环方式,每种方式都有其特定的用途和语法。本文将详细介绍 Rust 中的循环,包括 loop、while、while let、…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...
