上次结束了链表部分的内容:链接未来:深入理解链表数据结构(二.c语言实现带头双向循环链表)
然而,当我们涉及特定问题时,另一个非常有用的数据结构也开始显得至关重要——栈
栈与链表有着截然不同的特性,它采用一种后进先出(LIFO)的策略,这意味着最后进入栈的元素将首先被取出。这样的特性赋予了栈在特定场景下独特的价值和功能
源码可以去我的gitee:Nero的gitee
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则
压栈(push):栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
出栈(pop):栈的删除操作叫做出栈。出数据也在栈顶
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。栈只在一端进行插入和删除,选择数组尾端非常契合。
- 头文件Stack.h:用来基础准备(常量定义,typedef),链表表的基本框架,函数的声明
- 源文件Stack.h:用来各种功能函数的具体实现
- 源文件test.c:用来测试功能是否有问题,进行基本功能的使用
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
int* a;
int top; // 标识栈顶位置的
int capacity; //栈的容量
}ST;
void STInit(ST* ps);//初始化
void STDestroy(ST* ps);//销毁
void STPush(ST* ps, STDataType x);// 栈顶插入
void STPop(ST* ps);// 栈顶删除
STDataType STTop(ST* ps);//返回栈顶元素
bool STEmpty(ST* ps);//判断栈是否为空
int STSize(ST* ps);//栈中元素数量
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = -1;//我选择了-1
ps->capacity = 0;
}
top用来标记栈顶位置,那一开始初始化为多少合适:
- 初始化为**-1**:指向塔顶元素。因为底层用数组来实现,那第一个元素下标为0,一开始无元素为-1,有了第一个元素加上1变成0,完全符合。
- 初始化为0:指向塔顶元素的下一个位置。道理同上,但是总是比塔顶元素下标大1
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->capacity == ps->top + 1)//判断有没有满
{
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
STDataType* new = (STDataType*)realloc(ps->a, sizeof(ST) * newCapacity);
assert(new);
ps->a = new;
ps->capacity = newCapacity;
}
ps->top++;
ps->a[ps->top] = x;
}
代码确保传入的栈指针
ps
是有效的。然后,它检查栈是否已满。当栈满时,需要扩展栈的容量栈的
top
指针增加,表示栈顶位置上移一个单位。最后,要推入栈的元素x
被放入栈顶位置
ps->a[ps->top]
void STPop(ST* ps)
{
assert(ps);
assert(ps->top >= 0);//确保不为空
ps->top--;
}
STDataType STTop(ST* ps)
{
assert(ps);
assert(ps->top >= 0);
return ps->a[ps->top];
}
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == -1;
}
int STSize(ST* ps)
{
assert(ps);
return ps->top + 1;
}
大家一般都是:栈和队列,栈和队列,(二者放在一起)马上就到队列的知识梳理了。
感谢大家支持!!!