什么是栈
栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在 栈顶。
出栈:栈的删除操作叫做出栈。 出数据也在栈顶。
栈的结构:
实现栈的方式
实现栈的方式有两种: 顺序表和链表
链表的优缺点:
优点: 1、任意位置插入删除O(1)
2、按需申请释放空间
缺点:
1、不支持下标随机访问
2、CPU高速缓存命中率会更低
先说链表实现栈的缺点:
- 额外内存开销:链表实现的栈需要为每个节点分配内存空间来存储数据和指针。相比于数组实现的栈,链表实现需要额外的内存开销来维护节点之间的指针关系,可能导致内存碎片化。
- 动态内存分配:链表实现的栈需要通过动态内存分配来创建和释放节点。这涉及到频繁的内存分配和释放操作,可能导致内存管理的复杂性和性能开销。在某些情况下,可能会出现内存分配失败或内存泄漏的问题。
- 指针操作开销:链表实现的栈需要通过指针进行节点之间的连接操作。这包括插入和删除节点时的指针修改,可能涉及到多个指针的更新。相比于数组实现的栈,链表实现的栈需要更多的指针操作,可能会带来一定的性能开销。
- 随机访问的限制:链表是一种顺序访问的数据结构,无法像数组一样通过索引进行随机访问。如果需要在栈中进行随机访问元素,链表实现的栈可能不太适合,而数组实现的栈更具优势。
顺序表的优缺点:
优点:1、尾插尾删效率不错。
2、下标的随机访问。
3、CPU高速缓存命中率会更高
缺点:
1、前面部分插入删除数据,效率是O(N),需要挪动数据。
2、空间不够,需要扩容。a、扩容是需要付出代价的b、一般还会伴随空间浪费。
顺序表实现栈的优点
- 内存连续性:顺序表在内存中是连续存储的,相比于链表的动态内存分配,顺序表的元素在物理上更加紧凑。这样可以减少内存碎片化,提高内存的利用效率。
- 随机访问:顺序表可以通过索引直接访问栈中的元素,具有随机访问的能力。这意味着可以快速访问栈中任意位置的元素,而不需要遍历整个链表。
- 操作简单高效:顺序表的插入和删除操作只涉及元素的移动,不需要额外的指针操作和动态内存分配。这使得操作相对简单高效,并且在某些情况下比链表实现更快。
- 空间效率:相比于链表实现,顺序表不需要额外的指针来维护节点之间的连接关系,因此可以节省一定的空间开销。只需要存储元素本身和栈顶指针即可。
综上所述,用顺序表实现栈更好。
栈的实现
a.头文件的包含
#include<stdlib.h> #include<assert.h> #include<stdbool.h> #include<stdio.h>
b.栈的定义
typedef int STDataType; typedef struct Stack { STDataType* a; int top;//栈顶 int capacity;//栈的容量 }ST;
c.接口函数
// 初始化栈 void STInit(ST* pst); // 入栈 void STPush(ST* pst, STDataType data); // 出栈 void STPop(ST* pst); // 获取栈顶元素 STDataType STTop(ST* pst); // 获取栈中有效元素个数 int STSize(ST* pst); // 检测栈是否为空,如果为空返回true,如果不为空返回false bool STEmpty(ST* pst); // 销毁栈 void STDestroy(ST* pst);
接口函数的实现
1.栈的初始化
pst->top表示栈的顶部指针,通常情况下,它指向栈顶元素的下一个位置,而不是指向当前栈顶元素。通过 pst->top 可以确定栈中元素的个数,打印的时候记得将 top - 1。
void STInit(ST* pst) { assert(pst);//防止敲代码的人传过来是NULL指针 pst->a = NULL;//栈底 //top不是数组下标,不能理解成数组下标,因为栈只能拿到栈顶的元素,而数组可以随机访问拿到中间元素 //pst->top=-1;//指向栈顶元素 pst->top = 0;//指向栈顶元素的下一个位置 pst->capacity = 0; }
分别解释一下各自的含义:
- pst 是指向栈的指针,指向栈的首节点或头节点。
->
是一个成员访问运算符,用于通过指针访问结构体或类的成员
- pst ->a 是指向存储栈元素的数组的指针。栈中的元素通常被存储在数组中,通过 pst->a 可以访问和操作该数组。在 STInit 函数中, pst->a 被设置为 NULL,表示栈底为空,即栈中没有任何元素。
- pst->capacity 表示栈的容量,即栈可以容纳的最大元素数量。当栈中元素的数量达到 pst->capacity 时,栈被认为已满,无法再进行入栈操作。在初始化时,pst->capacity 的值通常被设置为 0,表示栈的初始容量为 0。
- pst->top 表示栈顶指针,它指向当前栈顶元素的下一个位置。在栈为空时,pst->top 的值为 0,表示栈底。随着元素的入栈和出栈操作,pst->top 的值会相应地增加或减少,指向栈中下一个元素的位置。
2.销毁栈
为了防止野指针的出现,栈销毁后记得将指针置空。
void STDestroy(ST* pst) { assert(pst); free(pst->a); pst->a = NULL; }
3.入栈
三元运算符
condition ? value1: value2
它的含义是,如果条件condition为真(非0),则整个表达式的值为value1;如果条件为假(0),则整个表达式的值为value2
解析:
int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
这段代码的执行顺序如下:
- 首先,评估条件pst->capacity == 0 这将检查 pst 指针所指向的结构体中的 capacity 成员是否等于 0
- 如果条件为真(pst->capacity 等于 0),则表达式的值为 4,将其赋给 newCapacity
3、如果条件为假(pst->capacity不等于 0),则表达式的值为pst->capacity * 2,将其赋给 newCapacity
realloc函数:【C进阶】-- 动态内存管理_Dream_Chaser~的博客-CSDN博客
动图:
函数代码:
void STPush(ST* pst,STDataType x) { if (pst->top == pst->capacity) { int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2; STDataType* tmp = (STDataType*)realloc(pst->a, newCapacity * sizeof(STDataType)); if (tmp == NULL) { perror("realloc fail"); return; } pst->a = tmp;//返回的是realloc出来的内存块的地址 pst->capacity = newCapacity;//把扩容后的空间大小赋值给栈容量 } pst->a[pst->top] = x;//先放值 pst->top++;//再++ }
【注意事项】
1️⃣检查栈的顶部指针 top 是否等于栈的容量 capacity 。如果这两个值相等,那么说明栈已经满了,无法再添加新的元素。
2️⃣接着判断此时栈的容量是否是0,若是0,则把4赋值给newcapacity作为新的栈容量。此后若栈满了,则把此时栈满时的容量 * 2进行扩容,赋值给newcapacity作为新的栈容量。
3️⃣先放入新的元素入栈,接着pst->top指向栈顶元素的指针++