数据结构栈的实现及经典OJ一道

目录

什么是栈?

栈的实现方式

栈的实现

初始化

判空

大小

入栈

出栈

获取栈顶元素

销毁栈

 测试

总结

OJ有效——括号匹配问题

栈的拷贝

代码实现


什么是栈?

栈是一种数据结构,它的特点是先进后出(First In Last Out)。数据结构的作用简单来讲就是组织数据的方式,这里所谓的先进后出,说的就是数据在栈中进出的特点。

玩栈,你首先的明白几个概念:

栈顶:栈顶是栈唯的一个“开口”,入栈、出栈操作只能在栈顶进行。

入栈:把元素从栈顶放到栈里面。

出栈:把栈顶的元素从栈里拿出。

配个图,更好理解一点:

栈的实现方式

栈的实现方式有两种:

数组实现

数组实现的方法,就是用一个数组来模拟栈,我们需要一个top来标记栈顶元素的位置(或者栈顶元素的下一个位置),通过这样的方式来实现栈的功能。

链表实现

链表实现(以单链表为例)需要注意,你可以把链表的头部作为栈顶,因为单链表的头插是方便的,尾插需要找尾的前一个,需要遍历是不方便的。

 当然你可以可以采取增加一个变量的方法,用一个ptail指针指向尾部,把尾作为栈顶,也是完全可以的,如下图所示。

总之,两种实现方法都是可以的,但实现过程需要注意栈各项功能的要求。


栈的实现

我选择使用数组的方式实现栈。

栈对应的接口如下(C语言实现)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

//对int 类型的重命名,方面以后修改栈存的元素
typedef  int STDataType;

//用一个结构体把栈的相关参数放在一起
 typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;

} ST;

 void STInit(ST *pst);//初始化
 void STPush(ST* pst, STDataType x);//入栈
 void STPop(ST* pst);//出栈
 STDataType STTop(ST* pst);//获取栈顶元素
 bool STEmpty(ST* pst);//判断栈是否为空
 int STSize(ST* pst);//获取栈的元素个数
 void STDestory(ST *pst);//销毁栈

我们的实现方法是,创建一个结构体,就相当于创建了一个栈,我们通过修改结构体里面的元素,来模拟栈的功能。

 那么接下来,我们对接口,进行分析。

初始化

void STInit(ST* pst) {

	pst->a = NULL;
	pst->top = 0;//栈顶元素的下一个
	pst->capacity = 0;

}

初始化很简单,把我们栈的相关参数给一个初始值。

这里重点说一下,top为什么给0。

前面提到了,top可以标识栈顶元素的下标,也可以标识栈顶元素下一个位置的下标,那么这两者的区别就是字面意思,那么为什么这里,栈里面还没有元素,要给零呢?

其实可以这样考虑,栈为空是一个特殊的状态,不管你选择那个标识方法,栈为空的这个特殊状态你都得唯一标识

假定,我们top标识栈顶元素的下一个位置,那么给零,如果栈里面进了一个元素,top++变成了1,是不是刚好就是栈顶元素的下一个位置。

假定,我们top表示的是栈顶元素,那么你说为空时,给0合适吗?明显不合适,得给-1才行,这样你插入元素后top++,top指向了栈顶元素。

所以,要注意这个细节,这里我选择的是标识栈顶元素的下一个位置。

capacity显示的是数组的容量。


判空

判空就是看看栈内有没有元素,返回一个bool值。

bool STEmpty(ST* pst) {

	assert(pst);

	return pst->top == 0;
}

前面提到了,我们top标记的时栈顶的下一个位置的元素,栈为空即top为0,那么这里返回

pst->top == 0这个表达式的结果。

如果top为零,那么表达式结果为true,栈为空。

如果top不为零,表达式结果为false,栈不为空。

(这里用的assert就是用来检查参数的值是否不满足要求(一般判断不为空)。

比如这里pst是不能为空的,因为栈得存在,如果pst是NULL或false(在C语言里面NULL、false和0是等价的)。

如果为空,程序会直接死掉,并且报出这一行的错误,下面用到的这个断言都是这个功能。)

如下图所示:


大小

计算栈内有多少个元素。

int STSize(ST* pst) {
	assert(pst);

	return pst->top;
}

我们的栈是用数组来实现的,top指向的是栈顶元素的下一个元素。

比如栈内有五个元素,下标从0到4,top的值是5,刚好是栈内元素的个数,栈为空也是没问题的,返回的就是0。


入栈

入栈操作就是向栈内从栈顶,入一个元素。

void STPush(ST* pst, STDataType x) {

	//判断是否需要增容
	if (pst->top == pst->capacity) {
		int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL) {
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newCapacity;
	}

    //添加数据
	pst->a[pst->top] = x;
	pst->top++;
}

由于我们的栈是用数组来模拟的,所以在插入数据的时候需要判断是否需要增容。

这里capacity这个参数就需要用上了,那么什么情况需要增容呢?需要注意什么呢?

假设我们的capacity是6,也就是栈内目前最多放6个元素,假定我们的栈满了,那么最后一个元素的下标是不是应该是5,那么此时top的值是不是应该是6,在数值上是和capacity相等

如图所示:

这个状态就是栈满的状态,还需要插入数据,就需要增容了。

对于数组数组,增容就用realloc,一般扩容为原来的两倍。注意,这里有个魔鬼细节,经过初始化后,我们的容量时0,这时候你应该开辟一段空间,此时realloc功能就和malloc一样了。

所以出现了这句代码:

int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;

判断capacity是否为零,为零就给赋值为4,下面就会栈开辟四个元素的空间,不为零,就变为原来的二倍,下面正常扩容。

之后就正常的把数据放入栈中,然后不要忘了让top++。


出栈

出栈操作更简单,操纵top即可。

void STPop(ST* pst) {

	//为空不能删
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}

出栈的前提是栈得有元素,所以咱先判断是否为空,这里如果为空,STEmpty返回值是 true,用 !取反后,值就是flase,就会报错啦。

然后,如果不为空,这里删除,我们就让top减减。

注意这里的删除,并不是真真正正的把数据从数组中移除了,只是在逻辑上,我们把top--,这个元素就不属于我们的栈了。

但这个数据在内存中还是占着内存的,这块内存还是属于我们数组的,下一次插入数据,可以直接覆盖掉。

如图所示:


获取栈顶元素

只返回栈顶元素,不修改站内的元素。

STDataType STTop(ST* pst) {

	assert(pst);
	assert(!STEmpty(pst));
	return  pst->a[pst->top - 1];
}

同样的,栈要存在且不为空才能得到栈顶元素。

top记录的是栈顶元素下一个位置的下标,那么top-1就是栈顶元素的下标,直接通过这个下标,访问数组的元素,返回即可。


销毁栈

释放内存,变量置空。

void STDestory(ST* pst) {

	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;

}

这个就很简单了,malloc开辟,free释放,指针置空,变量设为零。


 测试

接下来,通过简单的代码来测试一下我们的栈,是否能正常使用。

void test01()
{
	//创建栈,即创建了一个结构体
	ST  st;

	//初始化
	STInit(&st);
	//入栈 10 20 30 40
	STPush(&st, 10);
	STPush(&st, 20);
	STPush(&st, 30);
	STPush(&st, 40);
	//获得栈元素个数
	int sz = STSize(&st);
	printf("栈内元素个数为:>%d\n", sz);//4
	//获得栈顶元素
	int tmp = STTop(&st);
	printf("栈顶元素为:>%d\n", tmp);//40
	//元素以此出栈,顺序应为40 30 20 10 满足先进后出
	printf("出栈:>");
	while (!STEmpty(&st))
	{
		tmp = STTop(&st);
		printf("%d  ", tmp);
		STPop(&st);
	}
	//销毁栈
	STDestory(&st);
}
int main()
{

	test01();
	return 0;
}

结果如下:

从测试结果看,我们栈的实现是成功的!!!


总结

栈的实现要注意以下几点:

  • 栈的的特点是先进后出
  • 栈的实现方式可以用数组、链表
  • 栈的功能有push、pop、top、size、empty、destory


你以为到这里就结束了吗?开玩笑,看我标题,来用我们写的栈做一道题吧!!

OJ有效——括号匹配问题

题目链接在这里:

20. Valid Parentheses - 力扣(Leetcode)

题目描述:

题目概述:

就是有一个字符串,只有'( )[ ] { }'这些括号组成,让我们判断它给的字符串中括号是否是匹配的。

所谓匹配,就是相同的括号,一左一右才叫匹配,题目给的用例很简单,我给大家写一个,你看看匹不匹配。

"(   [   {   }   ]    )" 中间的空格是为了隔开字符),大家认为这个字符串中,括号是匹配的吗?

答案是:匹配的。

字符串中,左括号和有括号尽管不是相邻两个,但在逻辑上,它是匹配的,

你可以这样考虑,从中间看,两个花括号匹配,然后移除掉,剩下"(   [      ]    )" 然后中间的方括号也是匹配的,移除掉,同理圆括号也是匹配的。


基于这些理解,我们给出一下思路:

我们采用栈,把字符中的字符入到栈内。

如果遇到左括号,入栈。

如果遇到右括号,获得出栈顶的元素和这个右括号进行匹配判别。

匹配情况

  1. 单对匹配的情况好说,继续进行下一个字符的判别即可。
  2. 那不匹配的情况也有一下几种:
  • 遇到右括号,和栈顶元素不匹配,直接返回false。
  • 字符串走到空,站内还有元素(肯定是左括号,因为只有左括号入栈),直接返回false

那么返回true的情况就很清楚了,当字符串走到空,并且栈内是没有元素,就要返回true.


栈的拷贝

把我们写好的栈拷贝过来,拷贝到OJ题目里面。

//接口型的OJ不需要包含头文件,删掉它
//#pragma once
//#include<stdio.h>
//#include<stdlib.h>
//#include<stdbool.h>
//#include<assert.h>

//typedef  int STDataType;
//因为站内要放字符,所以这里改为char
typedef  char STDataType;

 typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;

} ST;
//其实这里函数声明也可以不写,写了也不错。
 void STInit(ST *pst);
 void STPush(ST* pst, STDataType x);
 void STPop(ST* pst);
 STDataType STTop(ST* pst);
 bool STEmpty(ST* pst);
 int STSize(ST* pst);
 void STDestory(ST *pst);
//头文件删掉
//#define _CRT_SECURE_NO_WARNINGS 1
//#include"stack.h"

void STInit(ST* pst) {

	pst->a = NULL;
	pst->top = 0;//栈顶元素的下一个
	pst->capacity = 0;

}
void STPush(ST* pst, STDataType x) {

	//判断是否需要增容
	if (pst->top == pst->capacity) {
		int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType));
		if (tmp == NULL) {
			perror("realloc fail");
			return;
		}
		pst->a = tmp;
		pst->capacity = newCapacity;
	}
	pst->a[pst->top] = x;
	pst->top++;
}
void STPop(ST* pst) {

	//为空不能删
	assert(pst);
	assert(!STEmpty(pst));
	pst->top--;
}
STDataType STTop(ST* pst) {

	assert(pst);
	assert(!STEmpty(pst));
	return  pst->a[pst->top - 1];
}
bool STEmpty(ST* pst) {

	assert(pst);

	return pst->top == 0;
}
int STSize(ST* pst) {
	assert(pst);

	return pst->top;
}
void STDestory(ST* pst) {

	assert(pst);
	free(pst->a);
	pst->a = NULL;
	pst->capacity = pst->top = 0;

}

 


代码实现

bool isValid(char * s){

	ST st;//创建一个栈
	STInit(&st);//初始化栈

	while(*s!='\0'){//判别过程

	char tmp= *s;//取出一个字符进行判断

	if(tmp=='('||tmp=='['||tmp=='{'){//左括号入栈
			STPush(&st,tmp);
			s++;
	}
	else{//右括号,出栈顶元素,比较(前提是栈内有数据,所以这里要判空)
		//栈为空,肯定不匹配,返回false
		if(STEmpty(&st)){
			STDestory(&st);//返回前最好销毁一下栈,下面的步骤也是
			return false;
		}
		else{//栈不为空,取出栈顶元素,匹配
					STDataType top= STTop(&st);
				if(top=='('&&tmp==')'||
					 top=='['&&tmp==']'||
					 top=='{'&&tmp=='}')
					 {
						 STPop(&st);//匹配了,就出栈顶元素,并让s++去找下一个字符
						 s++;
					 }
					 else{//不匹配,直接返回false
						 	STDestory(&st);
						 return false;
					 }
		}
}
}
			bool ret=STEmpty(&st);//true 说明栈为空同时也说明括号是匹配的
                                  //false 说明栈不为空同时也说明括号是不匹配的
				STDestory(&st);
			//栈不为空 不匹配 栈为空 匹配
			return ret;


}

代码分析,

首先创建栈并初始化。

接下来从字符串中获取一个字符。

若是左括号,入栈。

若是右括号,说明要匹配。

但首先要判断栈是否为空,若为空说明不匹配(栈内没元素和它匹配)。

若不为空,取出栈顶元素,判断是否匹配,如果匹配继续下一个匹配,如果不匹配返回false。

最后,如果while退出了,我们要看看栈是否为空,若为空这返回true,若不为空则返回false。

代码的整体逻辑就是这样,来看看结果。

我们的代码是可以通过的,说明栈的实现和解题的方式都是没有问题的!!!


 以上,就是我对栈实现的理解和分析,如果文章对您有所帮助,不放给博主点点关注,点点赞,

后续会继续更新博客,下一次就是队列的实现和相关习题!!

!!我们下一期再见!!

如果需要源代码的可以到博主的仓库自取哦~~

test_5_19 · 琦琦爱敲代码/test_c - 码云 - 开源中国 (gitee.com)

 

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值