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

栈底层结构选型 栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。原因如下:
使用数组来实现栈时,栈的核心操作(入栈,出栈)只需操作数组尾部元素,时间复杂度都是O(1)
struct Stack
{
int* arr;
int size;
int capacity;
}使用链表来实现栈时,栈的核心操作(入栈,出栈)都可以在链表的头部进行操作,时间复杂度都是O(1)
struct Stack
{
int data;
struct Stack* next;
}这样看好像二者没有差别,但数组的优势不止如此:
可以发现栈的物理结构取决于底层实现,如果用数组实现,底层就是连续的(线性的),用链表实现(不连续的)
函数声明:

这里虽然栈的结构和顺序表几乎一摸一样,但是不同的数据结构,其提供的方法是不一样的。栈只有一端开口,栈只能在栈顶入数据和出数据
函数定义:

测试文件:

//销毁
void STDesTroy(ST* ps);
//入栈---栈顶
void STPush(ST* ps, STDataType x);栈顶在数组的尾部,接下来把size改为top更符合栈的结构,top指向栈顶的位置,刚好就是栈中有效数据个数

这里和顺序表类似,空间足够,插入一个数据,size++
空间不够的话,就需要先额外增容了


补充:ps不能为空,否则对空指针进行解引用了


出栈之前有个操作是判断栈是否为空,栈为空无数据可出了
//判断栈是否为空
bool STEmpty(ST* ps);栈顶为0的时候,说明栈中的有效数据个数为0

//出栈---栈顶
void STPop(ST* ps);此时有5个空间,栈顶在下一个要插入数据的位置,栈顶对应的下标为4




出栈即少一个数据,栈顶要减减,此时栈中有4个有效的空间,只有3个有效的数据(3个有效的数据就是1,2,3,不包含4)
//取栈顶元素
STDataType STTop(ST* ps);取栈顶并不是出栈,而是把栈顶这个元素返回



这个操作叫循环出栈,输出结果体现出栈后进先出的规则
//获取栈中有效元素个数
int STSize(ST* ps);

Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//定义栈的结构 - Stack是栈的名称
typedef int STDataType;
typedef struct Stack {
STDataType* arr;//存储数据的数组
int top;//top指向栈顶的位置,刚好就是栈中有效数据个数
int capacity;//栈的空间大小
}ST;
//初始化
void STInit(ST* ps);
//销毁
void STDesTroy(ST* ps);
//入栈---栈顶
void STPush(ST* ps, STDataType x);
//出栈---栈顶
void STPop(ST* ps);
//取栈顶元素
STDataType STTop(ST* ps);
//判断栈是否为空
bool STEmpty(ST* ps);
//获取栈中有效元素个数
int STSize(ST* ps);Stack.c
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//初始化
void STInit(ST* ps)
{
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
//销毁
void STDesTroy(ST* ps)
{
//arr不能为空,为空的话都没有向操作系统中申请内存,就没有必要销毁了
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->capacity = ps->top = 0;
}
//入栈---栈顶
void STPush(ST* ps, STDataType x)
{
assert(ps);
//判断空间是否足够
if (ps->top == ps->capacity)
{
//增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
//如果capacity为0,就给4,realloc第二个参数的单位为字节,这里要申请4个整型空间,而不是4个字节
STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail!");
exit(1);
}
ps->arr = tmp;
ps->capacity = newCapacity;
}
//空间足够
ps->arr[ps->top++] = x;
}
//判断栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//出栈---栈顶
void STPop(ST* ps)
{
assert(!STEmpty(ps));
ps->top--;
}
//取栈顶元素
STDataType STTop(ST* ps)
{
assert(!STEmpty(ps));
return ps->arr[ps->top - 1];
}
//获取栈中有效元素个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}test.c
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
void test01()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
printf("size:%d\n", STSize(&st));
//STPop(&st);
while (!STEmpty(&st))
{
//取栈顶
STDataType top = STTop(&st);
printf("%d ", top);
//出栈
STPop(&st);
}
printf("\n");
STDesTroy(&st);
}
int main()
{
test01();
return 0;
}

思路:借助数据结构—栈,遍历字符串,左括号入栈,是右括号就取栈顶元素比较,是否匹配

第一种情况,匹配的话,就让左括号出栈,出栈之后,继续在字符串中遍历

这里用一个指针pi来遍历字符串,此时拿到一个右大括号,此时取栈顶取到的是一个左大括号,将其与* pi比较,是匹配的让左大括号出去
以此类推,循环往复,当pi遍历完字符串,此时栈为空,说明这就是一个有效的字符串

这里参数有一个char* s,s就是字符串,s指向字符串的第一个字符,循环截至的条件是遍历完字符串 基于上述思路代码如下:


这里看似写完了,实则不然,对比案例测试一下,能否覆盖所有情况

这里只有左括号,入栈之后pi++,此时pi走到\0,跳出while循环,就直接销毁了,所以这里没有写全

这样代码看着有点冗余,这里可以写为三目表达式 此时再测试分析

第一个是右括号,直接进else取栈顶,但是当前栈为空,所以还需要特殊处理,调用STTop会出现断言报错


此情况也推理符合 最后完整代码如下:

以上就是深入解析数据结构之栈,下一篇内容为栈和队列,喜欢的兄弟们不要忘记一键三连支持一下