栈的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
栈的结构
定义栈
栈有两种常见的定义方式:基于数组(或动态数组)的实现和基于链表的实现。
1. 基于数组(或动态数组)的栈定义
基于数组的实现是最常见的栈定义方式。这种实现方式使用一个固定大小或动态调整大小的数组来存储栈中的元素。数组的一个端点被视作栈底(通常不直接操作),而另一端点被视作栈顶,用于执行压栈和弹栈操作。
- 压栈(push):将新元素添加到数组的栈顶位置,并更新栈顶指针。
- 弹栈(pop):移除数组的栈顶元素,并更新栈顶指针。
- 查看栈顶元素(peek):返回数组的栈顶元素但不移除它。
- 判断栈是否为空(isEmpty):检查栈顶指针是否指向栈底位置。
基于数组的实现简单且效率高,但存在一个问题:当栈满时,需要预先分配更大的数组空间,这可能导致空间浪费。为了解决这个问题,可以使用动态数组,它在需要时自动扩展大小。
2. 基于链表的栈定义
基于链表的实现使用链表数据结构来存储栈中的元素。链表的头部(或尾部)被视作栈顶,用于执行压栈和弹栈操作。
- 压栈(push):创建一个新节点,将新元素存储在该节点中,然后将该节点添加到链表的头部(或尾部)。
- 弹栈(pop):移除链表的头部(或尾部)节点,并返回该节点中存储的元素。
- 查看栈顶元素(peek):返回链表的头部(或尾部)节点中存储的元素但不移除该节点。
- 判断栈是否为空(isEmpty):检查链表是否为空。
基于链表的实现不需要预先分配固定大小的空间,因此更加灵活。然而,由于链表中每个节点都需要额外的存储空间来存储指针,因此这种实现方式在内存使用上可能不如基于数组的实现高效。
在实际应用中,根据具体需求和场景选择合适的栈定义方式是很重要的。例如,在需要频繁扩容且内存空间充足的情况下,基于动态数组的栈可能是一个好选择;而在内存使用受限或需要高度灵活性的情况下,基于链表的栈可能更合适。
链栈的定义
typedef int STDataType;//栈中存储的元素类型(这里用整型举例)
typedef struct Stack
{
STDataType* a;//栈
int top;//栈顶
int capacity;//容量,方便增容
}Stack;
这里定义了一个结构体,并给它起了一个别名Stack
。结构体中包含三个成员:
a
:这是一个指向STDataType
类型的指针,用于存储栈中的元素。它实际上是一个数组,用来存放栈中的各个元素。top
:这是一个整数,表示栈顶的位置。在栈中,通常使用top
来追踪栈顶元素的位置。当栈为空时,top
的值可能是-1或其他表示空栈的值;当栈不为空时,top
的值是栈顶元素的索引。capacity
:这也是一个整数,表示栈的容量。这通常用于动态数组实现的栈,以便在栈满时知道何时需要扩容。
栈的基本操作
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
初始化栈
//初始化栈
void StackInit(Stack* pst)
{
assert(pst);
pst->a = (STDataType*)malloc(sizeof(STDataType)* 4);//初始化栈可存储4个元素
pst->top = 0;//初始时栈中无元素,栈顶为0
pst->capacity = 4;//容量为4
}
通过这个函数,我们实现了对栈的初始化
销毁栈
因为栈的内存空间是动态开辟出来的,当我们使用完后必须释放其内存空间,避免内存泄漏。
//销毁栈
void StackDestroy(Stack* pst)
{
assert(pst);
free(pst->a);//释放栈
pst->a = NULL;//及时置空
pst->top = 0;//栈顶置0
pst->capacity = 0;//容量置0
}
入栈
压栈:栈的插入操作叫做进栈 / 压栈 / 入栈。(入数据在栈顶)
进行入栈操作前,我们需要检测栈的当前状态,若已满,则需要先对其进行增容,然后才能进行入栈操作。
//入栈
void StackPush(Stack* pst, STDataType x)
{
assert(pst);
if (pst->top == pst->capacity)//栈已满,需扩容
{
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType)*pst->capacity *2);
if (tmp == NULL)
{
printf("realloc fail\n");
exit(-1);
}
pst->a = tmp;
pst->capacity *= 2;//栈容量扩大为原来的两倍
}
pst->a[pst->top] = x;//栈顶位置存放元素x
pst->top++;//栈顶上移
}
出栈
出栈:栈的删除操作叫做出栈。(出数据也在栈顶)
出栈操作比较简单,即让栈顶的位置向下移动一位即可。但需检测栈是否为空,若为空,则不能进行出栈操作。
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));//检测栈是否为空
pst->top--;//栈顶下移
}
获取栈顶元素
获取栈顶元素,即获取栈的最上方的元素。若栈为空,则不能获取。
//获取栈顶元素
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(!StackEmpty(pst));//检测栈是否为空
return pst->a[pst->top - 1];//返回栈顶元素
}
检测栈是否为空
检测栈是否为空,即判断栈顶的位置是否是0即可。若栈顶是0,则栈为空。
//检测栈是否为空
bool StackEmpty(Stack* pst)
{
assert(pst);
return pst->top == 0;
}
获取栈中有效元素个数
因为top记录的是栈顶,使用top的值便代表栈中有效元素的个数。
//获取栈中有效元素个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->top;//top的值便是栈中有效元素的个数
}