基本概念
- 栈:栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出 LIFO(Last In First Out)的原则。
- 压栈:栈的插入操作叫做进栈 / 压栈 / 入栈,进入的数据始终放在栈顶。
- 出栈:栈的删除操作叫做出栈,出栈的数据始终是栈顶元素。
- 栈的进栈 / 出栈示意图:
功能实现
栈的结果实现可以使用顺序表,也可以使用链表,这二者相比较而言,顺序表更优。因为栈的特点是先进后出,只在一端操作,而顺序表在尾部的插入删除只需 O(1) 时间复杂度。
在之前的博客说过,顺序表比之链表差的地方就是在除了尾部以外的地方插入删除时间复杂度过高,但在其他方面都是优于链表的,而栈只在一端操作就刚刚好使得顺序表比链表更适合实现栈的基本结构。
//这是 .h 部分的代码
#pragma once
//使用这种方式来重命名数据类型,这样可以很方便的修改后续数据的数据类型,相当于#define的作用
typedef int StackType;
//创建栈
typedef struct Stack {
//使用指针指向一块动态开辟的内存
StackType* _date;
//表示栈中的有效数据,也代表了栈顶
size_t _size;
//表示栈的最大容量
size_t _capacity;
}Stack;
//包含所有函数的声明
//栈初始化
void StackInit(Stack* s1);
//检查栈是否已满
void CheckCapacity(Stack* s1);
//进栈
void StackPush(Stack* s1, StackType val);
//出栈
void StackPop(Stack* s1);
// 获取栈顶元素
StackType StackTop(Stack* s1);
// 获取栈中有效元素个数
int StackSize(Stack* s1);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* s1);
//栈销毁
void StackDestroy(Stack* s1);
//这是 .c 部分的代码
#include<stdio.h>
#include<stdlib.h>
#include"stack.h"
//栈初始化
void StackInit(Stack* s1) {
//参数合法性检验
if (s1 == NULL) {
return;
}
s1->_date = NULL;
s1->_capacity = s1->_size = 0;
}
//检查栈是否已满
void CheckCapacity(Stack* s1) {
//该函数是在其他函数内部被调用的,所以就不用参数合法性检验了,因为前面已经检查过了
//如果有效数据等于最大容量,那么需要增容
if (s1->_capacity == s1->_size) {
//每次以2倍的空间进行增容
s1->_capacity = s1->_capacity == 0 ? 1 : 2 * s1->_capacity;
//用realloc函数进行动态增容
s1->_date = (StackType*)realloc(s1->_date, sizeof(StackType) * s1->_capacity);
}
}
//进栈
void StackPush(Stack* s1, StackType val) {
//参数合法性检验
if (s1 == NULL) {
return;
}
//检查容量是否够,不够的话就增容
CheckCapacity(s1);
s1->_date[s1->_size] = val;
s1->_size++;
}
//出栈
void StackPop(Stack* s1) {
//参数合法性检验,如果没有数据就直接返回
if (s1 == NULL || s1->_size == 0) {
return;
}
//这是顺序表,所以不用像链表一样,删除一个数据就要释放一个空间,只需有效数据个数减一即可
s1->_size--;
}
// 获取栈顶元素
StackType StackTop(Stack* s1) {
//参数合法性检验不好做,因为不能以任何值作为出错的返回值,以后学了抛出异常就可解决
return s1->_date[s1->_size - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* s1) {
//参数合法性检验
if (s1 == NULL) {
return 0;
}
return s1->_size;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* s1) {
//参数合法性检验
if (s1 == NULL || s1->_size == 0) {
return 1;
}
return 0;
}
//栈销毁
void StackDestroy(Stack* s1) {
//参数合法性检验,如果动态开辟空间为空,则直接返回
if (s1 == NULL || s1->_date == NULL) {
return;
}
free(s1->_date);
s1->_date = NULL;
}
int main() {
Stack s1;
StackInit(&s1);
StackPush(&s1, 0);
StackPush(&s1, 9);
StackPush(&s1, 1);
StackPush(&s1, 4);
while (s1._size) {
printf("%d\n",StackTop(&s1));
StackPop(&s1);
}
return 0;
}
简单练习
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。LeetCode链接
bool validateStackSequences(int* pushed, int pushedSize, int* popped, int poppedSize){
//开辟数组空间,用来当做栈使用
int* stackarr = (int*)malloc(pushedSize * sizeof(int));
//该标志位为记录入栈元素进入 stackarr 中的次数
int flag = pushedSize;
//该标志位为记录栈顶元素和出栈元素相等的次数
int flag1 = pushedSize;
//循环入栈、比较、出栈等等
for(int i = 0;flag1;){
//在 flag 不为零的情况下,如果 stackarr 为空的就先入栈一个元素,或者比较不相等就入栈一个元素
if(flag && (i == 0 || stackarr[i - 1] != *popped)){
//入栈一个元素然后更新变量
stackarr[i++] = *pushed++;
flag--;
}
//比较如果相等,就更新变量
if(stackarr[i - 1] == *popped){
i--;
popped++;
flag1--;
}
//如果 flag 先走到 0,这就说明入栈元素已全部压入 stackarr 中,但是实现不了出栈序列的次序,所以返回 false
else if(!flag){
return false;
}
}
return true;
}
-
另外还有两道题,可以参考我的另两篇博客:
- 1、 20. 有效的括号
- 2、 232. 用栈实现队列