栈(Stack)
是一种重要的数据结构,它具有以下特点和用途:
-
后进先出(LIFO)原则:栈的最基本特性是后进先出,也就是最后添加到栈中的元素会是第一个被移除的元素。这种特性让栈非常适合处理那些需要按顺序记录然后逆序处理的数据。
-
操作限制:栈主要支持两种操作:压栈(push),即在栈顶添加一个元素;和弹栈(pop),即移除栈顶的元素。还可以进行的操作包括查看栈顶元素(peek/top)和检查栈是否为空。
-
应用广泛:栈在计算机科学中有广泛的应用,如在算法中进行递归调用的处理、程序的函数调用堆栈、表达式求值(例如计算器中的操作)、回溯算法(如迷宫求解、深度优先搜索等)等。
-
简单高效:由于栈的操作非常简单(只在一个端点操作),它的各种操作通常都非常高效,大部分时间复杂度为 O(1),即常数时间。
-
内存管理:在一些编程语言中,栈用于管理局部变量的内存。这些变量只在函数调用期间存在,并在函数调用结束时自动被移除。
-
实现方式:栈可以通过数组或链表实现。数组实现的栈具有固定的大小,而链表实现的栈则可以动态地增长。
下面我们来实现这个栈吧
头文件
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top; // 标识栈顶位置的
int capacity;
}ST;
void STInit(ST* pst);
void STDestroy(ST* pst);
// 栈顶插入删除
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
bool STEmpty(ST* pst);
int STSize(ST* pst);
这段代码是一个用C语言编写的栈(Stack)的实现。首先,我们看到包含了几个头文件:stdio.h
用于标准输入输出,assert.h
用于断言,stdlib.h
提供了动态内存分配、随机数生成等功能,而 stdbool.h
用于处理布尔类型。
接下来定义了一个数据类型 STDataType
,在这个代码中它被定义为 int
类型,用于表示栈中存储的数据类型。
然后,定义了一个 Stack
结构体,这个结构体有三个成员:一个指向 STDataType
类型的指针 a
(用于存储栈的数据),一个整型 top
(用于标识栈顶的位置),以及一个整型 capacity
(表示栈的容量)。
之后是关于栈的一系列操作函数:
STInit(ST* pst)
:初始化栈。STDestroy(ST* pst)
:销毁栈,释放资源。STPush(ST* pst, STDataType x)
:向栈顶插入一个元素。STPop(ST* pst)
:从栈顶删除一个元素。STTop(ST* pst)
:获取栈顶元素。STEmpty(ST* pst)
:检查栈是否为空。STSize(ST* pst)
:返回栈中元素的数量。
初始化栈
void STInit(ST* pst) {
pst->a = NULL;
pst->top = 0;
pst->capacity = 0;
}
-
参数:函数接受一个指向
ST
结构体的指针pst
作为参数。这意味着它操作的是一个已经存在的ST
类型的实例。 -
初始化
a
:pst->a = NULL;
这一行将栈的数据指针设置为NULL
。这表示在初始化时,栈是空的,没有分配任何内存来存储数据。 -
初始化
top
:pst->top = 0;
这行代码将栈顶指标top
设置为0
。在栈的上下文中,top
通常用来表示下一个可插入元素的位置,或当前栈顶元素的位置。在初始化时,由于栈是空的,所以top
被设置为0
。 -
初始化
capacity
:pst->capacity = 0;
这行代码将栈的容量设置为0
。这意味着在初始化阶段,栈没有分配任何内存来存储数据,因此其容量为零。
栈的销毁
void STDestroy(ST* pst) {
assert(pst);
free(pst->a);
pst->top = 0;
pst->capacity = 0;
}
-
断言检查:
assert(pst);
这一行是一个安全检查,确保传入的栈指针pst
不是NULL
。如果pst
是NULL
,这个断言将失败,并导致程序终止。这是一种常见的调试手段,用于在开发阶段捕捉错误。 -
释放内存:
free(pst->a);
这行代码释放了栈pst
的数据数组a
所占用的内存。在栈初始化或在其生命周期中通过动态内存分配函数(如malloc
,realloc
等)分配了内存,那么在销毁栈的时候就需要使用free
函数来释放这块内存。这是防止内存泄漏的关键步骤。 -
重置栈的状态:随后的两行代码
pst->top = 0;
和pst->capacity = 0;
将栈的顶部索引和容量重置为0
。这意味着栈被视为一个空栈,没有元素,也没有为存储元素分配内存空间。
栈顶插入和删除
// 栈顶插入删除
void STPush(ST* pst, STDataType x) {
assert(pst);
if (pst->top == pst->capacity) {
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDataType* tmp = (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL) {
perror("realloc failed");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
pst->a[pst->top] = x;
pst->top++;
}
//尾删,栈顶删除
void STPop(ST* pst) {
assert(pst);
assert(pst->top > 0);
pst->top--;
}
STPush 函数
STPush
函数用于向栈中插入一个新元素。它的具体工作流程如下:
-
断言检查:使用
assert(pst)
确保传入的栈指针pst
不是NULL
。 -
容量检查和扩展:
- 首先检查栈是否已满(即
pst->top == pst->capacity
)。如果已满,需要扩展栈的容量。 - 计算新容量:如果当前容量是
0
,则新容量设为4
;否则,将容量加倍。 - 使用
realloc
函数调整栈的存储空间大小。realloc
尝试重新分配内存块,并将其扩展到新的大小。 - 如果
realloc
返回NULL
,表示内存分配失败。这时会打印错误信息,并提前返回,不会执行插入操作。 - 更新栈的存储空间指针
pst->a
和容量pst->capacity
。
- 首先检查栈是否已满(即
-
插入元素:将新元素
x
放置在栈顶的位置,即pst->a[pst->top]
。 -
更新栈顶位置:增加
pst->top
的值,以指向下一个空闲位置。
STPop 函数
STPop
函数用于从栈中删除栈顶元素。其操作较为简单:
-
断言检查:
- 使用
assert(pst)
确保传入的栈指针pst
不是NULL
。 - 使用
assert(pst->top > 0)
确保栈中至少有一个元素可供删除。
- 使用
-
删除元素:通过减少
pst->top
的值来实现删除操作。在栈中,这通常意味着仅仅减少指向栈顶元素的指针或索引。实际上并没有从内存中删除元素,但被视为从栈中移除了。
栈顶元素的读取
STDataType STTop(ST* pst) {
assert(pst);
assert(pst->top > 0);
return pst->a[pst->top-1];
}
-
断言检查:
assert(pst);
这个断言确保传入的栈指针pst
不是NULL
。如果pst
为NULL
,程序将因断言失败而终止。这是一种防御性编程手段,用于捕捉程序中的错误。assert(pst->top > 0);
这个断言确保栈不为空(即栈中至少有一个元素)。在空栈上尝试获取栈顶元素是不合法的操作,所以如果pst->top <= 0
,程序同样会因断言失败而终止。
-
获取栈顶元素:
return pst->a[pst->top-1];
这行代码返回栈顶的元素。由于pst->top
指向下一个可插入元素的位置,因此栈顶元素实际上位于pst->top-1
的位置。
检查栈是否为空
bool STEmpty(ST* pst) {
assert(pst);
return pst->top == 0;
}
检查栈是否为空:return pst->top == 0;
这行代码检查栈的 top
成员是否为 0
。在栈的上下文中,top
表示栈顶的位置,也就是下一个可插入元素的位置。如果 top
为 0
,这意味着栈中没有任何元素,即栈为空。函数返回一个布尔值:如果栈为空,则返回 true
;否则返回 false
。
栈的大小判定
int STSize(ST* pst) {
assert(pst);
return pst->top;
}
返回栈的大小:return pst->top;
这行代码返回栈的 top
成员的值。在这个栈的实现中,top
成员不仅代表栈顶元素的位置,也隐含地表示了栈中元素的数量。因为每次添加元素时,top
的值会增加,每次移除元素时,top
的值会减少。
测试
int main()
{
ST s;
STInit(&s);
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
printf("%d ", STTop(&s));
STPop(&s);
printf("%d ", STTop(&s));
STPop(&s);
STPush(&s, 4);
STPush(&s, 5);
while (!STEmpty(&s))
{
printf("%d ", STTop(&s));
STPop(&s);
}
printf("\n");
return 0;
}