赞
踩
前面学习的线性表中包含顺序表和链表,这两种数据结构允许在任意位置进行插入和删除,那么有没有一种数据结构是不能在任意位置进行插入删除,而只允许在一边进行插入删除的呢??当然有的,这就是我们今天要学习的一种新的数据结构:栈
栈是一种只允许在一端进行插入删除的数据结构,插入删除的一端叫做栈顶,不能进行插入删除的一端叫做栈底。
通常情况下,为了能够方便的修改数据结构中存放的数据类型,我们会对数据类型进行重定义
在学习任何的数据结构的时候,最重要的首先是要了解这个数据结构的结构,既然栈是一种数据结构,那么就说明栈可以用来存储数据,所以栈的结构中一定包含一个数据域,由于栈需要不断对栈顶元素进行出栈,所以需要一个标记栈顶元素的指针,这个指针是一个数组下标,也就是一个整数,综合这两种,我们觉得数据域设置成数组能够方便的处理这个问题。由于是数组,如果是静态数组,就导致数组的容量不能变,容量太小,会导致不够,容量太大,导致空间浪费。因此,我们觉得动态数组(能够进行扩容的数组)能够方便处理以上问题。动态数组就涉及到扩容的问题,所以肯定需要一个变量来记录数组中的容量。
在上述的结构中,val表示栈的数据域,是一个动态的数组,top表示栈顶指针,能够标识栈顶元素的情况(可以有两种表示方法),capacity表示栈中的容量大小。
这里需要注意一个点:top能够表示栈顶元素的情况,是数组的下标,其初始状态可以是两种表示方法
学习数据结构的另一个核心就是学习如何对这些数据结构进行操作
在上面的声明中,我们发现函数参数中传的是栈的结构体地址,这是为啥呢?
我们知道栈的结构本质是一个结构体,而结构体一般都是很大的,同时,我们在函数体中有时可能需要对栈中的内容进行修改,比如入栈和出栈,就需要改变栈中的内容,如果传的是栈的结构体,那么在函数的形参中我们知道是一份实参的拷贝,所以对形参的改变是不会影响实参的,那么我们考虑传结构体的地址,通过结构体的地址,函数就可以通过指针找到真正想改变的内容。
因此,传结构体的地址的好处有两个:
初始化函数是根据数据结构中的结构来实现的,我们知道栈中包含一个动态数组的指针和一个栈顶指针(数组下标)和一个容量,动态数组刚开始啥数据都没有,我们只需要对其置成空指针即可,防止出现野指针,栈顶指针指向的是栈顶元素在数组中的下标的下一个位置,即新元素入栈时所在的位置,当栈为空的时候,栈顶指针指向0,刚开始的容量就是0
销毁栈函数主要是完成对栈中申请的空间进行释放,注意在我们不想要使用这个栈的时候,一定要记得对栈进行销毁,本质就是对栈中开辟的数组进行释放,防止出现内存泄露。
入栈时一定要考虑栈是否已经满了,如果已经满了,则需要进行扩容,扩容时在确定新容量的时候一定要注意原先栈中的容量是否为0(初始化状态),这种情况需要进行特殊处理。扩容完成之后就是将数据插入栈顶,因为top表示的是栈顶元素的下一个位置,所以是先将元素入栈到top位置,再将top++。
出栈的本质就是删除栈顶元素,在出栈的时候一定要注意栈是否为空,当st->top>0
时才可以进行删除数据,删除的时候并不是真的将该数据从内存中移除,而是将栈顶指针往前移一位即可。栈中的top其实标识也可以是栈中的有效数据,因为刚开始栈为空的时候,top为0,标识没有数据,每次在入栈的时候,插入数据之后都会对top进行++操作,每次删除栈顶元素的时候,都会对top进行–的操作,所以栈中的top可以标识栈中的有效数据个数。
返回栈顶元素函数中,也要注意栈是否为空,如果栈为空,无法返回栈顶元素,这里需要注意,因为top表示的是栈顶元素的下一个位置,所以返回的是top的前一个位置的值,而不是返回top位置的值。这个函数在遍历访问栈的时候是很重要的。
当栈顶指针指向0的时候表示现在栈是空的状态,即初始化状态。这个函数在遍历访问栈的时候也是很重要的。
当栈为空的时候,top = 0,每次入栈的时候,top++,出栈的时候top–,所以top可以很好地表示栈的数据个数
在上面的函数定义中,我们发现每一个函数都需要对栈的结构体指针进行断言操作,这是为啥?
在函数的操作中,我们知道函数中需要通过栈的结构体指针找到栈中的内容,也就是需要对栈的结构体指针进行解引用,因此,这个结构体指针是一定不能为空的,否则就会出现空指针的解引用从而使程序崩溃。
使用栈的时候,一定需要注意几点:首先是创建栈的结构体,接下来,通过这个栈的结构体的地址对栈中的内容进行处理:处理首先是对栈进行初始化,就是可以插入和删除数据,当栈中拥有一定的数据的时候,这个时候我们可以遍历访问栈中的数据,这个通常需要使用的函数有判空函数,返回栈顶元素函数,出栈函数,当我们使用完栈之后,不想再使用的时候需要对栈进行销毁,防止出现内存泄露。
1.
从上面的测试代码中,我们要学习栈的基本使用:先定义一个栈的结构体,再对这个栈进行初始化操作,后面再进行各种常见的操作。除此之外,我们还需要学习栈的遍历思路:需要一个循环来解决,当栈不为空时,先访问栈的栈顶元素,再出栈。
2.
提交代码:
bool isValid(char * s){ // 需要一个栈 // 创建并初始化栈 Stack st; StackInit(&st); // 正常使用栈 while(*s) { if(*s == '('||*s == '{'||*s == '[') { // 左括号,入栈 StackPush(&st,*s); } else { // 右括号:出栈 // 出栈时需要判断此时栈是否为空,如果栈为空,则匹配失败 if(StackEmpty(&st)) { // 此时徐奥入栈,但是栈为空,所以匹配失败 return false; } else { // 栈不为空,出栈顶元素进行匹配,匹配失败,则返回false; char retTop = StackTop(&st); StackPop(&st); if(*s == ')'&&retTop!='('||*s == '}'&&retTop!='{'||*s == ']'&&retTop!='[') { // 当出栈的括号和遍历到的右括号不匹配的时候,匹配失败 return false; } } } s++; } return StackEmpty(&st); }
提交结果:
思路分析:
本题中采用构造一个栈来解决问题,遍历给定的字符串,遍历到的括号可能是左括号也可能是右括号。当遇到左括号时,直接入栈,当遇到右括号时,出栈顶元素和此时的右括号进行匹配,此时栈中可能为空也可能不为空,如果栈此时为空,则无法匹配,返回false。如果栈不为空,则出栈顶元素进行匹配,如果匹配,继续向后遍历字符串,如果不匹配,则此时返回false。当遍历完字符串之后,需要检查此时栈是否为空,因为可能会出现栈中还有多余的左括号的情况,如果栈为空,则成功匹配,返回true,如果栈不为空,则匹配失败,返回false。
综合上面的处理,我们可以知道,题目给定的字符串可能出现的情况:
- 为空
- 正常的情况,匹配成功的
- 遇到第一个就是右括号,匹配失败
- 遇到第一个是左括号,还没发确定,只能先入栈
- 最后一个是左括号的时候,一顶入栈的,此时就会导致栈中不为空,所以显然匹配失败
- 最后一个是右括号的时候,可能匹配成功,也可能匹配失败,这个时候需要看匹配完成之后栈是否为空,如果栈为空,则成功,栈不为空,则失败。
所以一般我们对一串数据进行分析的时候,一般需要考虑的问题,是否为空,常规情况(中间位置),第一个位置,最后一个位置。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。