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

2023-09-22 13:45:15

栈的结构及概念

栈是一种特殊的线性表,只允许在固定的一端插入或删除数据,进行插入和删除的一端被称为栈顶,另一端称为栈底。栈中的数据遵循后进先出原则
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,加油!

更多推荐

合肥对新通过(CMMI)五级、四级、三级认证的软件企业,对新通过信息技术服务标准(ITSS)认证的软件企业,给予最高50万奖励

合肥市加快软件产业发展推进软件名城创建若干政策实施细则为贯彻落实《合肥市人民政府办公室关于印发合肥市加快软件产业发展推进软件名城创建若干政策的通知》(合政办〔2023〕9号)文件精神,规范政策资金管理,制定本实施细则。一、申报主体在合肥市行政区域范围内注册成立、具有独立法人资格的软件企业或软件园区运营单位。本实施细则中

SpringMVC自定义注解

目录一,Java注解简介1.java注解的定义2.Java注解分类2.1JDK基本注解2.2JDK元注解2.3自定义注解二,自定义注解如何自定义注解?三,Aop自定义注解的应用四,总结一,Java注解简介1.java注解的定义Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配

网页游戏如何开发网页游戏类型有哪些?

随着互联网的普及和技术的发展,网页游戏已经成为娱乐和休闲活动的重要组成部分。无需安装任何应用程序,只需打开浏览器,您就可以畅玩各种类型的网页游戏。然而,开发网页游戏并不是一项容易的任务,因为不同类型的游戏需要不同的开发方式和技术。在本文中,我们将探讨一些常见的网页游戏类型以及它们的开发方式。1.休闲游戏开发休闲游戏通常

【B+树索引】索引的使用和注意事项

索引的使用和注意事项一、索引的注意事项根节点是不会变的!内节点中目录项记录的唯一性一个页面至少容纳两条记录二、回表的代价三、更好的使用索引四、索引的代价一、索引的注意事项上一篇【B+树索引】索引页的结构含有可以快速查询的秘密从索引页的角度认识了MySQL为了提升查询速率,使用了B+树的数据结构对索引页进行了内存存储。以

【2023华为杯B题】DFT类矩阵的整数分解逼近(思路及代码下载)

💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳️座右铭:行百里者,半于九十。📋📋📋本文目录如下:🎁🎁🎁目录💥1概述📚2详细数学模型及题目、数据🎉3参考文献🌈4Matlab代码及思路实现💥1概述离散傅里叶变换(Discre

爬虫逆向实战(34)-某视综数据(MD5、AES)

一、数据接口分析主页地址:某视综1、抓包通过抓包可以发现数据接口是/rank/waiting/fans2、判断是否有加密参数请求参数是否加密?通过查看“载荷”模块可以发现有一个sign参数请求头是否加密?无响应是否加密?通过查看“响应”模块可以发现数据是加密的cookie是否加密?无二、加密位置定位1、sign(1)看

c++八股day3-c++什么时候生成默认拷贝构造函数

背景:如果不提供,就是浅拷贝,即位拷贝(把值按字节复制过去)位拷贝的危害:1、比如某个类的对象当中有堆上的资源(里面有一个指针指向了堆上的资源)2、文件句柄、socket3、虚函数表指针可能会丢失。。。如果是位拷贝,就会出现两个对象持有相同的堆上资源、文件句柄如果有一个对象释放,释放时会把堆上资源进行释放,把文件句柄进

App测试中ios和Android有哪些区别呢?

App测试中,大家最常问到的问题就是:ios和Android有什么区别呢?在Android端,我们经常会使用JavaScript、HTML、CSS等技术来编写一些简单的UI界面。而iOS端,我们经常会使用到UI设计、界面布局、代码结构、API等技术来开发一款App。那究竟有什么区别呢?作为一名开发者,应该了解一些基础知

JVM——4.垃圾回收

这篇文章我没来讲一下JVM中的垃圾回收。这是比较重要,内容也比较多的一篇文章。目录1.垃圾回收概述2.如何判断对象可以回收2.1引用计数法2.2可达性分析算法2.2.1GCRoot的选取2.3再谈引用2.3.1强引用2.3.2软引用2.3.3弱引用2.3.4虚引用2.3.5终结器引用2.3.6引用小结3.垃圾回收算法3

uniapp——实现二维码生成+保存二维码图片——基础积累

最近在做二维码推广功能,自从2020年下半年到今天,大概有三年没有用过uniapp了,而且我之前用uniapp开发的程序还比较少,因此很多功能都浪费了很多时间去查资料,现在把功能记录一下。这里写目录标题效果图1.根据接口返回的链接生成二维码——`uv-Qrcode`的用法1.1插件市场导入`uv-qrcode`插件1.

优思学院|为什么六西格玛团队不能忽视DMAIC中的C?【案例分享】

在DMAIC(即Define(定义)、Measure(测量)、Analyze(分析)、Improve(改进)和Control(控制))过程中,控制阶段扮演着至关重要的角色,有助于维护六西格玛项目所带来的改进效益。如果按照正常程序执行,它还有助于进一步提高结果。什么是DMAIC过程?在深入探讨DMAIC控制阶段的重要性之

热文推荐