目录
一、栈的概念及结构
二、栈的实现
1.初始化--StackInit
2.插入--StackPush
3.判断栈是否为空栈--StackEmpty
4.删除栈元素--StackPop
5.计算栈的长度--StackSize
6.找栈顶--StackTop
7.打印栈的数据(主函数中实现)
8.销毁栈--StackDestroy
三、关于栈的典型编程题
栈:一种特殊的线性表,(线性表就是数据元素挨着放的,呈现出线性的结构)其只允许在固定的一端进行插入和删除元素操作。进行数据删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last in First Out)的原则。可以理解为一把枪打出子弹,肯定先打出的一定是后装的子弹,而新装的子弹一定是后打出的。
关于栈的两个操作:
①压栈:栈的插入操作叫作进栈/压栈/入栈,入数据在栈顶。
②出栈:栈的删除操作叫作出栈。出数据也在栈顶。
—— 后进先出(Last In Frist Out)(也可以说先进后出)
里链表相对于数组没有太多优势,而数组的cpu命中率更高,数组在尾上插入数据的代价比较小。在讲双向链表的时候说过数组相对于链表的优点。所以用数组实现栈更胜一筹。还有一点就是,我们这里实现的是动态栈,因为静态栈没什么优势
首先应该了解栈应该用结构体创建,并且创建动态栈,所以这个操作跟以前通讯录的实现的操作极其相似,如果之前学会了通讯录,这里的栈的实现小菜一碟!a是用来维护栈的,通俗点讲就是动态开辟内存的头,这里的top其实就是通讯录里的size,通俗点讲就是有效元素的个数。只是对于栈结构来说你可以说是栈顶的位置,capacity当然就是这个栈最大容量了
//动态栈---更加推荐
typedef struct Stack
{
STDataType* a;
int top;//栈顶的位置
int capacity;
}ST;
//静态栈
//typedef struct Stack
//{
// STDataType _a[N];
// int _top;
//}
1.初始化--StackInit
top初始化为0是什么意思,就意味着top最后是指向栈顶数据的下一个。(先放数据再+1)如果top初始化为-1,意味着top最后指向栈顶数据。(先+1再放数据)。这两种都可以。我们这里用top初始化为0实现
void StackInit(ST* ps)
{
assert(ps);//传入的结构体指针不能为空
ps->a = NULL;
ps->top = 0;//也可以写成ps->top = -1;
ps->capacity = 0;
}
2.插入--StackPush
与通讯录插入的操作基本相同,值得注意的就是满了要增容的
void StackPush(ST* ps, STDatatype x)
{
assert(ps);
//检查空间,满了就增容
if (ps->top == ps->capacicy)
{
//第一次开辟空间容量为4,其它次容量为当前容量*2
int newcapacity = ps->capacicy == 0 ? 4 : ps->capacicy * 2;
//第一次开辟空间,a指向空,realloc的效果同malloc
STDatatype* tmp = realloc(ps->a, sizeof(STDatatype) * newcapacity);
//检查realloc
//realloc失败
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
//realloc成功
ps->a = tmp;
ps->capacicy = newcapacity;
}
//插入数据
ps->a[ps->top] = x;
ps->top++;
}
3.判断栈是否为空栈--StackEmpty
运用bool,要引用头文件stdbool.h
bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
assert(ps);
if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//这种写法很简单
}
4.删除栈元素--StackPop
删除要注意栈为空就不能删除了
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//如果为空返回1 那么即!1为0 assert判断为假,直接报错
// 所以你传入的这个栈不能为空栈
//删除的数据个数不能top--;
}
5.计算栈的长度--StackSize
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
6.找栈顶--StackTop
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//也可以写成assert(ps->top > 0);
//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
//因为这道题我初始化top为0,所以不用改
//因为为空就不需要找这个栈顶了
return ps->a[ps->top - 1];
//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}
7.打印栈的数据(主函数中实现)
打印栈的数据只要遍历栈就可以,知道栈为空,因为栈只能从栈顶插入和删除数据,那么我们就可以从栈顶开始打印,打印完一个栈顶,再把这个栈顶删除,使它前一个元素被看为一个栈顶
8.销毁栈--StackDestroy
为什么需要销毁栈,因为栈中的每个元素都是动态开辟过的空间
void StackDestroy(ST* ps)
{
assert(ps);
if (ps->a)
{//如果之前开辟空间成功过,则释放
//如果之前都没有开辟过空间,即什么都没做或者开辟失败了,就不用释放了
free(ps->a);
}
ps->a = NULL;
ps->capacity = ps->top = 0;
}
① Stack.h,用于函数的声明和类型定义
② Stack.c,用于函数的实现
③ test.c,用于测试函数
ლ(′◉❥◉`ლ)--- Stack.h
#pragma once
#include
#include
#include
#include
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;//栈顶的位置
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);//找栈顶的函数
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
ლ(′◉❥◉`ლ)--- Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void StackInit(ST* ps)
{
assert(ps);//传入的结构体指针不能为空
ps->a = NULL;
ps->top = 0;//也可以写成ps->top = -1;
ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//满了是要扩容的
if (ps->capacity == ps->top)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);//程序异常退出
}
ps->capacity = newCapacity;
ps->a = tmp;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//如果为空返回1 那么即!1为0 assert判断为假,直接报错
// 所以你传入的这个栈不能为空栈
//删除的数据个数不能top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//也可以写成assert(ps->top > 0);
//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
//因为这道题我初始化top为0,所以不用改
//因为为空就不需要找这个栈顶了
return ps->a[ps->top - 1];
//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}
void StackDestroy(ST* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
}
ps->a = NULL;
ps->capacity = ps->top = 0;
}
bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
assert(ps);
if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//这种写法很简单
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
ლ(′◉❥◉`ლ)--- test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void TestStack()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))//栈不为空
{
printf("%d ", StackTop(&st));//打印原栈顶
StackPop(&st);//删除原栈顶,使它前一个元素成为新的栈顶
}
StackDestroy(&st);
}
int main()
{
TestStack();
return 0;
}
※ 题述:给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
① 左括号必须用相同类型的右括号闭合。
② 左括号必须以正确的顺序闭合。
示例1:
输入:s = “()”
输出:true
示例2:
输入:s = “()[]{}”
输出:true
示例3:
输入:s = “(]”
输出:false
示例4:
输入:s = “([)]”
输出:false
示例5:
输入:s = “{[]}”
输出:true
思路:不能单纯的数左括号和右括号的个数,因为有顺序,即呈现对称结构的才行。我们利用栈的先进后出的思想就可以解决这道题。我们规定1、左括号,入栈 2、右括号,跟左括号匹配。(即右括号不入栈,而是与栈中最近的栈顶匹配,在栈中的肯定都是左括号),这样就保证了所有的左括号均在栈中,想取出他只需要取栈顶,而右括号直接与栈顶匹配即可,并且注意题目中要求的括号类型只有三种即'()' '{}' '[]' ,那么就可以罗列出来。
1、首先要遍历传入的字符串(包含括号的字符串),要遍历到尾,判断是左括号还是右括号,左括号则入栈,右括号则与栈顶的元素即左括号匹配,匹配完后再删除这个左括号,那它前一个元素就变成了栈顶
2、右括号如何匹配的左括号,通过找栈顶top,就是把最新放进去的左括号拿出来(即栈顶),然后再跟字符串的括号比较(即与*s比较),如果不相等就直接返回假,但是在这之前一定要先销毁这个栈,因为是之前动态内存开辟的,否则s++判断下一个位置的括号,直到判断到字符串结束都匹配的话,那么这个栈应该为空,因为栈中的左括号都与右括号匹配成功,左括号应已全部出栈
3、
针对传入的字符串
问题一:只有左括号呢(这种包括没有右括号的情况和右括号与左括号匹配了一部分,可最后只剩下左括号的情况)?那就没有右括号与之匹配了,所以最后要判断这个栈是否为空,如果为空就是匹配成功了,否则就只剩左括号了,左括号没有匹配的了,所以返回假就可以了。
问题二:只有右括号呢?也就是意思在判断右括号之前如果就没有左括号,一定就不匹配,因为题中要求的,一定要先从左括号开始。所以在else中先判断是否为空,因为左括号用来入栈,栈中都没数据,直接返回假即可。
——————————————————————————
代码如下:注意这道题是在之前写的栈的实现的代码的基础上实现inValid函数(判断是否为有效的括号)的,如果用c++实现,就可以直接调用库,在调用库的基础上写这道题,注意用c写的时候,记得把char typedef 为 STDataType,因为这里括号都是char类型的了
#include
#include
#include
#include
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top;//栈顶的位置
int capacity;
}ST;
void StackInit(ST* ps);
void StackDestroy(ST* ps);
void StackPush(ST* ps,STDataType x);
void StackPop(ST* ps);
STDataType StackTop(ST* ps);//找栈顶的函数
int StackSize(ST* ps);
bool StackEmpty(ST* ps);
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
void StackInit(ST* ps)
{
assert(ps);//传入的结构体指针不能为空
ps->a = NULL;
ps->top = 0;//也可以写成ps->top = -1;
ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//满了是要扩容的
if (ps->capacity == ps->top)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);//程序异常退出
}
ps->capacity = newCapacity;
ps->a = tmp;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//如果为空返回1 那么即!1为0 assert判断为假,直接报错
// 所以你传入的这个栈不能为空栈
//删除的数据个数不能top--;
}
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
//也可以写成assert(ps->top > 0);
//但更建议这种用函数的写法,因为万一top初始化为-1,这里还需要改。
//因为这道题我初始化top为0,所以不用改
//因为为空就不需要找这个栈顶了
return ps->a[ps->top - 1];
//这里因为初始化top=0,所以栈顶应该是对应top-1才对
}
void StackDestroy(ST* ps)
{
assert(ps);
if (ps->a)
{
free(ps->a);
}
ps->a = NULL;
ps->capacity = ps->top = 0;
}
bool StackEmpty(ST* ps)
{/*下面这种第一个写法比较麻烦
assert(ps);
if (ps->top == 0)
{
return true;
}
else
{
return false;
}*/
return ps->top == 0;//这种写法很简单
}
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
bool inValid(char* s)
{
ST st;
StackInit(&st);
while (*s)
{//循环遍历字符串,直到遍历为空
if (*s == '('
|| *s == '{'
|| *s == '[')
{
//左括号则入栈
StackPush(&st, *s);
++s;
}
else
{ //进了else就说明是右括号
//遇到右括号了,但是栈里面没数据,说明前面没有左括号,不匹配,返回false
//遇到右括号也可能前面有左括号,这样就右括号与出栈后的左括号匹配即可
if (StackEmpty(&st))
{
StackDestroy(&s);
return false;
}
//因为栈中有数据,则右括号与出栈后的左括号匹配
STDataType top = StackTop(&st);//取栈顶,即左括号
StackPop(&st);//删除栈顶,便于下一次取新的栈顶
if ((*s == '}' && top != '{')
|| (*s == ']' && top != '[')
|| (*s == ')' && top != '('))
{//这三种情况的任意一个成立就说明括号不匹配
StackDestroy(&st);
return false;
//在终止函数之前销毁动态开辟的栈,防止内存泄漏
}
else
{ //进了else就说明匹配上了,就继续++看后面的括号是否匹配
s++;
}
}
}
//最后是否匹配成功,还要判断栈是不是空//这种情况是应对最后只有左括号的情况
//如果栈不是空,则栈中还有左括号未出,没有匹配完,返回false
//栈为空就说明匹配成功
bool ret = StackEmpty(&st);
StackDestroy(&st);
return ret;
}
1、一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出栈的顺序是 ( )
A. 12345ABCDE
B. EDCBA54321
C. ABCDE12345
D. 54321EDCBA
解析:B、根据栈的特点 —— 后进先出,先进后出
2、若进栈的序列为1,2,3,4,进栈过程中可以出栈,则下列不可能的一个出栈序列是 ( )
A. 1,4,3,2
B. 2,3,4,1
C. 3,1,4,2
D. 3,4,2,1
解析:C,本题需要注意的是进栈过程中可以出栈,这也导致了多种情况的出现:
进栈顺序:1 2 3 4
出栈顺序:1 2 3 4
4 3 2 1
… …