数据结构之栈以及栈的基本操作

article/2025/8/16 9:03:57

文章目录

    • 前言
    • 进栈出栈的变化形式
    • 栈的实现
      • 栈的顺序存储结构:
      • 栈的链式存储结构:
      • 文件的创建
      • 栈结构的定义
      • 栈的初始化
      • 入栈
      • 出栈
      • 获取栈顶元素
      • 获取栈中有效元素个数
      • 检测栈是否为空
      • 销毁栈
    • 括号匹配问题

前言

栈是限定仅仅在表尾进行插入和删除操作的线性表。

在软件中,栈的这种后进先出数据结构的应用是非常普遍的。比如我们在使用浏览器时,浏览器都有一个“后退键”,你点击后可以按照访问顺序的逆序加载浏览过的网页。很多类似的软件,比如Word、Photoshop等文档或图像编辑软件中,都有撤销的操作,也是用栈这种方式来实现的。

我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何元素的栈称为空栈,栈又被称为后进先出(Last In First Out)的线性表,简称LIFO结构。

如下图:

网站的返回键:

image-20210805190707729

word里面的撤销键:

image-20210805205402128

还有很多涉及到栈的软件,这里就不一一说了,下面我们来看压栈和出栈

压栈栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈栈的删除操作叫做出栈。出数据也在栈顶。

image-20210805205231486

image-20210805205113200

动图演示进栈:

进栈

出栈12

进栈出栈的变化形式

有个问题问问大家?最先进栈的元素,是不是就只能是最后出栈呢?

答案是不一定,栈对线性表的插入和删除的位置进行了限制,并没有对插入和删除的时间进行限制,所以,在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。

我们举个例子:

现在有3个数字元素1、2、3一次进栈,会有那些的出栈次序呢?

第一种:1、2、3进,再3、2、1出,出栈次序为321

第二种:1进,1出,2进,2出,3进,3出,出栈次序为123

第三种:1进,2进,2出,1出,3进,3出,出栈次序为213

第四种:1进,2进,2出,3进,3出,1出,出战次序为231

第五种:1进,1出,2进,3进,3出,2出,出栈次序为132

有没有可能是312这种出栈顺序呢?答案是不可能的,要让3先出栈,那么123必须全部进栈,那么出栈顺序必须是321。

栈的实现

我们想一下栈的实现我们是使用顺序表实现呢还是链表实现呢?其实相对而言顺序表结构的实现会更优一些,因为数组在尾上插入和删除数据的代价比较小。

栈的顺序存储结构:

image-20210805214411342

栈的链式存储结构:

进栈示意图:

image-20210805214340496

出栈示意图:

image-20210805214354152

注意:

栈的链式结构不能让尾做栈顶,因为栈的操作都在栈顶,在栈顶进行进栈和出栈时,如果让尾做栈顶,那就相当于链表的尾插尾删,而链表的尾插尾删是需要遍历找尾和尾的前一个元素的,而栈这种结构我们是无法进行遍历的,所以我们需要让链表的头做栈顶。

因为栈的顺序表结构的实现会更优一些,因为数组在尾上插入和删除数据的代价比较小,所以我们实现顺序表结构的栈


文件的创建

我们首先创建三个文件:

image-20210806155517496

Stack.h对相关头文件的包含,以及实现栈的结构和函数的声明

Stack.c对实现栈的操作的函数进行定义

test.c文件进行栈相关函数和栈功能的测试


栈结构的定义

话不多说,我们开始实现栈,首先实现栈的进栈和出栈操作,我们需要先定义栈这样的一个结构体:

typedef int STDataType;
typedef struct Stack
{STDataType* a;//指向动态开辟的内存的指针int top;int capacity;
}Stack;

我们实现顺序表结构的栈,和博主前面讲的顺序表的实现一样,我们使用动态顺序表,这样大小灵活可变,我们这里定义一个指向动态开辟的内存的指针a,我们定义一个top来指示栈顶元素的位置,为了满足动态顺序表我们还需定义一个表示容量的变量。

下面我们来完成栈的这些操作:

// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);

栈的初始化

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

image-20210806152958982

我们经过调试发现已经将栈已经初始化


入栈

// 入栈
void StackPush(Stack* ps, STDataType data)
{assert(ps);//检测容量if (ps->capacity == ps->top){//增容int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* temp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (temp == NULL){perror("realloc");return;}ps->a = temp;ps->capacity = newcapacity;}//操作入栈ps->a[ps->top] = data;ps->top++;
}

在入栈元素前,我们需要先考虑增容,仔细想想,当前top值等于我们的当前容量时我们要增容,我们在增容时,如果capacity是0时我们先开辟存储4个数据的内存,不是0时,我们就进行二倍的增容,如果开辟失败了,我们加一个判断pal->a是不是NULL,是NULL则打印错误返回,最后将capacity更新,入栈的话就简单了,因为top就是我们栈里面最后一个元素的下一个元素的下标,所以我们直接赋值即可,最后将top++。

下面我们调试一下入栈操作的代码:

image-20210806153441747

image-20210806153541126

image-20210806153609312

image-20210806153629459

发现没有问题,下面我们继续看接下来的操作:


出栈

// 出栈
void StackPop(Stack* ps)
{assert(ps);assert(!StackEmpty(ps));//栈不能为空ps->top--;
}

首先出栈,我们得栈不能为空,所以我们这里断言一下,这个判空得函数会在下面会写。出栈我们直接top–就可以了

我们进行调试代码:

image-20210806161955981
image-20210806162020541

发现此时top已经改变。


获取栈顶元素

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

我们的top是栈中最后一个元素的下一个位置的下标,所以top-1就是最后一个元素的下标,我们将它返回即可


获取栈中有效元素个数

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

top就是我们的元素个数。返回即可


检测栈是否为空

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

判断栈是否为空,当ps->top==0时,我们的栈是空的,这句话是真,则返回真,ps->top!=0时,这句话为假,则返回假。

销毁栈

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

销毁栈,如果指向存储栈中元素空间的指针不为NULL的话,我们free掉这个指针,并将他置空,防止出现野指针,最好将top以及capacity都清0。

栈的元素的打印我们是没办法进行的,因为栈只能在栈顶操作,我们不能遍历它,我们可以这样打印,但是打印出来的元素只能是逆序的。

while (!StackEmpty(&s))
{printf("%d ", StackTop(&s));StackPop(&s);
}

image-20210806161652921

我们每次打印栈顶元素,打印为将它pop,可以打印出逆序的,但是此时栈已经空了

我们接下来来看一个栈的面试题:

括号匹配问题

点击括号匹配问题跳转到做题网站

问题描述:

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

1、左括号必须用相同类型的右括号闭合。
2、左括号必须以正确的顺序闭合。

示例1:

输入:s = "()"输出:true

示例 2:

输入:s = "()[]{}"输出:true

示例 3:

输入:s = "(]"输出:false

示例 4:

输入:s = "([)]"
输出:false

示例 5:

输入:s = "{[]}"
输出:true

思路:

该题需要用到栈的功能,遍历字符串,遇到左括号就入栈,直到遇到右括号,然后取栈顶元素跟它比较是否匹配,不匹配返回false,匹配则继续

image-20210806175803253

我们把我们上面实现的栈的操作函数拷贝进去:

typedef char STDataType;
typedef struct StackNode
{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);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);// 初始化栈
void StackInit(Stack* ps)
{assert(ps);ps->a = NULL;ps->capacity = ps->top = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{assert(ps);//检测容量if (ps->capacity == ps->top){//增容int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;STDataType* temp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (temp == NULL){perror("realloc");return;}ps->a = temp;ps->capacity = newcapacity;}//操作入栈ps->a[ps->top] = data;ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{assert(ps);assert(!StackEmpty(ps));//栈不能为空ps->top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{assert(ps);return ps->a[ps->top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{assert(ps);return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{assert(ps);if (ps->a){free(ps->a);ps->a = NULL;}ps->capacity = 0;ps->top = 0;
}bool isValid(char * s)
{Stack st;StackInit(&st);while(*s){if(*s=='('||*s=='['||*s=='{'){StackPush(&st,*s);s++;}else{char ch=StackTop(&st);StackPop(&st);if((*s==')' && ch!='(')||(*s==']' && ch!='[')||(*s=='}' && ch!='{')){//不匹配return false;}else{//匹配s++;}}}return true;
}

我们按逻辑写下来然后提交发现有错误,这里我们就要看它的错误案例:

image-20210806172558756

它说只输入"["时输出的是true,但是正确答案是false,于是我们对照代码走一遍,发现我们将[入栈之后循环就结束了,返回了true,所以我们返回时不能这么写,我们应该写return StackEmpty(&st);上面循环走完,栈为空返回true,栈不为空返回false。

我们提交后还有一个错误:

image-20210806173113210

我们对照代码走一遍,发现我们栈里面还没有元素时,就取栈顶元素了,于是我们在前面加个判断:

if(StackEmpty(&st))
{return false;
}

当没有进栈元素时,直接返回false

我们改正后,发现可以通过了:

image-20210806173427847

此时别高兴太早,我们的代码还有问题,有什么问题呢?

是不是我们动态开辟的内存还没有释放啊,对,此时造成了内存泄漏。

所以我们最后应该这样改:

bool isValid(char * s)
{Stack st;StackInit(&st);bool match=true;while(*s){if(*s=='('||*s=='['||*s=='{'){StackPush(&st,*s);s++;}else{if(StackEmpty(&st)){match=false;break;}char ch=StackTop(&st);StackPop(&st);if((*s==')' && ch!='(')||(*s==']' && ch!='[')||(*s=='}' && ch!='{')){//不匹配match=false;break;}else{//匹配s++;}}}if(match==true){match = StackEmpty(&st);}StackDestroy(&st);return match;
}

我们设定了match来保存真假,初始为真,进去循环那里比较时如果栈为空将match改为false,并且在不匹配那里也要将match改为false,循环出来,判断match是不是为真,为真的话将判断栈是不是空赋给它,是空则match为true,不是空则match为false,最后统一销毁栈,最后返回match。

关于栈就讲解到这里,觉得可以的话点个赞再走吧,欢迎大家学习交流


http://chatgpt.dhexx.cn/article/52LgEvuy.shtml

相关文章

【数据结构二】栈

数组是一种线性结构,并且可以在任意位置插入和删除数据。但是有时候,为了实现某些功能,必须对这种任意性加以限制。栈和队列就是比较常见的受限的线性结构。 栈:Stack,也是一种常见的数据结构。它是一种受限的线性结构…

数据结构 栈 入栈 输出 出栈

数据结构 栈 入栈 输出 出栈 #include<bits/stdc.h> /* #include<iostream> #include<> */ using namespace std; //pA->p(Next)pB->p(top)含义是pA指向pB typedef struct Node//有节点的数据类型 {int data;//数据域struct Node * pNext; }NODE,*PN…

数据结构-栈

栈的定义 栈是一种特殊的线性表&#xff0c;仅允许在表的一端进行插入和删除运算。这一端被称为栈顶&#xff08;top&#xff09;&#xff0c;相对地&#xff0c;把另一端称为栈底&#xff08;bottom&#xff09;。向一个栈插入新元素又称作进栈、入栈或压栈&#xff08;push&…

数据结构-栈及栈的应用

目录 栈的概述 部分算法分析 顺序栈的表示和实现 global.h Stack.h StackTest.cpp 运行结果 栈的应用 数的任意进制转换 括号匹配检验 栈的概述 栈是一种重要的线性结构&#xff0c;属于一种操作受限的线性表栈(stack) 是限定仅在表尾进行插入或删除操作的线性表表尾端…

java数据结构-栈

栈 1、栈的定义 栈&#xff08;Stack&#xff09;&#xff1a;是只允许在一端进行插入或删除的线性表。首先栈是一种线性表&#xff0c;但限定这种线性表只能在某一端进行插入和删除操作。 栈顶&#xff08;Top&#xff09;&#xff1a;线性表允许进行插入删除的那一端。 栈底…

数据结构栈(顺序栈、链栈、插入push、删除pop)、队(循环队,链队、入队push,出队pop)知识点梳理

数据结构栈知识点梳理 一 栈的定义 栈&#xff08;stack&#xff09;是限定仅在表尾进行插入和删除操作的线性表 不含任何元素的栈称为空栈 允许插入和删除的一端成为栈顶&#xff08;top&#xff09;&#xff0c;另一端称为栈底&#xff08;bottom&#xff09; 具有LIFO&a…

数据结构栈和队列

整本书的知识点&#xff0c;点击右方链接&#xff1a;整本书笔记知识点 文章目录 三、栈和队列3.1、栈和队列的定义和特点3.1.1、栈的定义和特点3.1.2、队列的定义和特点 3.2、案例引入3.3、栈的表示和操作的实现3.3.1、栈的类型定义3.3.2、顺序栈的表示和实现3.3.3、链栈的表示…

C++数据结构——栈

C数据结构——栈 最近计划再复习一遍数据结构&#xff0c;看到一篇博客&#xff1a;https://www.cnblogs.com/QG-whz/p/5170418.html#_label0。 1、栈(Stack)是一种线性存储结构&#xff0c;它具有如下特点&#xff1a; &#xff08;1&#xff09;栈中的数据元素遵守“先进后…

数据结构 栈-链栈及基本操作

目录 一.栈的定义二.栈的特点三.栈的理解四.链栈引入五.链栈定义六.链栈的结构体设计七.链栈的基本操作7.1链栈的初始化7.2链栈判空7.3链栈入栈7.4链栈出栈7.4取栈顶元素 八.总结 一.栈的定义 栈是限定仅在表尾进行插入和删除操作的数据结构&#xff08;受到限制的线性表&…

数据结构之——栈

文章目录 数据结构之——栈一&#xff1a;栈的定义&#xff1a;二&#xff1a;栈的抽象数据类型&#xff1a;三&#xff1a;栈的顺序存储结构及其实现&#xff1a;1: 预说明&#xff1a;2&#xff1a;栈的顺序存储结构——结构定义&#xff1a;3:栈的顺序存储结构——进栈操作4…

数据结构与算法(3)栈

栈 1、栈的一个实际需求 请输入一个表达式&#xff1a;7x2x2-51-53-3 2、栈的介绍 栈的英文为stack 栈是一个**先入后出(FILO-First In Last Out) **的有序列表 栈(stack) 是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。 允许插入和删除的一端…

【数据结构】栈

栈的概念及结构 在学习栈前&#xff0c;我们先看一下栈的正式解释&#xff1a; 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。 栈中的数据元素遵守后进先出 L…

什么是栈?

本文将介绍一个重要的数据结构—栈&#xff0c;和之前讲到的链表、数组一样也是一种数据呈线性排列的数据结构&#xff0c;不过在这种结构中&#xff0c;我们只能访问最新添加的数据。栈就像是一摞书&#xff0c;拿到新书时我们会把它放在书堆的最上面&#xff0c;取书时也只能…

【图解数据结构】栈全面总结

目录 一、前言 二、基本概念 三、栈的表示和实现 1.顺序栈 2.链栈 四、栈的常见算法实现 1.初始化 2.判空 3.判满 4.顺序栈取栈顶元素 5.顺序栈入栈 6.顺序栈出栈 五、双栈 1.双端顺序栈进栈操作 2.双端顺序栈出栈操作 六、栈的应用举例 1.回文游戏 2.多进制…

数据结构——栈

栈和队列 栈和队列介绍特点栈、队列与一般线性表的区别栈——stack栈的基本运算栈的存储结构顺序栈链栈 栈的应用 栈和队列 介绍 栈和队列是在软件设计中常用的两种数据结构&#xff0c;它们的逻辑结构和线性表相同 特点 栈和队列在运算上受到了限制&#xff1a;栈是按照“…

遥感影像百分比线性拉伸

AI Earth地球科学云平台开发者模式提供了丰富的遥感数据和函数计算能力&#xff0c;下面介绍结合AIE Notebook&#xff0c;实现遥感数据的百分比线性灰度拉伸。 本期开发者实践案例——遥感影像百分比线性拉伸 灰度拉伸 (GrayScale Stretch) 是遥感影像处理过程中的重要步骤&…

遥感影像分类方法

最初的遥感影像分类是通过目视解译(濮静娟, 1984)来完成的&#xff0c;对研究人员的主观意识有较强的依赖性&#xff0c;而且效率较低&#xff0c;适用于数据量较小的情况&#xff0c;通常作为其他方法对比的对象。目前的遥感图像分类主要以计算机分类为主&#xff0c;因此按照…

遥感影像配准

文章目录 前言步骤1.ENVI&#xff1a;打开Image Registration Workflow2.Image Registration Workflow(1)选择GF2为Base Image File&#xff0c;某季节Sentinel2为Warp&#xff0c;然后Next(2)修改该参数为100(3)人为选择5个左右控制点&#xff0c;然后Next(4)删除离谱点(5)在E…

遥感图像分类技术

什么是遥感图像分类技术&#xff1f; 图像分类是将土地覆盖类别分配给像素的过程。例如&#xff0c;这9个全球土地覆盖数据集将图像分为森林、城市、农业和其他类别。 https://gisgeography.com/free-global-land-cover-land-use-data/ 一般来说&#xff0c;这是遥感中的三种主…

遥感图像分类

遥感图像分类 一、背景简介 遥感图像分类就是利用计算机通过对遥感图像中各类地物的光谱信息和空间信息进行分析&#xff0c;选择特征&#xff0c;将图像中各个像元按照某种规则或算法划分不同的类别&#xff0c;然后获得遥感图像中与实际地物的对应信息&#xff0c;从而实现…