带你一步实现《栈》(括号匹配问题)

news2025/1/16 9:14:21

栈的结构及概念

栈是一种特殊的线性表,只允许在固定的一端插入或删除数据,进行插入和删除的一端被称为栈顶,另一端称为栈底。栈中的数据遵循后进先出原则
LIFO(LAST IN FIRST OUT)

  • 俗称栈的插入过程叫做压栈,入栈,从栈顶入数据
  • 出栈就是栈的删除,出数据也在栈顶哦,不然怎么做到后进先出原则。
    来看一个动态图理解入栈出栈的过程。
    在这里插入图片描述
    接下来首先要进行分析,由于是后进先出,如果用链表来实现的话,相比数组的尾删和尾插,用链表实现更加麻烦一点,如果用数组来实现,尾删只需要size减一,如果需要入栈的话只需要在末尾的位置添加这个数就可以了。

一步一步来实现他

//静态的
//#define N 10
//struct stack
//{
//	int  a[N];
//	int top;
//};

typedef int TYPE;
typedef struct Stack
{
	TYPE* a; 	
	TYPE top;
	int capacity;
}ST;

如果还是用静态的数组就太低端了,而且还会遇到容量不够的问题,因为要储存的值的类型有可能会发生变化,所以可以将变量类型定义一下,需要修改时不至于要修改很多地方。

  • 定义一个数组,top是栈内储存的数据的量,capacity是数组的容量,如果存储的数据的量等于容量就要用malloc函数进行扩容,大体思路就是如此了。

进入正题

  • 仍然利用分文件的形式来实现,以下栈的功能全部放在STACK.c中
    初始化函数
void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

判断创建的结构体指针变量是否为空assert
将ps指向的数组先置空,然后将容量capacity设置为0,数据量设置为0。
判空函数

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

还是要检查ps是否有问题,返回一个bool变量,判断是false还是true,如果等于0,就返回true,说明栈内为空。
求栈内元素个数

int STSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

这个很简单,只需要返回top的值即可。
删除元素
这个很很简单相较于用链表实现

void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	ps->top--;
}

即可,当然如果ps指向的top已经是0了就不能再删除了。
添加元素

void STPush(ST* ps, TYPE x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		//ps->capacity *= 2;//当capacity等于零时不好用
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		TYPE* tmp = (TYPE*)realloc(ps->a, sizeof(TYPE) * newcapacity);//判断是否扩容成功
		if (tmp == NULL)
		{
			perror("realloc fail");
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}
  • 传入一个x,首先来判断容量是否为零,如果容量为零的话,那就申请4个空间的内存,然后先存放数据,存放完数据会使数据量top加一,如果top和数据的容量相等时,那就扩充容量为原来的二倍。将x放进该位置,入栈的过程就结束啦。
    销毁栈
void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}

判断必不可少,因为内存是malloc过来的,要用free函数将其释放掉,然后恢复到初始化函数时的状态。将容量还有数量全部还原为零。
获取栈顶元素

TYPE STTop(ST* ps) 
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

判断栈内到底有没有元素,这是很重要的,不然会越界访问,然后返回入栈的数量减一下标的元素即可,为什么减一呢,第一个元素,下标为零,懂了吧!
别忘记在STACK.h中声明这几个函数

void STInit(ST* ps);
void STDestroy(ST* ps);
//入栈
void STPush(ST* ps,TYPE x);
//出栈
void STPop(ST* ps);

int STSize(ST* ps);

bool STEmpty(ST* ps);

//获取栈顶元素
TYPE STTop(ST* ps);

来测试一波
test.c

#define _CRT_SECURE_NO_WARNINGS
#include "stack.h"
#include <stdbool.h>
Test1()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPush(&st, 4);
	STPush(&st, 5);

	while (!STEmpty(&st))
	{
		printf("%d ", STTop(&st));
		STPop(&st);
	}
	printf("\n");
	STDestroy(&st);
}

运行后如图
加粗样式
如果有想测验一下的,下边就是代码

Test1()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPush(&st, 2);
	STPush(&st, 3);
	STPush(&st, 4);
	STPush(&st, 5);

	while (!STEmpty(&st))
	{
		printf("%d ", STTop(&st));
		STPop(&st);
	}
	printf("\n");
	STDestroy(&st);
}

int main()
{
	Test1();
	return 0;
}

学习完了栈的定义及实现,上一个重头戏
题目:括号的匹配
在这里插入图片描述
这道题实现起来很不简单,如果没有学习栈相关的知识,我们在判断时会遇到很多阻力,这道题也可以叫做括号的匹配。例如

  • ( { } )这组括号就是可以匹配的。
  • ( [ ] ) { } ( )还有像这种。
  • ( [ ( ) } ) 然而这组中明显匹配有问题,然而我们怎么来找呢?
    如果用字符数组的话,会有很多情况需要判断,而且很容易出错,现在我们来尝试利用栈的特性来解决这一问题。

分析题目要求,仔细分析可以发现,在利用括号开始进行匹配时,如果是右括号(统称)那就入栈,如果是左括号,那就从栈中取出一个元素与其相匹配,如果匹配成功,那就继续向后匹配,如果匹配失败,那就直接返回,并标明匹配错误。
拿出一个测试用例进行解释
在这里插入图片描述
接下来解决逻辑问题
首先,因为我们需要用到栈,可以将上边所讲的栈的实现函数全部复制粘贴进去,然后利用栈的特性来实现大体逻辑。
代码如下

bool isValid(char * s){
    ST st;
    STInit(&st);
    char top;
    while(*s)
    {
        if((*s=='[')||(*s=='(')||(*s=='{'))
        {
            STPush(&st,*s);           
        }
        if(*s==')')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='(')
            {
                return false;
                STDestroy(&st);
            }
        }
        if(*s=='}')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='{')
            {
                return false;
                STDestroy(&st);
            }
        }
        if(*s==']')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='[')
            {
                return false;
                STDestroy(&st);
            }
        }        
        s++;
    }
    bool ret=STEmpty(&st);
    STDestroy(&st);//销毁
    return ret;
}

创建一个结构体指针变量,这里要注意,上边所写的默认是整型数组,将设置的TYPE改成char类型即可。
在循环体中,我们可以选择使用switch case语句,也可以用if else 语句,代码逻辑十分清晰,在遍历数组的过程中,如果是右括号,则直接入栈,如果是左括号,就出栈顶元素与其相匹配,匹配成功,则走到循环体最后s++的位置,进行下一次判断。

也要注意数组是否为空,可以使用判空函数,如果为空就返回true,如果不为空就返回false,如果是空的话,相当于经历循环体后已经没有左括号了,但是如果不为空的话,那就相当于数组中还剩下一些括号,这些括号是没有被匹配的。

例如

{}()[]{{{

经历过循环后,前边的三组已经匹配完成了,然而后边还有三个右括号入栈,这三个括号没有匹配,所以用判空函数判断一下,如果循环之后栈不为空的话,那就说明匹配不成功,返回false。
需要注意的是取出栈顶元素后将栈顶元素删除。
全部代码如下


typedef char STDataType;
typedef struct Stack
{
	STDataType* a;
	STDataType top;
	int capacity;
}ST;
void STInit(ST* ps)
{
	assert(ps);
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void STDestroy(ST* ps)
{
	assert(ps);

	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	// 11:40
	if (ps->top == ps->capacity)
	{
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}

		ps->a = tmp;
		ps->capacity = newCapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}
void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	--ps->top;
}
STDataType STTop(ST* ps)
{
	assert(ps);

	// 
	assert(ps->top > 0);

	return ps->a[ps->top - 1];
}
int STSize(ST* ps)
{
	assert(ps);

	return ps->top;
}

bool STEmpty(ST* ps)
{
	assert(ps);

	return ps->top == 0;
}
// bool isValid(char * s)
// {

// }

bool isValid(char * s){
    ST st;
    STInit(&st);
    char top;
    while(*s)
    {
        if((*s=='[')||(*s=='(')||(*s=='{'))
        {
            STPush(&st,*s);           
        }
        if(*s==')')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='(')
            {
                return false;
                STDestroy(&st);
            }
        }
        if(*s=='}')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='{')
            {
                return false;
                STDestroy(&st);
            }
        }
        if(*s==']')
        {
            if(STEmpty(&st))
            {
                return false;
            }
            top=STTop(&st);
            STPop(&st);
            if(top!='[')
            {
                return false;
                STDestroy(&st);
            }
        }        
        s++;
    }
    bool ret=STEmpty(&st);
    STDestroy(&st);//销毁
    return ret;
}

7777777,加油!

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

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

相关文章

Batbot智慧能源管理云平台:拥抱数字化,提高能源效率!

我们拥抱数字化&#xff0c;以帮助提高能源效率。 政府已采取措施增强国家的环境信誉&#xff0c;旨在实现雄心勃勃的法定目标&#xff0c;即到2035年&#xff0c;将国家温室气体排放量减少78%&#xff08;与1990年相比&#xff09;。 拥抱数字化&#xff0c;提高能源效率&a…

HTTP 协商缓存 Last-Modified,If-Modified-Since

浏览器第一次跟服务器请求一个资源&#xff0c;服务器在返回这个资源的同时&#xff0c;在respone header加上Last-Modified属性&#xff08;表示这个资源在服务器上的最后修改时间&#xff09;&#xff1a; ----------------------------------------------------------------…

ThinkPHP5,使用unionAll取出两个毫无相关字段表的数据且分页

一&#xff1a;首先来了解一下 union 和 unionAll 1&#xff1a;取结果的并集&#xff0c;是否去重 union&#xff1a;对两个结果集进行并集操作&#xff0c;不包括重复行&#xff0c;相当于distinct&#xff0c;同时进行默认规则的排序&#xff1b; unionAll&#xff1a;对两…

JVM面试题-JVM对象的创建过程、内存分配、内存布局、访问定位等问题详解

对象 内存分配的两种方式 指针碰撞 适用场合&#xff1a;堆内存规整&#xff08;即没有内存碎片&#xff09;的情况下。 原理&#xff1a;用过的内存全部整合到一边&#xff0c;没有用过的内存放在另一边&#xff0c;中间有一个分界指针&#xff0c;只需要向着没用过的内存…

【QT】QRadioButton的使用(17)

QRadioButton这个控件在实际项目中多用于多个QRadioButton控件选择其中一个这样的方式去执行&#xff0c;那么&#xff0c;今天这节就通过几个简单的例子来好好了解下QRadioButton的一个使用。 一.环境配置 1.python 3.7.8 可直接进入官网下载安装&#xff1a;Download Pyt…

PIL或Pillow学习2

接着学习下Pillow常用方法&#xff1a; PIL_test1.py : 9, Pillow图像降噪处理由于成像设备、传输媒介等因素的影响&#xff0c;图像总会或多或少的存在一些不必要的干扰信息&#xff0c;我们将这些干扰信息统称为“噪声”&#xff0c; 比如数字图像中常见的“椒盐噪声”&…

聊一聊Twitter的雪花算法

什么是Twitter的雪花算法方法&#xff1f; 这是一种在分布式系统中生成唯一ID的解决方案。Twitter在推文、私信、列表等方面使用这种方法。 •ID是唯一且可排序的•ID包含时间信息&#xff08;按日期排序&#xff09;•ID适用于64位无符号整数•仅包含数字值 符号位&#xff08…

芋道商城,基于 Vue + Uniapp 实现,支持分销、拼团、砍价、秒杀、优惠券、积分、会员等级、小程序直播、页面 DIY 等功能

商城简介 芋道商城&#xff0c;基于 芋道开发平台 构建&#xff0c;以开发者为中心&#xff0c;打造中国第一流的 Java 开源商城系统&#xff0c;全部开源&#xff0c;个人与企业可 100% 免费使用。 有任何问题&#xff0c;或者想要的功能&#xff0c;可以在 Issues 中提给艿艿…

【从0学习Solidity】 10. 控制流,用solidity实现插入排序

【从0学习Solidity】10. 控制流&#xff0c;用solidity实现插入排序 博主简介&#xff1a;不写代码没饭吃&#xff0c;一名全栈领域的创作者&#xff0c;专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构&#xff0c;分享一些项目实战经验以及前沿技术的见解。关…

python中的NaN在质量控制中怎么处理?

一、数据中的缺省值 气象数据中经常存在缺省值&#xff0c;比如未入库的站点数据、比如海温格点实况数据中的陆地区域。这些缺省值往往被赋予NaN&#xff08;Not a Number&#xff0c;非数&#xff09;。NaN是计算机科学中数值数据类型的一类值&#xff0c;表示未定义或不可表…

远程端点管理和安全性

当今的企业网络环境是一个分布式动态环境&#xff0c;其中有许多需要管理、验证和保护的移动部件&#xff0c;而不会对最终用户的生产力产生任何威慑力。提供有效的端点管理安全性&#xff0c;同时仍提供无缝最终用户体验的解决方案至关重要。 Endpoint Central 执行的活动可确…

Linux高性能服务器编程 学习笔记 第六章 高级IO函数

pipe函数用于创建一个管道&#xff0c;以实现进程间通信&#xff1a; fd参数是一个包含两个int的数组。该函数成功时返回0&#xff0c;并将一对打开的文件描述符填入其参数指向的数组&#xff0c;如果失败&#xff0c;则返回-1并设置errno。 pipe函数创建的这两个文件描述符f…

用Python爬取短视频列表

短视频是一款备受欢迎的短视频分享平台&#xff0c;每天都有大量精彩的视频内容等待我们去探索。在本文中&#xff0c;我们将分享如何使用Python爬取短视频的视频列表&#xff0c;让您能够发现更多有趣的视频。 一、安装必要的库 在开始之前&#xff0c;确保已安装以下库&…

Unity——对象池

对象池是一种朴素的优化思想。在遇到需要大量创建和销毁同类物体的情景时&#xff0c;可以考虑使用对象池技术优化游戏性能。 一、为什么要使用对象池 在很多类型的游戏中都会创建和销毁大量同样类型的物体。例如&#xff0c;飞行射击游戏中有大量子弹&#xff0c;某些动作游戏…

java用easyexcel按模版导出

首先在项目的resources下面建一个template包&#xff0c;之后在下面创建一个模版&#xff0c;模版格式如下&#xff1a; 名称为 financeReportBillStandardTemplateExcel.xlsx&#xff1a; {.fee}类型的属性值&#xff0c;是下面实体类的属性&#xff0c;要注意这里面的格式&a…

iPhone15拉胯,国产手机用折叠屏大反攻!

文 | 智能相对论 作者 | K星 iPhone偷懒式的15代机型发布&#xff0c;让市场大跌眼镜&#xff0c;虽然在莫名的宗教虔诚下依旧卖得很好&#xff0c;但苹果走下神坛之巅已经板上钉钉。 但是&#xff0c;苹果从最高处摔落&#xff0c;却依旧在神坛之上&#xff0c;iPhone15拉胯…

【STM32笔记】HAL库定时器捕获配置、操作及通用函数定义

【STM32笔记】HAL库定时器捕获配置、操作及通用函数定义 文章目录 定时器捕获设置输入捕获滤波器设置输入捕获极性设置输入捕获映射关系设置输入捕获分频器 定时器配置定时器捕获函数全局变量定时器回调和定时器捕获回调频率计算 附录&#xff1a;Cortex-M架构的SysTick系统定…

css自学框架之二级下拉菜单

下拉菜单是我们开发中最常见&#xff0c;最常用的&#xff0c;今天我们就自学二级下来菜单。首先看一下最终效果&#xff1a; 一、css代码 .arrow-down::before {content: ""; width: 10px;height: 10px;border: solid ;border-width: 2px 2px 0 0;border-color: …

BIGEMAP在土地规划中的应用

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 1.使用软件一般都用于套坐标&#xff0c;比如我们常见的&#xff08;kml shp CAD等土建规划图纸&#xff09;以及一些项目厂区红线&#xff0c;方便于项目选址和居民建…

软考复习 -- 计算机网络

1 网络互连设备 物理层&#xff1a;中继器和集线器&#xff08;多路中继器&#xff09;数据链路层&#xff1a;网桥和交换机&#xff08;多端口网桥&#xff09;网络层&#xff1a;路由器应用层&#xff1a;网关 2 广播域和冲突域 3 协议簇 4 网际层协议 4 TCP和UDP 4.1 TC…