栈的简单实现及应用

news2024/11/24 18:55:30

栈的简单实现及其应用

  • 什么是栈?
  • 栈的分类
  • 栈的数据结构
  • 栈的基本操作
    • 栈的初始化
    • 栈的销毁
    • 入栈操作
    • 出栈和栈空的判断
    • 获取栈顶元素
    • 获取栈的元素个数
    • 头文件
  • 总结
  • 栈的应用

什么是栈?

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

做个简单的比喻:我们的就相当于一个弹夹,入栈操作就相当向弹夹之中压入子弹;出栈操作就相当将弹夹里面的子弹取出来;而这个压入子弹和取出子弹都只有一个口;这个口就是栈顶,出入数据都从栈顶进行;

压栈和出栈:
在这里插入图片描述

栈的分类

栈主要分为两类
1、顺序栈;(用顺序表实现的栈)
2、链式栈;(用链表实现的栈)

日常情况下,我们通常会选用顺序表来实现栈?
为什么?
因为如果我们将顺序表的尾作为栈顶的话,入栈(尾插)、出栈(尾删)效率很高就是O(1);
当然理论上我们也可以将顺序表的头作为栈顶,但是没人会这样高,因为入栈(头插)、出栈(头删)效率很低,时间复杂度是O(N^2)是一种事倍功半的操作;

那既然这样的话,我们为什么不选单链表的头做为栈顶,这样的话入栈(头插)、出栈(头删)效率也很高啊,时间复杂度也是O(1);
那么为什么不选它嘞?
我们目前来看是这样,但是具体实现起来,我们就得用一个栈顶指针来维护这个栈,那么就需要考虑很多特俗情况,同时参数这块也需要传二级指针,因为我的栈顶指针(链表头节点指针)是会变的;

而如果利用顺序表实现的话,就不会存在传二级指针的问题,也不需要考虑过多的特殊情况;
为此我们强烈推荐利用顺序表来实现栈!!!

栈的数据结构

为了维护这一个栈,我们利用一个结构体来维护栈;
在这里插入图片描述
这里我们只需与顺序表区分一下top指针,top指针是专门用来维护栈顶元素的;

栈的基本操作

栈的初始化

刚开是一定是空栈,这点毋庸置疑;
所以我们结构体里面的nums置空,容量capcity也应该置空;
但是这里的top指针有两种初始化方式
1、top=-1;
2、top=0;
这两种初始化方式对应着后面的操作有些不同;
如果top初始化为-1,那么我的栈顶元素是那个?
是不是就是nums[top],我的top每次都是指向上一次的空间的,为此我们每一次入栈,都要先将top++,在将数据放入top所指位置;为此我的top指针就指向最后一个空间(栈顶位置);
如果我的top指针初始化为0的话,我就不需要先++top了,直接在top位置插入数据就可以了,因为此时top位置就是待插入数据的位置,为此我们数据插入完毕过后需要++top,而此时的top表示的是栈顶元素的下一个位置top-1才表示栈顶元素的位置,这时的top也就相当于顺序表里面元素个数也就相当于顺序表里面的size;
本文采用top=0;的初始化方式:

//初始化栈
void InitStack(ST* ps)
{
	assert(ps);//防止乱传
	ps->capcity = 0;
	ps->top = 0;
	ps->nums = NULL;
}

栈的销毁

销毁与初始化操作通常都是连载一起的,我们在初始化操作实现完成过后便可直接实现销毁操作:

//销毁栈
void DestroyStack(ST*ps)
{
	assert(ps);
	ps->capcity = 0;
	ps->top = 0;
	free(ps->nums);
}

入栈操作

入栈操作对于顺序表来说就是尾插,既然是要插入数据,我们就要首先检查一下容量够不够,不免容量不够而出现插入失败的情况:
检查容量

//检查扩容,不提供给用户,由程序自己完成
static void Check_Capcity(ST* ps)
{
	assert(ps);
	if (ps->capcity == ps->top)//需要扩容
	{
		int len = (ps->capcity == 0) ? 4 : ps->capcity * 2;
		DataType* tmp = (DataType*)realloc(ps->nums,len*sizeof(DataType));
		if (!tmp)
		{
			printf("realloc fail!\n");
			exit(EXIT_FAILURE);
		}
		ps->nums = tmp;
		ps->capcity = len;
	}
}

当然这个检查容量的操作是由程序自己完成的,我们使用者是用不到的,为此我们可以将这个函数加以static关键字加以修饰,以此切断它的外部链接属性,不把它暴露给用户,加强其封装性!!!
容量问题现在解决了,接下来便是插入数据(入栈):

//入栈
void StackPush(ST* ps,DataType x)
{
	assert(ps);
	Check_Capcity(ps);
	ps->nums[ps->top] = x;
	ps->top++;
}

出栈和栈空的判断

当然出栈的操作,必然伴随着数据的删除,为此我们必须保证有数据可删才行!!数据都没了,那还删个der!为此我们需要先实现一下栈的判空
由于我么初始化的时候选择的是将top初始化为0,那么top的数据就是元素个数,为此top==0是便是栈为空,我们便返回真;栈不为空,便返回假:

//判断栈是否为NULL
bool StackEmpty(ST* ps)
{
	assert(ps);
	return !ps->top;
}

接下来我们保证了数据有的删,那我们直接移动top指针就行了,让计算机访问不到,不用真的删除并释放,同时free也不支持只释放不完整的空间;

//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//判空
	ps->top--;
}

获取栈顶元素

同理可得,我们获取元素,那也得要栈里面有元素才行,栈都为空,就没必要再去取元素了:
为此,我们首先要先判断一下栈是不是为NULL:

//获取栈顶元素
DataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈不为空,我们才有元素获取;
	return ps->nums[ps->top - 1];
}

获取栈的元素个数

我们初始化的时候top初始化的0,为此,我们top所表示的意义和元素个数等价,为此我们只需返回top即可:

//统计栈的元素
size_t StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

头文件

#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
typedef char DataType;
typedef struct Stack
{
	DataType* nums;
	int capcity;
	int top;
}ST;
//初始化栈
void InitStack(ST* ps);
//销毁栈
void DestroyStack(ST* ps);
//入栈
void StackPush(ST* ps,DataType x);
//出栈
void StackPop(ST*ps);
//判断栈是否为NULL
bool StackEmpty(ST* ps);
//统计栈的元素
size_t StackSize(ST* ps);
//获取栈顶元素个数
DataType StackTop(ST*ps);

总结

或许有的读者会疑惑,我们为什么要单独对栈顶元素和栈的元素个数专门写个函数呢?我们直接一句printf不就搞定了嘛。比如:我直接printf(“%d\n”,st.top);不就是栈的元素嘛,为啥还要专门写个函数;
我们现在是知道top是初始化为0,然后top就直接就表示元素个数,那如果我初始化top为-1,那么此时我的top就不再等价表示顺序表元素个数了,top+1才是表示元素个数,这也就意味着我需要去查看源码到底是怎么初始化top的,很是麻烦,而且难免有时候看错😊😊😊,而我们直接使用函数接口去求栈的元素个数,则不需要关心top是怎么初始化的,直接调就行了,很是方便,也不会出错!!就问这两种方式你更青睐与哪一种,我比较喜欢第二种!!😊😊😊

栈的应用

上面介绍了什么是栈,同时也介绍了栈的基本操作,下面我们就来做上一道题,感受一下栈的威力!!
题目描述:
在这里插入图片描述
➡️挑战链接⬅️

分析:
思路:
我们可以利用栈先进后出的特点:
遇到左括号就进栈,遇到右括号就获取栈顶元素,并且出栈;
举个例子:
在这里插入图片描述
为此我们来写我们的第一次代码:
由于C语言没有栈,所以我们需要将我们写的栈copy过去,才行:
在这里插入图片描述

在这里插入图片描述
运行失败!!
通过测试用例,我们在走读代码,发现当只有左括号的时候,就会出现一直进栈,没有出栈的操作,自然也就不会走到false位置,最后直接就会来到treu位置,为此我们通过测试用例可以相当,如果全部能够匹配成功的话,那么我的栈最后一定是没有剩余元素的,如果有,那么说明一定还存在左括号没有匹配成功;为此我们的代码改进如下:
在这里插入图片描述
再次运行:
在这里插入图片描述
还是失败!!
这次他给我们报的测试用例是全是右括号的情况,假设全是右括号,按照上面的逻辑,我就需要去进行出栈操作,可是由于全是右括号,没有左括号给我们入栈,栈里面自然也就没有元素给我们出,这种情况就是我遇到了右括号,但是没有左括号与我匹配,我直接就返回false;
代码改进:
在这里插入图片描述

这次代码就过了:
在这里插入图片描述
时间复杂度:O(N)
空间复杂度:O(N)

具体代码:

typedef char DataType;
struct ListNodes
{
	DataType val;
	struct ListNodes* next;
};
typedef struct Stack
{
	int size;
	struct ListNodes* Head;
}ST;
//初始化栈
void InitStack(ST*ps);
//销毁栈
void DestroyStack(ST* ps);
//入栈
void StackPush(ST*ps,DataType x);
//出栈
void StackPop(ST*ps);
//统计栈里元素个数
size_t StackSize(ST*ps);
//判断栈是否为NULL
bool StackEmpty(ST* ps);
//获取栈顶元素
DataType StackTop(ST* ps);
//初始化栈
void InitStack(ST* ps)
{
	assert(ps);
	ps->Head = NULL;
	ps->size = 0;
}
//销毁栈
void DestroyStack(ST* ps)
{
	assert(ps);
	struct ListNodes* cur = ps->Head;
	while (cur)
	{
		struct ListNodes* next = cur->next;
		free(cur);
		cur = next;
	}
	ps->Head = NULL;
	ps->size = 0;
}
//创建节点,程序自动创建,用户无需关心
static struct ListNodes* BuyListNode(DataType x)
{
	struct ListNodes* NewNode = (struct ListNodes*)malloc(sizeof(struct ListNodes));
	if (NewNode == NULL)
	{
		printf("malloc fail!\n");
		exit(EXIT_FAILURE);
	}
	NewNode->next = NULL;
	NewNode->val = x;
	return NewNode;
}
//入栈
void StackPush(ST* ps, DataType x)
{
	assert(ps);
	struct ListNodes* NewNode = BuyListNode(x);
	struct ListNodes* next = ps->Head;
	NewNode->next = next;
	ps->Head = NewNode;
	ps->size++;
}
//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	struct ListNodes* next = ps->Head->next;
	free(ps->Head);
	ps->size--;
	ps->Head = next;
}
//判断栈是否为NULL
bool StackEmpty(ST* ps)
{
	assert(ps);
	return ps->size == 0;
}
//统计栈里元素个数
size_t StackSize(ST* ps)
{
	assert(ps);
	return ps->size;
}
//获取栈顶元素
DataType StackTop(ST* ps)
{
	assert(ps);
	assert(StackEmpty(ps)==false);
	return ps->Head->val;
}


bool isValid(char* s) {
    ST st;
    InitStack(&st);
    while (*s)
    {
        //1、左括号进栈
        if ((*s == '[') || (*s == '(') || (*s == '{'))
        {
            StackPush(&st, *s);
            s++;
        }
        else//2、右括号直接出栈
        {
            if (StackEmpty(&st))//表示栈里还没有元素,但是我的s指向右括号,无法与我的右括号匹配 
            {
                DestroyStack(&st);
                return false;
            }
            char top = StackTop(&st);
            StackPop(&st);
            //3、开始比较右括号与左括号匹不匹配
            //匹配成功
            if (((top == '[') && (*s == ']')) || ((top == '{') && (*s == '}')) || ((top == '(') && (*s == ')')))
                s++;
            else//匹配失败
            {
                printf("top==%c s==%c\n", top, *s);
                DestroyStack(&st);//先销毁一下栈,在返回避免造成内存泄漏
                return false;
            }
        }
    }
    if (StackEmpty(&st) == false)//栈不为空,表示里面还有左括号为匹配成功,直接返回false;
    {
        DestroyStack(&st);
        return false;
    }
    DestroyStack(&st);
    return true;
}

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

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

相关文章

【毕业设计】垃圾邮件(短信)分类算法研究与实现 - 机器学习

文章目录1 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后1 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#…

Vue面试题-答案、例子

1、Vue的生命周期 每一个vue实例从创建到销毁的过程&#xff0c;就是这个vue实例的生命周期。在这个过程中&#xff0c;他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。 将要创建 >调用beforeCreate函数 创建完毕 >调用creat…

振弦采集模块复位( 重启)及恢复出厂设置

振弦采集模块复位&#xff08; 重启&#xff09;及恢复出厂设置 以下几种情况&#xff08;或操作&#xff09;可使模块产生复位动作&#xff0c;重新启动。 &#xff08; 1&#xff09; 在模块正常工作期间&#xff0c;向寄存器 SYS_FUN 发送软复位指令 0x01&#xff1b; &…

74ls192无法正常使用。

分析与解决74ls192芯片无法在proteus中正常运行 博主最近要做电子技术课程设计&#xff0c;于是重新拾起了长久不用的proteus。在构建倒计时电路时&#xff0c;发现了一个问题&#xff1a; 74ls192芯片&#xff0c;在软件提供的时钟信号下能正常开启计时。但是在自己使用的55…

从理论到实践:MySQL性能优化和高可用架构,一次讲清

数据库系统作为IT业务系统的核心&#xff0c;其高可用性和容灾能力对整个业务系统的连续性和数据完整性起着至关重要的作用&#xff0c;是企业正常运营的基石 尤其是在性能优化与高可用架构两方面&#xff0c;很多从业多年的DBA限于生产环境的固定体系&#xff0c;往往盲人摸象…

Grafana-web使用说明

本文分别记录了&#xff1a; Grafana使用步骤P50 P99 min max m1_rate等指标分别是什么意思&#xff0c;Metrics为何不会对“吞吐量”指标记录P99 min 等聚合Metrics常用的几种记录方式&#xff08;我司用了两种&#xff09; 1.场景 Metrics收集日志交给Graphite&#xff08;…

第九节:类和对象【三】【static、代码块、对象的打印】

目录 &#x1f947;1.什么是封装 &#x1f4d8;1.1封装的实现 &#x1f392;2.static成员 &#x1f4d2;2.1 再谈学生类 ​编辑 &#x1f4d7;2.2 static修饰成员变量 2.3 static修饰成员方法 &#x1f4d5;2.4 static成员变量初始化 &#x1f532;3. 代码块 &#x…

第四届全国中医药院校大学生程序设计竞赛 : 二进制码(Python)

文章目录题目描述输入输出样例输入 Copy样例输出 Copy代码测试题目描述 在计算机中&#xff0c;对于定点数有三种不同的表示方法。在本题中&#xff0c;假定码的长度固定为 8 位&#xff0c;从左往右依次编号为第 1 到 8 位&#xff0c;第 1 位为最高位。 x 的原码&#xff1a…

Python爬虫实战(七):某讯较真辟谣小程序爬虫

追风赶月莫停留&#xff0c;平芜尽处是春山。 文章目录追风赶月莫停留&#xff0c;平芜尽处是春山。一、准备工作二、目标分析二、接口分析url分析返回数据分析三、编写代码获取数据保存数据完整代码大四考研狗没时间更新博客了&#xff0c;大家勿怪&#xff0c;等我有学上了&a…

手把手带你搭建个人博客系统(二)

⭐️前言⭐️ 因文章篇幅较长&#xff0c;所以整个流程分两篇文章来完成。 &#x1f349;博客主页&#xff1a; &#x1f341;【如风暖阳】&#x1f341; &#x1f349;精品Java专栏【JavaSE】、【备战蓝桥】、【JavaEE初阶】、【MySQL】、【数据结构】 &#x1f349;欢迎点赞…

Matplotlib设置限制制作

Matplotlib自动到达要沿着图的x&#xff0c;y(以及3D图的情况下为z轴)轴显示的变量的最小值和最大值。但是&#xff0c;可以使用set_xlim()和set_ylim()函数显式设置限制。 在下图中&#xff0c;显示了x和y轴的自动缩放限制 - #! /usr/bin/env python #codingutf-8 import matp…

【关于Linux中----进程控制和进程替换】

文章目录一、进程创建二、进程终止2.1进程退出场景2.2进程退出方法三、进程等待3.1进程等待必要性3.2进程等待的方法3.3获取子进程status四、进程程序替换4.1替换原理4.2替换函数4.3命名理解五、总结一、进程创建 谈到创建进程&#xff0c;不得不提到一个函数----fork。 在li…

【Python】一个矩阵根据某一列选择大于或小于范围的数据

data_all data_all[data_all[:,3]>54201]data_all data_all[data_all[:, 3] < 54220] 上面就是根据数据的第3列&#xff0c;选取54201到54220的范围的数据&#xff1a;

单片机最小系统

单片机最小系统,或者称为最小应用系统,是指用最少的元件组成的单片机可以工作的系统. 对51系列单片机来说,最小系统一般应该包括:单片机、晶振电路、复位电路. 下面给出一个51单片机的最小系统电路图. 晶振电路&#xff1a; 单片机里都有晶振&#xff0c;在单片机系统里晶振作用…

2013年第四届C/C++ A组蓝桥杯省赛真题+解析+代码

目录 第一题&#xff1a;高斯日记 题目描述 思路分析 AC代码 第二题&#xff1a;排它平方数 题目描述 思路分析 AC代码 第三题&#xff1a;振兴中华 题目描述 思路分析 AC代码 第四题&#xff1a;颠倒的价牌 题目描述 思路分析 AC代码 第五题&#xff1a;前缀…

jsp就业管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 就业管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,Myeclipse8.5开 发&#xff0c;数据库为Mysql&#xff0c;使用ja…

蓝桥杯备赛(三)

目录 前言&#xff1a; 一、门牌制作 解析&#xff1a; 代码实现 二、寻找2020 解析&#xff1a; 代码实现 三、蛇形填数 解析&#xff1a; 代码实现 四、成绩分析 解析&#xff1a; 代码实现 五、单词分析 解析&#xff1a; 代码实现 小结&#xff1a; 前言&am…

热门Java开发工具IDEA入门指南——了解并学习IDE

IntelliJ IDEA&#xff0c;是java编程语言开发的集成环境。IntelliJ在业界被公认为最好的java开发工具&#xff0c;尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。 本…

面试了1个月连续失败4次,自动化测试真没想象的那么简单

我干测试6年了&#xff0c;最近面试又碰壁了… 这大概是我这一个月来第4次面试失败了&#xff0c;起初我投简历比较勇猛&#xff0c;奔着薪资高的有点儿名气的企业就开始海投&#xff0c;碰上了2家还不错的邀约面试&#xff0c;前面交流还行&#xff0c;一问到自动化测试就傻眼…

怎么进行视频恢复?推荐使用这4种方法

电脑视频怎么恢复&#xff1f;很多朋友在使用电脑的过程中&#xff0c;如果系统或者是存储文件出现问题的话&#xff0c;可能会出现视频丢失的情况。因为在使用电脑运行视频软件时&#xff0c;系统或者存储文件存在一些质量问题从而导致视频丢失。那么想要进行视频恢复&#xf…