[数据结构初阶】栈

各位读者老爷好,鼠鼠我好久没写博客了(太摆烂了),今天就基于C语言浅介绍一下数据结构里面的栈,希望对你有所帮助吧。

目录

1.栈的概念及结构

2.栈的实现

2.1定义栈

2.2.初始化栈 

2.3.入栈

2.4.出栈

2.5.获取栈顶元素

2.6.获取栈中有效元素个数

2.7.检查栈是否为空,如果为空返回真,如果不为空返回假

2.8.销毁栈 

3.栈小应用

3.1.Stack.h

3.2.Stack.c 

3.3.test.c 

4.小知识

5.ending


1.栈的概念及结构

栈:一种特殊的线性表,其只允许在固定的一端进行插入、删除和访问元素操作(ps:我们之前介绍的顺序表和链表都可以在任意位置插入、删除和访问)。进行数据插入、删除和访问操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

这里有几个概念需要理解,将栈比喻成手枪弹夹十分合适:
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶,弹夹出入弹口就像栈顶。

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

后进先出的原则:栈中的数据元素的插入、删除和访问均要遵循这个原则。栈中的数据元素就像子弹,先进入弹夹的子弹会后射出,后进入弹夹的子弹会先射出,栈中数据元素在栈中的操作就像弹夹的子弹一般。

举个列子解释栈中元素遵守的后进先出原则如图:

2.栈的实现

栈的实现一般可以使用数组或者链表实现,实现出来的栈只要满足数据结构对栈的定义即可。相对而言数组的结构实现更优一些,因为数组在尾上插删数据的代价比较小。所以下面我们实现一下以数组尾部为栈顶的数组栈。

而定长的静态栈的结构,实际中一般不实用,所以我们主要实现下面的支持动态增长的栈。

我们栈主要实现以下功能:

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);

// 检测栈是否为空,如果为空返回真,如果不为空返回假 
bool StackEmpty(Stack* ps);

// 销毁栈 
void StackDestroy(Stack* ps);

2.1定义栈

typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}Stack;

方便后续代码的维护,我们先不妨将int重命名成STDataType。由于实现动态生长的栈我们需要一个STDataType*类型指针a维护以后动态申请的空间(用来存放需存储的数据元素的)。用top指向栈顶元素的下一个元素(可以理解成元素个数)。用capacity记录以后动态图申请空间的大小。将a、top和capacity用结构体包起来并重命名成Stack。画下图方便理解:

2.2.初始化栈 

//初始化栈
void StackInit(Stack*ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

断言防止传入的Stack变量地址为空。不妨将a初始化为NULL,所以易知capacity初始化为0合适,由于将top设计成指向栈顶元素下一个元素(或者理解成元素个数) ,所以top也初始化为0。

2.3.入栈

//入栈
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);

	//扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = (ps->capacity == 0) ? 4 : (ps->capacity * 2);
		STDataType* tmp = realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail:");
			return;
		}
		ps->capacity = newcapacity;
		ps->a = tmp;
	}

	ps->a[ps->top] = data;
	ps->top++;
}

断言防止传入的Stack变量地址为空(这点以下如有相同不再赘述)。 当top和capacity相等时说明动态申请的空间不足以支持元素入栈,调用扩容函数(扩容了记得更新capacity),将元素在数组尾部入栈,top加一即可。

2.4.出栈

//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}

断言防止栈为空的时候仍然出栈。元素在数组尾部出栈将top减一即可。 

2.5.获取栈顶元素

//获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

断言防止栈为空时获取栈顶元素(栈为空时获取的栈顶元素不是有效元素)。根据设定,易知top指向栈顶元素的下一个元素,所以ps->a[ps->top-1]就是栈顶元素。

2.6.获取栈中有效元素个数

//获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

根据设定,我们知道top的含义之一就是有效元素个数,所以返回ps->top即可。 

2.7.检查栈是否为空,如果为空返回真,如果不为空返回假

//检测栈是否为空,如果为空返回真,不为空返回假
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

实现这个功能的话返回ps->top==0能很好的形成逻辑自洽。当栈为空时,ps->top==0为真,返回真;当栈不为空时,ps->top==0为假,返回假。 

2.8.销毁栈 

//销毁栈
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

我们再回顾一下栈的想象图: 

对于栈的销毁来说,我们需要主动释放动态申请的内存,就是结构体Stack成员中指针a指向的空间(这块空间也是来存放需存放数据元素的)。所以free掉ps->a,再将ps->a置成NULL、将ps->top和ps->capacity置成0即可。

3.栈小应用

上面这么多代码说不定老爷们看到云里雾里,但是没关系,鼠鼠我写一个工程运用一下上面代码(该工程包含上面所有代码),有兴趣的老爷们可以将下面三个文件放到同一个工程下面玩玩,再参考上面鼠鼠的愚见所不定会明白点!

3.1.Stack.h

#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.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);

// 检测栈是否为空,如果为空返回真,如果不为空返回假 
bool StackEmpty(Stack* ps);

// 销毁栈 
void StackDestroy(Stack* ps);

3.2.Stack.c 

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"


//初始化栈
void StackInit(Stack*ps)
{
	assert(ps);
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

//入栈
void StackPush(Stack* ps, STDataType data)
{
	assert(ps);

	//扩容
	if (ps->top == ps->capacity)
	{
		int newcapacity = (ps->capacity == 0) ? 4 : (ps->capacity * 2);
		STDataType* tmp = realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail:");
			return;
		}
		ps->capacity = newcapacity;
		ps->a = tmp;
	}

	ps->a[ps->top] = data;
	ps->top++;
}

//出栈
void StackPop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}

//获取栈顶元素
STDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(Stack* ps)
{
	assert(ps);
	return ps->top;
}

//检测栈是否为空,如果为空返回真,不为空返回假
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

//销毁栈
void StackDestroy(Stack* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

3.3.test.c 

#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
int main()
{
	Stack s;
	StackInit(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	StackPush(&s, 4);
	StackPush(&s, 5);
	printf("%d\n", StackSize(&s));
	StackPop(&s);
	printf("%d\n", StackSize(&s));
	StackPush(&s, 6);
	while (!StackEmpty(&s))
	{
		printf("%d ", StackTop(&s));
		StackPop(&s);
	}
	printf("\n");
	printf("%d", StackSize(&s));
	StackDestroy(&s);
	return 0;
}

运行结果如图可以看看: 

 刚开始栈顶入栈五个数据元素,所以第一个printf打印5;然后将数据元素5栈顶出栈,所以第二个printf打印4;再次入栈数据元素6,此时栈内数据元素从栈底到栈顶分别为:1、2、3、4、6。

那我们如何打印栈内所有数据元素呢?

其实写一个如图中的while循环即可,由于栈要严格按照它的规定去访问数据元素,所以访问的数据元素只能时栈顶的,想要访问栈顶数据元素的前一个数据元素就必须将栈顶数据元素出栈才能访问到栈顶数据元素前一个数据元素,所以访问一遍栈后栈也就空了。

根据分析,while循环打印出来的应该是6 4 3 2 1。后面的printf打印为0也证明栈已经空了。

4.小知识

应该有一些老爷们听说过"栈溢出"这个概念,但我们这篇博客介绍的栈不是"栈溢出"的那个栈。

咱们这篇博客讲的栈是数据结构这门学科的概念,是一种数据结构,这个“栈”不存在“栈溢出”的概念。

“栈溢出”讲的栈是操作系统或者编程语言这些学科的概念。我们知道内存会划分成各种区域,其中有一个区域为栈区,“栈溢出”这个栈就是一个内存区域。很多情况会导致栈溢出,比如一个递归程序,由于递归返回条件有问题,导致递归不断建立栈帧,使得栈区空间没了还在建立栈帧就导致“栈溢出”。

5.ending

老弟我也是小白,如果有错误恳请各位大佬指正啊,感谢各位佬阅读到这里了!

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

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

相关文章

【Java EE初阶三十】JVM的简单学习

1. JVM 内存区域划分 一个运行起来的 Java 进程&#xff0c;就是一个 JVM 虚拟机&#xff0c;需要从操作系统申请一大块内存&#xff0c;就会把这个内存&#xff0c;划分成不同的区域&#xff0c;每个区域都有不同的作用. JVM 申请了一大块内存之后,也会划分成不同的内…

Git 基于ED25519、RSA算法生成 SSH 密钥

Git 基于ED25519、RSA算法生成 SSH 密钥 基于ED25519算法&#xff0c;生成密钥对命令如下&#xff1a; ssh-keygen -t ed25519 -C "邮箱地址"基于RSA算法&#xff0c;生成密钥对命令如下&#xff1a; ssh-keygen -t rsa -C "<注释内容>"基于ED255…

Day14:信息打点-主机架构蜜罐识别WAF识别端口扫描协议识别服务安全

目录 Web服务器&应用服务器差异性 WAF防火墙&安全防护&识别技术 蜜罐平台&安全防护&识别技术 思维导图 章节知识点 Web&#xff1a;语言/CMS/中间件/数据库/系统/WAF等 系统&#xff1a;操作系统/端口服务/网络环境/防火墙等 应用&#xff1a;APP对象/…

【多模态融合】CRN 多视角相机与Radar融合 实现3D检测、目标跟踪、BEV分割 ICCV2023

前言 本文介绍使用雷达与多视角相机融合&#xff0c;实现3D目标检测、3D目标跟踪、道路环境BEV分割&#xff0c;它是来自ICCV2023的。 会讲解论文整体思路、输入数据分析、模型框架、设计理念、损失函数等。 论文地址&#xff1a;CRN: Camera Radar Net for Accurate, Robus…

如何使用 CSS object-fit 进行图片的缩放和裁剪

简介 在处理图片时&#xff0c;你可能会遇到需要保持原始宽高比的情况。保持宽高比可以防止图片被拉伸或压缩而出现失真。解决这个问题的常见方法是使用 background-image CSS 属性。更现代的方法是使用 object-fit CSS 属性。 在本文中&#xff0c;你将探索 object-fit CSS …

如何在有/没有备份的情况下恢复华为上已删除的视频?6 个推荐选项

“我不小心删除了华为手机上的一堆视频。我怎样才能把它们找回来&#xff1f;我在谷歌上也找不到它们”。——来自知乎 在我们日常生活的喧嚣中&#xff0c;意外时有发生。无论是由于华为手机上的无意删除、恢复出厂设置、病毒感染、数据损坏还是系统故障&#xff0c;这些视频…

GEE数据——GEDI04_A_和GEDI02_A_002_MONTHLY出现的数据问题

简介 产品介绍 该数据集包含全球生态系统动力学调查&#xff08;GEDI&#xff09;第 4A 级&#xff08;L4A&#xff09;第 2 版对地上生物量密度&#xff08;AGBD&#xff0c;单位为兆克/公顷&#xff09;的预测&#xff0c;以及对每个采样地理定位激光足迹内预测标准误差的估…

python+django高校澡堂洗浴浴室预约签到管理系统8d8c

本系统在设计过程中&#xff0c;高校洗浴管理系统的出现就有很大的需求。该系统可以很好地解决这些麻烦和问题。 很好地发挥了该开发方式的优势&#xff0c;让实现代码有了良好的可读性&#xff0c;而且使代码的更新和维护更加的方便&#xff0c;操作简单&#xff0c;对以后的维…

前端将html导出pdf文件解决分页问题

这是借鉴了qq_251025116大佬的解决方案并优化升级完成的&#xff0c;原文链接 1.安装依赖 npm install jspdf html2canvas2.使用方法 import htmlToPdffrom ./index.jsconst suc () > {message.success(success);};//记得在需要打印的div上面添加 idlet dom document.que…

文心一言 VS 讯飞星火 VS chatgpt (209)-- 算法导论15.4 6题

六、设计一个 O(nlgn) 时间的算法&#xff0c;求一个 n 个数的序列的最长单调递增子序列。&#xff08;提示&#xff1a;注意到&#xff0c;一个长度为 i 的候选子序列的尾元素至少不比一个长度为 i-1 候选子序列的尾元素小。因此&#xff0c;可以在输入序列中将候选子序列链接…

如何在Linux上为PyCharm创建和配置Desktop Entry

在Linux操作系统中&#xff0c;.desktop 文件是一种桌面条目文件&#xff0c;用于在图形用户界面中添加程序快捷方式。本文将指导您如何为PyCharm IDE创建和配置一个 .desktop 文件&#xff0c;从而能够通过应用程序菜单或桌面图标快速启动PyCharm。 步骤 1: 确定PyCharm安装路…

Nodejs 第五十二章(定时任务)

什么是定时任务&#xff1f; 定时任务是指在预定的时间点或时间间隔内执行的任务或操作。它们是自动化执行特定逻辑的一种方式&#xff0c;可用于执行重复性的、周期性的或计划性的任务。 定时任务通常用于以下情况&#xff1a; 执行后台任务&#xff1a;定时任务可用于自动…

Nodejs 第五十一章(限流阀)

限流功能 目前我们学习了redis,lua,nodejs&#xff0c;于是可以结合起来做一个限流功能&#xff0c;好比一个抽奖功能&#xff0c;你点击次数过多&#xff0c;就会提示请稍后重试&#xff0c;进行限制&#xff0c;我们来实现一下该功能。 安装依赖 npm i ioredis express代码…

『操作系统OS笔记』MAC(m1芯片)电脑安装FFmpeg

MAC(m1芯片)电脑安装FFmpeg mac电脑安装ffmpeg两种方法 文章目录 1. brew安装FFmpeg2. 官网下载FFmpeg压缩包3. 使用FFmpeg将音频和视频合并 1. brew安装FFmpeg brew install ffmpeg # 需要等比较久的时间&#xff0c;安装很多东西&#xff0c;安装过程中如果遇到报错对应解决…

第十一篇 - 应用于市场营销视频场景中的人工智能和机器学习技术 – Video --- 我为什么要翻译介绍美国人工智能科技巨头IAB公司?

IAB平台&#xff0c;使命和功能 IAB成立于1996年&#xff0c;总部位于纽约市。 作为美国的人工智能科技巨头社会媒体和营销专业平台公司&#xff0c;互动广告局&#xff08;IAB- the Interactive Advertising Bureau&#xff09;自1996年成立以来&#xff0c;先后为700多家媒体…

MATLAB环境下基于图像处理的计算病理学图像分割(MATLAB R2021B)

人工智能是病理学诊断和研究的重要新兴方法&#xff0c;其不仅可用于病理形态数据分析&#xff0c;还可整合免疫组化、分子检测数据和临床信息&#xff0c;得出综合的病理诊断报告&#xff0c;为患者提供预后信息和精准的药物治疗指导。计算病理学是病理学与AI、计算机视觉等信…

机器人编程学习有哪些好处?

机器人编程学习有许多好处&#xff0c;无论是对个人还是对社会都具有重要意义。以下是机器人编程学习的一些好处&#xff1a; 1. **培养计算思维&#xff1a;** 通过机器人编程学习&#xff0c;可以培养逻辑思维、问题解决能力和创新思维。编程过程中需要分析问题、设计算法、…

旧物回收小程序开发:环保与科技的创新结合

随着科技的飞速发展&#xff0c;我们的日常生活越来越离不开手机应用程序。而在环保日益成为社会焦点的今天&#xff0c;如何将科技与环保相结合&#xff0c;成为了一个值得深思的问题。今天&#xff0c;我们将探讨旧物回收小程序的开发&#xff0c;它如何助力环保&#xff0c;…

dolphinscheduler海豚调度(五)seatunnel案例

seatunnel作为新一代流行的数据集成工具&#xff0c;其功能非常强大且简单易用&#xff0c;今天演示一下如何通过dolphinscheduler创建并运行seatunnel任务 本次dolphinscheduler和seatunnel均部署在同一机器上的单机版本 1、环境配置 打开dolphinscheduler安装目录&#xf…

Appium系列(1)安装启动Appium

Appium环境准备 Mac电脑jdk环境AndroidSDK环境node>8.1.0&#xff08;最好用最新版本&#xff09; 安装命令 npm i -g appium安装不成功请检查node 版本是否正确 安装成功命令行输入appium回车查看 安装驱动程序 1、先检查当前驱动情况 通过 appium driver list 进行…