一、栈的定义
1、栈是限定仅在表尾进行插入删除操作的线性操作。
(栈类似于子弹弹夹中的子弹一样,先进去的后出来,后进去的要先出来。)
在我们软件应用中,栈这种后进先出的数据结构是非常普。比如你用浏览器上网时,不管用什么浏览器,都有一个"后退键",你单击后可以按访问顺序的逆序加载浏览过的网页。再一个撤销操作也是用栈这样的方式实现的。
2、我们把允许插入和删除的一端称之为栈顶(top),另一端称作栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出(Last in First Out)的线性表,简称:LIFO结构。
3、栈是一个线性表,也就是说,栈元素具有线性关系 ,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。
它的特殊之处就在于限制了这个线性表的的插入和删除位置,它始终只在栈顶进行,这也就使得:栈底是固定的,最先进栈的只能在栈底。
4、栈的插入操作,叫做进栈,也称为压栈,入栈;栈的删除操作,叫做出栈,也有的叫弹栈。
二、栈的实现
(这里我们用数组来实现,链表空间开销更大)
1、栈的初始化:
先定义栈的结构:
cpp
//定义栈的结构
typedef int STDatatype;
typedef struct Stack
{
STDatatype* arr;
int top;
int capacity;
}ST;
cpp
//初始化
void StackInit(ST* ps)
{
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
这里top用来标记栈顶元素在数组中的位置。
2、入栈
cpp
void StackPush(ST* ps, STDatatype x)
{
assert(ps);
if (ps->top == ps->capacity) {
//增容
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
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;
}
3、销毁栈
cpp
//销毁
void StackDestroy(ST* ps)
{
if (ps->arr)
free(ps->arr);
ps->arr = NULL;
ps->top = ps->capacity = 0;
}
4、判断栈是否为空
cpp
//判断栈是否为空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
5、出栈
cpp
//出栈
void StackPop(ST* ps)
{
assert(!StackEmpty(ps));
--ps->top;
}
6、取栈顶元素
cpp
//取栈顶元素
STDatatype StackTop(ST* ps)
{
assert(!StackEmpty(ps));
return ps->arr[ps->top - 1];
}
7、取栈顶有效元素
cpp
//获取栈中的有效元素
int StackSize(ST* ps)
{
return ps->top;
}
三、栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。
四、栈的应用
后缀(逆波兰)表示法的定义:
逆波兰表示法,也称为后缀表示法,是一种数学表达式的表示方法。在这种表示法中,运算符位于其操作数之后,与我们常见的中缀表示法(如 a + b )不同,它的结构更利于计算机进行表达式求值(无需处理括号和运算符优先级)。
核心特点
- 无括号:通过运算符的位置天然体现运算顺序,无需括号来指定优先级。
- 运算顺序明确:从左到右扫描表达式,遇到运算符时,对其前面最近的若干个操作数执行运算。
示例
-
中缀表达式 3 + 4 ,对应的逆波兰表示为 3 4 + 。
-
中缀表达式 (3 + 4) * 5 ,对应的逆波兰表示为 3 4 + 5 * 。
-
中缀表达式 3 + 4 * 5 ,对应的逆波兰表示为 3 4 5 * + (因为乘法优先级高于加法,先算 4*5 )。
求值逻辑(以栈实现)
-
初始化一个空栈。
-
从左到右扫描逆波兰表达式的每个元素:
-
若为操作数,将其压入栈中。
-
若为运算符,从栈中弹出两个操作数(先弹出的是右操作数,后弹出的是左操作数),执行该运算符的运算,再将结果压入栈中。
- 扫描结束后,栈中仅剩的元素即为表达式的结果。
例如,对 3 4 5 * + 求值:
-
压入 3 →压入 4 →压入 5 ;
-
遇到 * ,弹出 5 和 4 ,计算 4*5=20 ,压入 20 ;
-
遇到 + ,弹出 20 和 3 ,计算 3+20=23 ,最终结果为 23 。