目录
- 栈
- 栈的实现
- 栈的结构
- 栈的初始化
- 栈的销毁
- 入栈
- 出栈
- 获取栈顶元素
- 栈的判空
- 获取栈的数据个数
- test.c(测试)
- 总结
栈
栈也是线性表(在逻辑上是顺序存储)的一种,栈只允许其在固定的一端进行插入和删除,栈中的元素遵循后进先出(先进后出)
出入数据可以比喻成羽毛球和电梯
栈顶:可以进行插入和删除数据,入数据和出数据都在栈顶
栈底:不允许插入和删除数据
栈的实现
栈可以用数组或单链表,双向链表实现
双向链表要多维护一个指针
单链表和数组实现栈都比较好,但是数组的缓存利用率比较高
缓存利用率(命中率)是什么?
如果第一个数据在缓存中,缓存命中,直接访问
不在缓存中,叫不命中,要把数据从内存中加载到缓存中,再访问
简单来说是缓存命中的多少
数组在内存中是连续存储的,所以提取数组的缓存利用率比较高
1.命中第一个数据,后面的数据就会连续命中
2.第一个数据不命中,把它加载到缓存中,后面的一系列数据就都会加载到缓存中
比如你们去春游,老师叫来一个大巴车把A同学带来了,司机不只把A同学带来,还把后面的所有同学都带来了
链表
链表是由一个一个指针链接在一起的,拉到缓存中一次也只能来一个数据,效率比较低,缓存命中率不高
还有一个原因就是拉一个数据,可能把后面无关的数据也拉进来,会造成缓存污染,把之前有用的数据给挤出去,链表物理上不是连续存储的
所以我们使用数组来实现栈
栈的结构
//静态栈不太实现,都是实现的动态栈
//用数组为基础实现动态栈
typedef int STDataType;
typedef struct Stack
{
STDataType* a;//指向数组的指针
int top;//标识栈顶
int capacity;//栈的大小
}Stack;
栈的初始化
//栈的初始化
void StackInit(Stack* pst)
{
assert(pst);
pst->a = NULL;
pst->top = 0;
//指向栈顶的下一个数据,可以表示元素的多少(size)
//s->top = -1;
//指向栈顶数据,可以表示元素的下标
pst->capacity = 0;
}
top有两种情况
- top可以指向栈顶,可以这样理解,top是指向数组下标
- 先top++,再插入数据
- top还可以指向栈顶的下一个数据,top可以理解为size,数据的多少
- 先插入数据,再top++
易错点:top指向栈顶元素,又top初始化为0,为0是指向栈顶的下一个元素
应该top初始化为-1
栈的销毁
//栈的销毁
void StackDestroy(Stack* pst)
{
assert(pst);
//pst指向栈的结构体为空就找不到数组了
free(pst->a);
pst->a = NULL;
pst->top = pst->capacity = 0;
}
入栈
//入栈
void StackPush(Stack* pst, STDataType x)
{
assert(pst);
//扩容
if (pst->capacity == pst->top)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
//->的优先级大于*
STDataType* tmp = (STDataType*)realloc(pst->a,(sizeof(STDataType))
* newcapacity);
//为数组realloc一个连续的空间
if (tmp == NULL)
{
perror("StackPush:realloc");
return;
}
//开辟成功一个空间
pst->capacity = newcapacity;
//把新空间给它
pst->a = tmp;
//新数组给它
}
pst->a[pst->top] = x;
//插入x
pst->top++;
//top增加1
}
判满(判断空间是否满了):
1.top指向栈顶的下一个数据(size),top == capacity
2.top指向栈顶(下标),top+1 == capacity
出栈
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->top > 0);
//top指向0的时候是没有元素的
pst->top--;
//元素少一个
}
获取栈顶元素
//获取栈顶元素
STDataType STTop(Stack* pst)
{
assert(pst);
assert(pst->top > 0);
//取的最后一个元素是0下标的元素,取0时下标为-1是越界访问的
return pst->a[pst->top - 1];
//返回最后一个数据
}
栈的判空
//栈的判空
bool STEmpty(Stack* pst)
{
assert(pst);
return pst->top == 0;
//为真时top为0,返回true,数组中没有元素
//为假时top为非0,返回false,数组中还有元素
}
获取栈的数据个数
//获取栈的数据个数
int STSize(Stack* pst)
{
assert(pst);
return pst->top;
//top(代表数据个数)
}
test.c(测试)
#define _CRT_SECURE_NO_WARNINGS
#include"Stack.h"
//int main()
//{
// Stack sl;
// StackInit(&sl);
// StackPush(&sl,1);
// StackPush(&sl, 2);
// StackPush(&sl, 3);
// StackPush(&sl, 4);
// StackPop(&sl);
// StackPop(&sl);
// StackPop(&sl);
// StackPop(&sl);
// printf("%d", STTop(&sl));
// StackDestroy(&sl);
//
// return 0;
//}
int main()
{
Stack sl;
//入栈: 1,2,3,4 出栈: 4,3,2,1 / 1,2,3,4 有多种可能
// 队列的入栈是1,2,3,4 出栈一定是:4,3,2,1
//创建一个栈的变量
StackInit(&sl);
StackPush(&sl,1);
//printf("%d", STTop(&sl));
StackPush(&sl, 2);
printf("%d ", STTop(&sl));
StackPop(&sl);
StackPush(&sl, 3);
//printf("%d", STTop(&sl));
StackPush(&sl, 4);
//printf("%d", STTop(&sl));
while (!STEmpty(&sl))//判断栈是不是空
{
//有数据返回false,!(取反)真,获取栈顶数据,弹出栈顶数据
//没数据返回true,!(取反)假,top == 0,退出循环,空栈
printf("%d ", STTop(&sl));
StackPop(&sl);
}
//2 4 3 1
StackDestroy(&sl);
return 0;
}
总结
栈做题关键是找最近的匹配(东西),比如leetcode中括号的匹配,
{ [ ( ) ] }
最后祝大家题题AC