1.什么是栈
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
后进先出
2.栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小,而且数组的缓存利用率比较高.。
第一步:创建一个头文件和两个源文件
在头文件中进行结构定义和函数声明
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;//栈顶
int capacity;//容量
}ST;
//初始化和销毁
void STInit(ST* pst);
void STDestroy(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 STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶数据的下一个位置
pst->top = 0;
//top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
注意:top等于-1的时候top指向栈顶,top等于0的时候top指向栈顶的下一个位置,大家可以根据自己的情况选择。
销毁
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
传过来的地址不能为NULL如果为空我们就不能对他进行解引用,所以断言一下
入栈
void STPush(ST* pst, STDataType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapcity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapcity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapcity;
}
pst->a[pst->top] = x;
pst->top++;
}
初始化的时候我们并没有开辟空间,所以我们需要先申请空间,先开辟4个空间不够再扩容,我们把开辟好的空间用一个临时变量保存起来然后对他进行判断,避免我们空间申请失败导致原地址丢失,然后更改空间容量大小,最后入数据
出栈
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
竟然要出数据那么肯定要有数据才能出,所以断言一下,我们直接top--就可以了
取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top - 1];
}
我们要有数据才能取数据所以加个断言,直接返回top-1的下标就是栈顶的数据
判空
bool STEmpty(ST* pst)
{
assert(pst);
return pst->top == 0;
}
top为0栈就为空
获取数据个数
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
top表示下标,也就是元素个数
最后一步:测试test.c
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
printf("%d\n", STTop(&s));
STPush(&s, 3);
STPush(&s, 4);
while (!STEmpty(&s))
{
printf("%d\n", STTop(&s));
STPop(&s);
}
STDestroy(&s);
return 0;
}
注意:出栈可以边入边出,入栈1 2 3 4,出栈不一定是4 3 2 1
3.括号匹配问题
OJ链接:有效的括号
左括号必须和右括号相匹配必须是成对出现的,如果匹配就返回true否则返回false,这道题乍一看不好判断,其实我们可以用栈来解决,栈是后进先出的原则,如果是左括号就入栈,如果是右括号就出栈顶的左括号进行判断是否匹配,此时的栈里面都是左括号,这里我们的需求是后进先出,我们要让右括号和后进的左括号相匹配,这不就完美的匹配了后进先出。
因为C语言没有自带的栈所以我们需要把写好的栈放进去,C++会有自带的栈。
代码如下:
bool isValid(char* s) {
ST st;
STInit(&st);
while(*s)
{
//左括号入栈
if(*s == '(' || *s == '[' || *s == '{')
{
STPush(&st,*s);
}
//右括号取栈顶左括号尝试匹配
else
{
if(STEmpty(&st))
{
STDestroy(&st);
return false;
}
char top = STTop(&st);
STPop(&st);
//不匹配
if((top == '(' && *s !=')')
|| (top == '[' && *s !=']')
|| (top == '{' && *s !='}'))
{
STDestroy(&st);
return false;
}
}
++s;
}
//栈不为空,说明左括号比右括号多,数量不匹配
bool ret = STEmpty(&st);
STDestroy(&st);
return ret;
}
全部代码如下:
typedef char STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
//初始化和销毁
void STInit(ST* pst);
void STDestroy(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 STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
//top指向栈顶数据的下一个位置
pst->top = 0;
//top指向栈顶数据
//pst->top = -1;
pst->capacity = 0;
}
void STDestroy(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
//入栈 出栈
void STPush(ST* pst, STDataType x)
{
assert(pst);
//扩容
if (pst->top == pst->capacity)
{
int newcapcity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, newcapcity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapcity;
}
pst->a[pst->top] = x;
pst->top++;
}
void STPop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
pst->top--;
}
//取栈顶数据
STDataType STTop(ST* pst)
{
assert(pst);
assert(pst->top > 0);
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;
}
bool isValid(char* s) {
ST st;
STInit(&st);
while(*s)
{
//左括号入栈
if(*s == '(' || *s == '[' || *s == '{')
{
STPush(&st,*s);
}
//右括号取栈顶左括号尝试匹配
else
{
if(STEmpty(&st))
{
STDestroy(&st);
return false;
}
char top = STTop(&st);
STPop(&st);
//不匹配
if((top == '(' && *s !=')')
|| (top == '[' && *s !=']')
|| (top == '{' && *s !='}'))
{
STDestroy(&st);
return false;
}
}
++s;
}
//栈不为空,说明左括号比右括号多,数量不匹配
bool ret = STEmpty(&st);
STDestroy(&st);
return ret;
}
总结
栈是一种特殊的线性数据结构,它遵循后进先出(Last In, First Out,LIFO)的原则。栈的主要操作包括在栈顶插入元素(称为压栈,push)和从栈顶移除元素(称为弹栈,pop)。由于这种操作限制,栈在访问和操作元素时表现出一种特殊的顺序性。
栈的主要特点包括:
- 1.后进先出(LIFO):栈中的数据元素按照它们进入栈的顺序的逆序进行排列。最后进入栈的元素将最先被移除,而最先进入栈的元素将最后被移除。
- 2.操作受限:栈只允许在栈顶进行插入和移除操作。这种限制确保了栈的后进先出特性。
- 3.应用广泛:栈在计算机科学和软件工程中有广泛的应用。它们常用于实现函数调用(函数调用栈)、表达式求值(算术表达式的括号匹配和计算顺序)、内存分配(如自动变量存储)等。栈还被用作一种数据结构和算法设计的辅助工具,例如在深度优先搜索(DFS)中,栈被用来保存待访问的节点。
栈的实现方式有多种,包括基于数组的静态栈和基于链表的动态栈等。这些实现方式各有优缺点,具体选择取决于应用场景和性能需求。在实际应用中,栈的使用通常需要与其他数据结构和算法相结合,以实现复杂的程序逻辑和功能。
栈的一个重要特性是它可以很容易地通过数组或链表来实现,并且具有常数时间的插入和删除操作(在栈顶)。这使得栈成为一种高效且灵活的数据结构,适用于需要按照特定顺序处理元素的情况。