初阶数据结构(五) 栈的介绍与实现

💓博主csdn个人主页:小小unicorn💓
⏩专栏分类:C++

🚚代码仓库:小小unicorn的学习足迹🚚
🌹🌹🌹关注我带你学习编程知识

  • 栈的介绍
    • 栈的概念
    • 栈的结构
  • 栈的实现
    • 初始化栈
    • 销毁栈
    • 入栈
    • 出栈
    • 获取栈顶元素
    • 检测栈是否为空
    • 获取栈中有效元素个数
  • 栈的作用:
  • 栈的应用-------递归:
    • 斐波那契数列的实现:
    • 递归的定义:

栈的介绍

栈的概念

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

压栈:栈的插入操作叫做进栈 / 压栈 / 入栈。(入数据在栈顶)

出栈:栈的删除操作叫做出栈。(出数据也在栈顶)

理解栈的定义需要注意:
首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表7 尾是指栈顶,而不是栈底。

它的特殊之处就在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。

栈的插入操作,叫作进栈,也称压栈、入栈。类似子弹入弹夹,如左下图所示。

栈的删除操作,叫作出栈,有的也叫作弹栈。如同弹夹中的子弹出夹,如右下图所示。

在这里插入图片描述

栈的结构

首先观看下面的动图,对栈概念进一步的加深理解。

在这里插入图片描述

栈的实现

初始化栈

首先,我们需要用结构体创建一个栈,这个结构体需要包括栈的基本内容(栈,栈顶,栈的容量

//栈中存储的元素类型(这里用整型举例)
typedef int STDataType;

typedef struct Stack
{
	STDataType* a;          //栈
	int top;               //栈顶  
	int capacity;          //容量,方便增容
}Stack;


在这里插入图片描述
然后呢,我们需要一个初始化函数,对刚创建的栈进行初始化。

//初始化栈
void StackInit(Stack* pst)
{

	assert(pst);

	pst->a = (STDataType*)malloc(sizeof(STDataType)* 4);  //初始化栈可存储4个元素
	pst->top = 0;                                        //初始时栈中无元素,栈顶为0
	pst->capacity = 4;                                   //容量为4
}



}


销毁栈

前面栈的空间是我们自己动态开辟出来的,当我们使用完后必须释放其内存空间,防止内存泄漏。

//销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);

	free(pst->a);              //释放栈
	pst->a = NULL;            //及时置空
	pst->top = 0;             //栈顶置0
	pst->capacity = 0;        //容量置0
}


入栈

进行入栈操作前,我们首先需要检测栈的当前状态,若已满,则需要先对其进行增容,然后才能进行入栈操作。

//入栈
void StackPush(Stack* pst, STDataType x)
{
	assert(pst);

	if (pst->top == pst->capacity)                          //栈已满,需扩容
	{
		STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity * 2);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		pst->a = tmp;
		pst->capacity *= 2;                              //栈容量扩大为原来的两倍
	}
	pst->a[pst->top] = x;                                //栈顶位置存放元素x
	pst->top++;                                          //栈顶上移
}

出栈

出栈操作比较简单,即让栈顶的位置向下移动一位即可。但需检测栈是否为空,若为空,则不能进行出栈操作。

//出栈
void StackPop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));                      //检测栈是否为空

	pst->top--;                                   //栈顶下移
}

获取栈顶元素

获取栈顶元素,即获取栈的最上方的元素。若栈为空,则不能获取。

//获取栈顶元素
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(!StackEmpty(pst));                    //检测栈是否为空

	return pst->a[pst->top - 1];                 //返回栈顶元素
}

检测栈是否为空

检测栈是否为空,即判断栈顶的位置是否是0即可。若栈顶是0,则栈为空。

//检测栈是否为空
bool StackEmpty(Stack* pst)
{
	assert(pst);

	return pst->top == 0;
}

获取栈中有效元素个数

因为top记录的是栈顶,使用top的值便代表栈中有效元素的个数。

//获取栈中有效元素个数
int StackSize(Stack* pst)
{
	assert(pst);

	return pst->top;                   //top的值便是栈中有效元素的个数
}

栈的作用:

有人可能就产生疑问了,用数组或者链表直接实现功能不就行了吗?干嘛还要引入栈这样的数据结构呢?

回答这个问题,就好比:其实这和我们明明可以有两只脚走路,干嘛还要乘坐汽车,火车,飞机性质一样。理论上,陆地上的任何地方,我们都是可以靠双脚走到的,可那需要耗费的时间和精力可想而知。我们更关注的是到达而不是如何去的过程。

栈的引入可以简化程序设计的问题,划分了不同关注层次,使得思考范围缩小,更利于解决问题核心。

栈的应用-------递归:

栈有一个很重要的应用:在程序设计语言中实现了递归。那么什么是递归呢?
当你往镜子前面一站,镜子里面就有一个你的像。但你试过两面镜子对着一起照吗?如果A、B两面镜子相互面对面放着,你往中间一站,嘿,两面镜子里都有你的千百个“化身”。为什么会有这么奇妙的现象呢?原来,A镜子里有B镜子的像,B镜子里也有A镜子的像,这样反反复复,就会产生一连串的“像中像”。这是一种递归现象,如下图所示。
在这里插入图片描述

我们先来看一个经典的递归例子:斐波那契数列(Fibonacci)。为了说明这个数列,这位斐老还举了一个很形象的例子。

斐波那契数列的实现:

斐老说如果兔子在出生两个月后,就有繁殖能力,一对兔子每个月能生出一对小兔子来。假设所有兔子都不死,那么一年以后可以繁殖多少对兔子呢?

我们拿新出生的一对小兔子分析一下:第一个月小兔子没有繁殖能力,所以还是一对;两个月后,生下一对小兔子共有两对;三个月以后,老兔子又生下一对,因为小兔子还没有繁殖能力,所以一共是三对……以此类推可以列出下表。
在这里插入图片描述
表中数字1,1,2,3,5,8,13,…构成了一个序列。这个是咧有个十分明显的特点,那就是:前面向量两项之和,构成了后一项。如下图所示:
在这里插入图片描述
会发现,编号1的一对兔子经过6个月就变成8对兔子了,如果用数学函数公式来定义的话,那就是:
在这里插入图片描述
思考一下,如果我们要实现这样的数列用常规迭代方法如何操作。以前40位的斐波那契数列为例。

//前40位
int main()
{
	int i;
	int a[40] = { 0 };
	a[0] = 0;
	a[1] = 1;
	printf("0:%d\n", a[0]);
	printf("1:%d\n", a[1]);
	for (i = 2; i < 40; i++)
	{
		a[i] = a[i - 1] + a[i - 2];
		printf("%d:%d\n", i,a[i]);

	}
	return 0;
}

看看结果:
在这里插入图片描述
用递归实现的话:

//斐波那契递归实现:
int Fbi(int i)
{
	if (i < 2)
	{
		return i == 0 ? 0 : 1;
	}
	return Fbi(i - 1) + Fbi(i - 2);
}

int main()
{

	int i;
	printf("递归显示斐波那契数列:\n");
	for (i = 0; i < 40; i++)
	{
		printf("%d:%d\n",i, Fbi(i));
	}

	return 0;
}

看下结果:
在这里插入图片描述

通过比较发现,明显递归版的代码干净很多。这就是递归,估计有人难以理解,函数怎么可以自己调用自己?我们可以换种思路,它调用函数时调用的函数跟它本身一样。

我们可以模拟一下Fbi(5)具体是怎么实现的。

在这里插入图片描述

递归的定义:

在高级语言中,调用自己和其他函数并没有本质的不同。我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。

当然,写递归程序最怕的就是陷入永不结束的无穷递归中,所以,每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。

比如刚才的例子,总有一次递归会使得i<2的,这样就可以执行return i的语句而不用继续递归了。

对比了两种实现斐波那契的代码。迭代和递归的区别是:迭代使用的是循环结构,递归使用的是选择结构递归能使程序的结构更清晰、更简洁、更容易让人理解,从而减少读懂代码的时间。但是大量的递归调用会建立函数的副本,会耗费大量的时间和内存。迭代则不需要反复调用函数和占用额外的内存。因此我们应该视不同情况选择不同的代码实现方式。

那么我们讲了这么多递归的内容,和栈有什么关系呢?这得从计算机系统的内部说起

前面我们已经看到递归是如何执行它的前行和退回阶段的。递归过程退回的顺序是它前行顺序的逆序。在退回过程中,可能要执行某些动作,包括恢复在前行过程中存储起来的某些数据。

这种存储某些数据,并在后面又以存储的逆序恢复这些数据,以提供之后使用的需求,显然很符合栈这样的数据结构,因此,编译器使用栈实现递归就没什么惊奇的了。

简单地说,就是在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。

当然,对于现在的高级语言,这样的递归问题是不需要用户来管理这个栈的,一切都由系统代劳了。

文章到这里就要告一段落了,有更好的思路或问题的,欢迎留言评论区。
下棋预告:初阶数据结构(六) 队列的介绍与实现

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

正中优配:红筹股是啥意思?

随着我国经济的高速开展&#xff0c;越来越多的人开始参加到股票出资中。其中&#xff0c;红筹股作为一种特别类型的股票&#xff0c;备受一些出资者的关注&#xff0c;但对于一般出资者来说&#xff0c;红筹股详细含义还不是特别清楚。本文将从多个角度探讨红筹股的含义、特征…

匿名函数( lambda 表达式)

在 C 中&#xff0c;匿名函数也被称为 lambda 表达式。C11 引入了 lambda 表达式&#xff0c;使得在需要函数对象&#xff08;函数符&#xff09;的地方可以使用匿名函数来代替。 lambda 表达式的基本语法如下&#xff1a; [capture list] (parameter list) -> return typ…

解决crosstalk的方法及原理分析

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 crosstalk是干扰线与受绕线之间由于信号跳变产生的耦合电容引起的。 解决crosstalk的方法从两方面入手,一方面降低耦合电容,一方面降低timing window的overlap。 静态时序分析: 串扰延迟分析 以…

Unity编辑器扩展 | 编辑器扩展基础入门

前言 Unity编辑器扩展 | 编辑器扩展基础一、基本概念二、核心知识点 简述三、相关API 总结 前言 当谈到游戏开发工具&#xff0c;Unity编辑器是一个备受赞誉的平台。它为开发者提供了一个强大且灵活的环境&#xff0c;使他们能够创建令人惊叹的游戏和交互式体验。然而&#xf…

国标视频融合云平台EasyCVR视频汇聚平台的应用场景及其功能说明

一、平台简介 EasyCVR国标视频融合云平台是一款基于端-边-云一体化架构的视频融合AI智能分析网关平台。EasyCVR平台支持视频汇聚、融合管理&#xff0c;兼容多类型设备、多协议接入。其提供的视频功能包括&#xff1a;视频监控、无插件直播录像、云存储、检索回放、智能告警、…

java基于微信小程序的讲座预约系统的研究与实现

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1 简介2 技术栈第三章 系统分析3.1初步需求分析 3.2 系统用例分析3.2.1 公告管理用例分析3.2.2 系…

Docker consul 容器服务自动发现和更新

目录 一、什么是服务注册与发现 二、Docker-consul集群 1.Docker-consul consul提供的一些关键特性 2.registrator 3.Consul-template 三、Docker-consul实现过程 以配置nginx负载均衡为例 先配置consul-agent &#xff0c;有两种模式server和client 四、Docker-cons…

uniapp启动微信小程序开发者工具报错Enable IDE Service (y/N) 

下载安装好微信小程序开发者路径 配置好启动路径后 报错[微信小程序开发者工具] ? Enable IDE Service (y/N) [27D[27C 解决办法 因为微信开发者工具的服务端口号没有打开

【git进阶】 .ignore 忽略有道 忽略核查gitcheck-ignore -v

git .ignore配置 .ignore使用场景新项目中.gitignore用法1 初始化生成.git文件夹2 git status 查看当前文件夹状态3 创建.ignore文件 忽略不想上传的文件4 编辑.gitignore文件 git status查看是否生效 .gitignore进阶用法模式匹配模式匹配例题练习1 忽略所有的内容2 忽略所有目…

iOS - 资源按需加载 - ODR

一、瘦身技术大图 二、On-Demand Resources 简介 将其保存管理在苹果的服务器&#xff0c;按需使用资源、优化包体积&#xff0c;实现更小的应用程序。ODR 的好处&#xff1a; 应用体积更小&#xff0c;下载更快&#xff0c;提升初次启动速度资源会在后台下载操作系统将会在磁…

2023新版医保目录明细(药品查询)

查询医保目录的主要目的是为了了解医保政策对于特定医疗服务、药品和医疗器械的覆盖范围和支付标准。大众可以通过查看医保目录可以确定哪些药品可以被医保支付以及报销的比例和限额&#xff1b;医药从业者可通过查看医保目录可以即使了解医保政策的变化&#xff0c;便于做出相…

Matlab之统计一维数组直方图 bin 计数函数histcounts

一、语法 [N,edges] histcounts(X) [N,edges] histcounts(X,nbins) [N,edges] histcounts(X,edges) 解释&#xff1a; 1.1 [N,edges] histcounts(X) 将 X 的值划分为多个 bin&#xff0c;并返回每个 bin 中的计数以及 bin 边界。histcounts 函数使用自动分 bin 算法&am…

SIEM(安全信息和事件管理)解决方案

什么是SIEM 安全信息和事件管理&#xff08;SIEM&#xff09;是一种可帮助组织在安全威胁危害到业务运营之前检测、分析和响应安全威胁的解决方案&#xff0c;将安全信息管理 (SIM) 和安全事件管理 (SEM) 结合到一个安全管理系统中。SIEM 技术从广泛来源收集事件日志数据&…

W5500-EVB-PICO主动PING主机IP检测连通性(十)

前言 上一章我们用W5500_EVB_PICO 开发板做UDP组播数据回环测试&#xff0c;那么本章我们进行W5500_EVB_PICO Ping的测试。 什么是PING&#xff1f; Ping &#xff08;Packet Internet Groper&#xff09;是一种因特网包探索器&#xff0c;用于测试网络连接量的程序 。Ping是…

Matlab图像处理-灰度插值法

最近邻法 最近邻法是一种最简单的插值算法&#xff0c;输出像素的值为输入图像中与其最邻近的采样点的像素值。是将(u0,v0)(u_0,v_0)点最近的整数坐标u,v(u,v)点的灰度值取为(u0,v0)(u_0,v_0)点的灰度值。 在(u0,v0)(u_0,v_0)点各相邻像素间灰度变化较小时&#xff0c;这种方…

Compose学习 - 环境配置及compose、kotlin插件、gradle、AndroidStudio版本对应关系

最近学习Compose&#xff0c;一开始学习的Compose版本是1.1.1&#xff0c;学习的过程中发现&#xff0c; LazyHorizontalGrid这个方法只有在1.2.0以后版本才支持。 想着既然要升级&#xff0c;直接用最新的好了。后面按照官网建议&#xff0c;下载了最新的AndroidStudio&#…

CDL基础原理

一、CDL简介 CDL&#xff08;全称Change Data Loader&#xff09;是一个基于Kafka Connect框架的实时数据集成服务。 CDL服务能够从各种OLTP数据库中捕获数据库的Data Change事件&#xff0c;并推送到kafka&#xff0c;再由sink connector推送到大数据生态系统中。 CDL目前支…

IntelliJ IDEA 2023.2.1使用Git时弹出“使用访问令牌登录”问题解决

文章目录 一、内网Git环境GitLabGogsGitea 二、外网Git环境GitHubGitee 升级为IntelliJ IDEA 2023.2.1后&#xff0c;使用Git时弹出“使用访问令牌登录”的窗口&#xff0c;习惯使用Git帐号密码登录的用户&#xff0c;面对这个突如其来的弹窗真的很懵。 一、内网Git环境 GitLa…

群晖NAS:DSM7.1激活Advanced Media Extensions【自留记录】

群晖NAS&#xff1a;DSM7.1激活Advanced Media Extensions【自留记录】 本文仅半白群晖可用&#xff0c;不需要安装其他套件或者ssh修改什么 使用DS Video 网页播放视频时候&#xff0c;提示&#xff1a;【不支持当前所选音轨的文件格式&#xff0c; 因此无法播放视频。请尝试…

阿里云centos9stream安装宝塔+vscode(code-server)集成云端开发环境

一、 安装宝塔面板 官网 https://www.bt.cn/new/download.htm 题外话&#xff1a;虽然感觉现在宝塔没以前好用了&#xff0c;而且有centos7、8 mysql编译导致OOM服务器挂掉无法ssh登录的情况&#xff0c;但他还是远程管理服务器的好选择&#xff0c;提示宝塔只支持最新的centos…