[面向小白]一篇博客带你认识什么是栈以及如何手撕一个栈

news2024/11/28 8:49:26

目录

0.前言

1.什么是栈

2.实现栈所选择的基本结构

3.认识栈的小练习

4. 用代码实现一个栈

4.1 用什么可以描述出一个栈

4.2栈接口的设计原则

4.3栈的初始化

4.4栈的插入

4.5 栈的删除

4.6 栈的判空

4.7栈的有效元素的数量

4.8取出栈顶元素

4.9栈的销毁

5. 对实现栈的测试与应用


0.前言

本文代码以及画图都已上传至gitee码云,可自取:

4栈的实现 · onlookerzy123456qwq/data_structure_practice_primer - 码云 - 开源中国 (gitee.com)icon-default.png?t=N176https://gitee.com/onlookerzy123456qwq/data_structure_practice_primer/tree/master/4%E6%A0%88%E7%9A%84%E5%AE%9E%E7%8E%B0开始本文内容:

1.什么是栈

先举一个不雅的例子,栈其实就是有口无肛门的生物,进食从口进,消化后的排泄物也从口出。然后排的时候,肯定是后吃进去的食物压在先吃进去的食物的上面,肯定是这个后吃进去食物先排出去,然后这个先吃进去的食物才能排出去

栈这个数据结构,可以用一句话总结:后进先出,先进后出。

再举一个之前的例子,我们之前学过的顺序表/链表,是一个线性结构,这个栈也是一个线性结构。栈其实是顺序/链表的功能退化版本,就是对顺序表/链表的阉割

顺序表可以在任意位置进行插入与删除,而栈是只能在一端进行插入与删除。

我们出数据是在一端,入数据也能在这一端,这一端我们称之为 栈顶。无论是入数据还是出数据,都是只在栈顶操作,这样其实也达成了我们先进后出的目的。

2.实现栈所选择的基本结构

光说不练,假把式。我们实现一个栈,其实可以使用两种线性结构,一种是数组结构,一种是链表结构。所以我们可以实现数组栈,可以实现链式栈,只要让这个数据结构能做到栈的操作即可。

那我们具体用哪一种呢?

栈只能在栈顶进行插入删除,所以无论是数组栈还是链表栈,我们只要限定只能选定一端(首 / 尾)进行插入删除即可。

对于顺序表这个数据结构来说,我们肯定是不是选定头插/头删,因为这样效率是O(N),我们肯定是选择尾部作为栈顶尾插/尾删的效率是O(1)嘛。从效率上说,用顺序表结构实现的栈,已经很优秀了。

可是顺序表自身也是有缺点的,一是realloc扩容,如果是异地扩容,那效率就是很的,二是我们为了防止频繁扩容,我们通常扩容是二倍扩,这样也会存在空间的浪费

对于链表数据结构实现栈来说,如果是非常普通的单链表,我们肯定是选用头插/头删(以头部作为栈顶),效率为O(1),因为单链表尾插/尾删需要找尾,效率为O(N)很低。

当然啦,你设计成双向循环链表方便找尾,单链表只能选头插头删,尾插/尾删效率也很好,也是O(1)。

可是链表自身也是有缺点的,那就是缓存命中率低

3.2 缓存命中率icon-default.png?t=N176https://blog.csdn.net/qq_63992711/article/details/128664914?spm=1001.2014.3001.5502#t16详情可以看这个博客片段。

总结:

两种结构架构栈都可以!如果非要选一种,数组(顺序表)结构稍好一点,因为缓存命中率更高,而且我们现在更看重效率,而不是看重对空间的过度节省,所以我们选用顺序表结构实现栈。

3.认识栈的小练习

1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈。然后再依次出栈,则元素出栈的顺序是() 

A. 12345ABCDE

B. EDCBA54321

C. ABCDE12345

D. 54321EDCBA

按照先进后出,后进先出(上面的出了,下面的才能出),我们依次分析四个选项:

A:入1,出1,入2,出2,入3,出3..........................入E,出E。即可达到A选项的出栈顺序。

B:直接入1 2 3 4 5 A B C D E,然后反着出E D C B A 5 4 3 2 1,就可以达到B选项的出栈顺序。

C:入1 2 3 4 5 A,出A,入B,出B,入C,出C,入D,出D,入E,出E,现在从栈顶到栈底,依次是5 4 3 2 1 (我们入的顺序是1 2 3 4 5,先进的后出),所以出栈的顺序只能是5 4 3 2 1 ,所以说C选项错误。

D:入1 2 3 4 5,出 5 4 3 2 1,入A B C D E,出E D C B A。

4. 用代码实现一个栈

4.1 用什么可以描述出一个栈

我们选定顺序表结构实现一个栈,我们选定在堆区开辟这个连续的顺序表,所以一个栈需要存储一个指向栈实体数组空间的指针_a

然后我们还需要记录栈顶的位置,这个位置决定了我们插入删除的位置(很重要),所以我们记录一个int _top为栈顶_a[_top]的位置就是下一个要插入数据(入栈)的位置_a[_top-1]此时是栈顶的元素,同时_top也能代表当前栈内有效元素的数量的大小。

同时顺序表需要每次扩二倍,所以我们不仅要记录有效元素数目的大小_top,所以还要记录_capacity代表数组空间容量的大小(我这个栈可以容纳存储多少个有效数据)。

//范式的数据类型,方便修改
typedef int STDataType;

//使用数组结构,以尾部作为栈顶,尾插尾删实现栈
typedef struct Stack 
{
	STDataType* _a; //指向栈实体数组空间
	int _top;		//下一个要插入数据的下标&&数组有效数据数量
	int _capacity;	//数组空间容量
}Stack;

4.2栈接口的设计原则

我们要改变一个栈,比如要对一个定义出来的栈进行修改,例如我们传入这个栈进入一个插入接口,经过这个插入接口作用后,可以使得这个栈得到改变。所以我们传参的话,是不可以直接传入这个Stack对象的,因为传值传参我们都是对原对象的拷贝,并不是在改变到传入这个Stack对象的实体,而是在改变这个Stack对象的拷贝。所以我们传入的应该是栈Stack对象的指针。根据指针指向的,就是我们在接口外这个Stack栈对象实体,修改的就不是拷贝了。

//传参传入指向三个结构体变量的指针
void StackInit(Stack* ps);
void StackDestroy(Stack* ps);
bool StackEmpty(Stack* ps);
int StackSize(Stack* ps);
void StackPush(Stack* ps, STDataType x);
void StackPop(Stack* ps);
STDataType StackTop(Stack* ps);

4.3栈的初始化

我们在程序当中,定义一个struct Stack对象,那此时其内部的_a是野指针,且_top和_capacity都是随机值,如果不初始化或者忘记初始化,而直接进行插入删除操作的话,那就会导致灾难性的后果野指针非法访问,_top随机值非法位置访问)。

所以每次定义出一个栈,都要初始化其成员变量

void StackInit(Stack* ps)
{
	ps->_a = NULL;
	ps->_top = ps->_capacity = 0;
}

4.4栈的插入

栈的插入,即入栈,只能从栈顶的位置(_a[_top])进行插入,所以插入只需要用户传入元素,我们在栈的内部实现上直接在栈顶插入即可。

但是每次插入之前,我们必须检查扩容,这个是容易遗忘的。每次插入成功之后,我们还需要++有效数据的个数 / 更新栈顶的位置,即要++_top

void StackPush(Stack* ps,STDataType x)
{
	//首先传入的必须是有效的栈
	assert(ps);
	//检查扩容
	if (ps->_top == ps->_capacity)
	{
		int newcapacity = (ps->_capacity == 0) ? 8 : ps->_capacity * 2;
		STDataType* ptmp = (STDataType*)realloc(ps->_a, newcapacity * sizeof(STDataType));
		if (ptmp == NULL)
		{
			perror("realloc error");
			exit(1);
		}
		//申请成功
		ps->_a = ptmp;
		ps->_capacity = newcapacity;
	}
	//队尾插入<=>尾插数据
	ps->_a[ps->_top] = x;
	ps->_top++;
}

同时我们也要检查用户传入的是不是一个有效的栈指针,如果传入的是一个NULL,这就不是一个指向栈的指针,需要反馈报错

4.5 栈的删除

栈的删除:在顺序表结构实现中,栈的删除是其实是伪删除,其实只需要--_top减少有效数据的一个数量,这样就可以完成删除了。

可是每次删除都需要检查,如果栈是空,那我们其实就不能无脑删除,要对外进行报错提示!同时也要记住,删除要更新有效数据的数量 / 栈顶位置_top--。

void StackPop(Stack* ps)
{
	//有效栈
	assert(ps);
	//必须有数据才能删除
	assert(ps->_top > 0);

	/*更形象可以这样写:assert(!StackEmpty(ps));*/

	//顺序表数量--,即可删除
	ps->_top--;
}

4.6 栈的判空

判断栈是否为空,就是看栈内有效元素的数量的是否为0即可,而_top的大小便代表了这一点。所以如果_top==0,那栈就是空的。

PS:在C语言当中如果想使用bool类型,需要包一个头文件#include<stdbool.h>即可。

同时也要记得检查用户传入的是否是一个有效栈指针,如果是非法栈,比如传一个NULL,那就会导致对空指针解引用的问题。

bool StackEmpty(Stack* ps)
{
	//传入的是有效的栈
	assert(ps);

	return ps->_top == 0;
}

4.7栈的有效元素的数量

我们在外部针对一个栈对象,获取这个栈对象内有效元素的数量,这点是需要的。所以我们设计一个返回栈内有效数据数量的接口。这个_top要插入栈顶位置大小,其大小也代表着栈内有效元素的数目。

int StackSize(Stack* ps)
{
    //传入的是一个有效栈指针
    assert(ps);
	return ps->_top;
}

4.8取出栈顶元素

我们只能从栈顶进行插入删除,对于一个栈来说我们可以获取的数据,只能是栈顶的元素,所以我们需要顶一个接口,返回获取栈顶元素现在是什么栈顶的元素就是_a[_top-1],同时我们也要检查现在栈不为空(不为空才有元素取)检查现在是一个有效的栈指针

STDataType StackTop(Stack* ps)
{
	//有效栈
	assert(ps);
	//必须有数据才能取出
	assert(ps->_top > 0);
	
	/*更形象可以这样写:assert(!StackEmpty(ps));*/

	return ps->_a[ps->_top - 1];
}

4.9栈的销毁

我们说栈区的变量空间,在最后可以由系统回收。可是在堆区的变量空间,必须要主动free掉,否则就会导致内存泄漏。而我们定义的一个栈,是有一个连续的定义在堆区的数组空间*_a,所以说在程序结束之前,我们必须要释放掉这块堆区空间!!!即我们在使用完一个栈之后,一定一定要Destroy!!!

void StackDestroy(Stack* ps)
{
	//传入的是有效的栈
	assert(ps);

	free(ps->_a);
	ps->_top = ps->_capacity = 0;
}

5. 对实现栈的测试与应用

我们实现数据结构,就必须要对之进行测试检查,能不能满足我们实际当中的应用,下面我们设计几个应用栈的简单场景进行测试。

#include"Stack.h"
void StackTest1()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 4);
	StackPush(&st, 7);
	StackPush(&st, 0); 
	StackPush(&st, 9);
	StackDestroy(&st);
}

void StackTest2()
{
	Stack st;
	StackInit(&st);
	StackPush(&st, 4);
	printf("Stack size:%d\n", StackSize(&st));
	StackPush(&st, 7);
	printf("Stack size:%d\n", StackSize(&st));
	StackPush(&st, 0);
	printf("Stack size:%d\n", StackSize(&st));
	StackPush(&st, 9);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackPop(&st);
	printf("Stack size:%d\n", StackSize(&st));
	StackDestroy(&st);
}

void StackTest3()
{
	//测试基本的遍历栈
	Stack st;
	StackInit(&st);
	//依次入栈
	StackPush(&st, 4);
	StackPush(&st, 1);
	StackPush(&st, 3);
	StackPush(&st, 1);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 5);
	//栈:先进后出,后进先出
	while (!StackEmpty(&st))
	{
		//寻找栈顶数据
		int top = StackTop(&st);
		//栈顶数据出栈
		StackPop(&st);
		printf("%d ", top);
	}
	printf("\n");
	StackDestroy(&st);
}

int main()
{
	//StackTest1();
	StackTest2();
	//StackTest3();
	return 0;
}

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

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

相关文章

BUUCTF-[ACTF新生赛2020]Splendid_MineCraft

题目下载&#xff1a;下载 这道题涉及SMC&#xff0c;动静态调试。 查壳&#xff0c;无壳32位&#xff0c;载入IDA 进入主函数main&#xff1a; 从上面可以看出&#xff0c;flag长度为26&#xff0c;前五个是ACTF{&#xff0c;最后一位是}。 strtok()函数用法&#xff1a;st…

sso单点登录

文章目录 目录 文章目录 前言 一、sso结构实现 二、使用步骤 2.1 建一个spring cloud 项目 2.2 common下的core的配置 2.3 实现系统的业务微服务 2.4 sso模块的编写 总结 前言 单点登录(SingleSignOn&#xff0c;SSO)&#xff0c;就是通过用户的一次性鉴别登录。当用户在身份…

复习知识点三:做人不能半途而废,就算躺平也要躺最舒服的那张床

目录 运算符​编辑 键盘录入: 练习:键盘输入数字并求和 练习: 算术运算符 隐式转换(自动类型提升) 强制转换 练习1: 字符串的 "" 操作 ​编辑 练习 1: 练习2: 练习3: 自增自减运算符 赋值运算符 关系运算符(比较运算符)的分类 练习: 逻辑运算符 短路逻辑运…

电商平台销量查询:2023年1月牛奶乳品热门排行榜

随着人们消费能力的提升以及健康意识的增强&#xff0c;牛奶乳品已经成为居民日常饮食中的重要组成部分&#xff0c;伴随人们整体消费的增长&#xff0c;牛奶乳品行业也越来越成熟。 今年1月份我国牛奶乳品行业的整体趋势如何呢&#xff1f;结合数据我们一同来分析&#xff01;…

国外博士后待遇情况汇总

许多老师再考虑申请国外博士后的时候会顾虑待遇方面的问题&#xff0c;对此知识人网小编整理关于主要国家博士后待遇情况的汇总。美国&#xff1a;美国机会更多&#xff0c;生活质量更高&#xff0c;生活空间也广。美国的年薪一般是3.0-6.5万美金左右&#xff0c;刚博士毕业出来…

无脑霸总漫开播,她穿书变成恶毒女配,本想和霸总离婚摆烂

无脑霸总漫开播&#xff0c;她穿书变成恶毒女配&#xff01;本想和霸总离婚摆烂&#xff0c;不料却被霸总盯上了&#xff5e;虐妻一时爽&#xff0c;追妻火葬场&#xff0c;曾经的我你爱搭不理&#xff0c;现在的我你高攀不起&#xff01;#无脑霸总漫开播 #如何在无脑霸总漫里艰…

Sql Server数据库实现表中字段的列加密研究

1、问题描述 去年6月份的时候做过一个薪酬系统&#xff0c;要对里面的一些敏感字段进行一下加密。Sqlserver列加密可以参考官方文档&#xff1a;SQL Server 技术文档 - SQL Server | Microsoft Learn。主要看下来有三种加密方法&#xff1a;1、利用证书对数据进行加密和解密。2…

Google Guice 3:Bindings(1)

1. 序言 上一篇博客&#xff0c;《Google Guice 2&#xff1a;Mental Model》&#xff0c;讲述了Guice的建模思路&#xff1a;Guice is a map Guice官网认为&#xff1a;binding是一个对象&#xff0c;它对应Guice map中的一个entry&#xff0c;通过创建binding就可以向Guice …

RocketMQTemplate 实现消息发送

代码托管于gitee&#xff1a;easy-rocketmq 文章目录一、前置工作二、消费者三、生产者1. 普通消息2. 过滤消息3. 同步消息4. 延时消息5. 批量消息6. 异步消息7. 单向消息8. 顺序消息9. 事务消息概要Demo源码解读一、前置工作 1、导入依赖 <dependency><groupId>…

《羊驼亡命跑》 NFT 系列:羊驼跑酷套装来袭!

完美的羊驼跑酷&#xff01;这一系列植物、平台、愤怒的农民和神秘物品与你们的 Alpacadabraz 化身都是绝配。 关于 Paca Death Run Alpacadabraz 团队推出的首个主要体验的一切都很吸引&#xff01;这款跑酷游戏垂直填满了一个整个 1x1 The Sandbox LAND&#xff0c;挑战玩家在…

扬帆优配|看多A股!多家外资高调发声

外资看多我国的声响和动作正在增多&#xff01; A股商场迎来全面注册制落地后的首个交易日&#xff0c;三大指数集体走强。业内人士分析称&#xff0c;跟着全面实行股票发行注册制改革正式发动&#xff0c;能够预见&#xff0c;跟着商场化程度逐步进步&#xff0c;外资布局我国…

每天五分钟机器学习:新的大规模的机器学习机制——在线学习机制

本文重点 本节课程我们将学习一种新的大规模的机器学习机制--在线学习机制。在线学习机制让我们可以模型化问题。在线学习算法指的是对数据流进行学习而非离线的静态数据集的学习。许多在线网站都有持续不断的用户流,对于每一个用户,网站希望能在不将数据存储到数据库中便顺…

【Mybatis源码分析】datasource配置${}表达式时是如何被解析的?

核心配置中${}表达式配置的解析一、核心配置主体二、核心配置文件中properties是如何被解析的&#xff1f;三、${} 表达式的解析四、总结前提&#xff1a; 核心配置文件是被XMLConfigBuilder 对象进行解析的&#xff0c;configuration 对象是由它父类BaseBuider继承下来的属性…

LQB10,AT24C02的使用

1、单片机用P20和P21和AT24C02通信&#xff1b; 2、比赛提供的开发包里面的代码。 头文件 c文件 提供的代码解读以及修改合适自己使用。 #ifndef _IIC_H #define _IIC_Hvoid IIC_Start(void); void IIC_Stop(void); bit IIC_WaitAck(void); void IIC_SendAck(bit …

产品经理考个 PMP 有用吗?

产品经理考PMP肯定是有用的。学无止境&#xff01; 这里给一些想要转行项目管理的朋友一些PMP考证资料分享&#xff0c;内含不少考纲知识&#xff0c;题库&#xff0c;解题技巧&#xff0c;思维导图等等&#xff0c;有需要就保存下来&#xff0c;留着下次需要的时候用。 一&a…

二、并发编程的三大特性

文章目录并发编程的三大特性1、原子性什么是并发编程的原子性&#xff1f;保证并发编程的原子性synchronizedCASLock锁ThreadLocal2、可见性什么是可见性?解决可见性的方式volatilesynchronizedLockfinal3、有序性什么是有序性?as-if-serialhappens-beforevolatile并发编程的…

谷歌seo新站如何快速排名?如何提高Google自然排名

本文主要分享谷歌SEO如何做新站排名&#xff0c;很多刚出海的外贸小伙伴不会做谷歌SEO&#xff0c;快来学习。 本文由光算创作&#xff0c;有可能会被剽窃和修改&#xff0c;我们佛系对待这种行为吧。 谷歌seo新站如何快速排名&#xff1f; 答案是&#xff1a;大量优质原创内…

科技新浪推前浪 ChatGPT将元宇宙“拍在沙滩上”?

近期ChatGPT的热度显然已经盖过了元宇宙&#xff0c;回想去年元宇宙大热之际&#xff0c;很多企业纷纷跟进&#xff0c;甚至还有不少公司选择更名以表达All In元宇宙的决心。而如今ChatGPT抢占风头&#xff0c;成为新宠&#xff0c;元宇宙似乎被“抛弃”了&#xff0c;难道元宇…

VCL界面组件DevExpress VCL v22.2 - 拥有全新的矢量图形

DevExpress VCL是Devexpress公司旗下最老牌的用户界面套包&#xff0c;所包含的控件有&#xff1a;数据录入、图表、数据分析、导航、布局等。该控件能帮助您创建优异的用户体验&#xff0c;提供高影响力的业务解决方案&#xff0c;并利用您现有的VCL技能为未来构建下一代应用程…

python网络编程详解

最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书&#xff0c;我重点关注了TCP协议相关的内容&#xff0c;结合自己后台开发的经验&#xff0c;写下这篇文章&#xff0c;一方面是为了帮助有需要的人&#xff0c;更重要的是方便自己整理思路&#xff0c;加…