1 定义
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。(度娘百科)
栈是一种中的数据结构,在做函数调用的时候存储断点,以及处理递归问题时都要用到栈。
在计算机系统中,栈则是一个具有以上属性的动态内存区域。在函数间进行互相调用时,程序将函数相关的变量环境信息压入栈中,也可以将数据从栈顶弹出。
在现实世界中,也有很多栈结构的体现。比如像枪械的弹匣就是一个标准的栈结构。装弹时子弹先后被压入弹匣,使用时后装入的子弹即顶部的子弹会先被射出,体现了先进后出的原则。
除此之外,经典的汉诺塔游戏也是根据栈结构规则设计的游戏。
2 顺序存储栈
栈是一种逻辑结构,定义了先进后出的规则。在前面章节中讲到,任何逻辑结构在计算机中都是通过两种物理结构来存储:顺序存储和连式存储。两种物理结构都可以用来实现抽象的逻辑结构,并且各有优劣。首先介绍如何使用顺序存储的方式实现栈结构。
2.1 结构定义
/// 设置栈的长度
#define MAXSIZE 20
/// 栈结构
typedef struct {
ElementType data[MAXSIZE]; // 顺序存储的数组来l保存栈的数据元素
int top; // 栈顶下标
} Stack;
2.2 栈常用方法
/// 初始化栈
static Status stackInit(Stack *s) {
// 数据不需要抹除,只需改变栈顶位置
s->top = -1;
return SUCCESS;
}
/// 清空栈
static Status stackClear(Stack *s) {
// 同初始化栈,不用管数据
s->top = -1;
return SUCCESS;
}
/// 判断栈是否为空
static int stackIsEmpty(Stack s) {
if (s.top == -1) {
return 1;
}
return 0;
}
/// 获取栈的长度
static int stackLength(Stack s) {
return s.top + 1;
}
/// 获取栈顶元素
static Status getTopElement(Stack s, ElementType *e) {
if (s.top == -1) {
return ERROR;
}
// 栈顶元素
*e = s.data[s.top];
return SUCCESS;
}
/// 遍历打印栈的数据元素
static Status stackTraverse(Stack s) {
int i = 0;
printf("Stack: ");
while (i <= s.top) {
printf("%d ", s.data[i]);
i++;
}
printf("\n");
return SUCCESS;
}
2.3 入栈
/// 数据元素入栈
static Status stackPush(Stack *s, ElementType e) {
// 栈溢出判断
if (s->top >= MAXSIZE - 1) {
return ERROR;
}
// 改变栈顶位置
s->top++;
// 栈顶设置新的数据
s->data[s->top] = e;
return SUCCESS;
}
2.4 出栈
/// 数据元素出栈
static Status stackPop(Stack *s, ElementType *e) {
// 空栈判断
if (s->top < 0) {
return ERROR;
}
// 返回数据元素的数据
*e = s->data[s->top];
// 改变栈顶位置
s->top--;
return SUCCESS;
}
2.5 使用
int main() {
Stack s;
printf("栈初始化\n");
stackInit(&s);
stackTraverse(s);
if (stackIsEmpty(s)) {
printf("\n栈是空的\n");
} else {
printf("\n栈不为空\n");
}
printf("\n构建栈\n");
for (int i = 0; i < 6; i++) {
ElementType element = i * 3 + 2;
stackPush(&s, element);
}
stackTraverse(s);
printf("栈的元素个数为 %d\n", stackLength(s));
ElementType topE;
getTopElement(s, &topE);
printf("栈顶的元素数据为 %d\n", topE);
ElementType e = 666;
printf("\n入栈 ← %d\n", e);
stackPush(&s, e);
stackTraverse(s);
ElementType e1;
stackPop(&s, &e1);
printf("\n出栈 → %d\n", e1);
stackTraverse(s);
ElementType e2;
stackPop(&s, &e2);
printf("\n出栈 → %d\n", e2);
stackTraverse(s);
printf("\n清空栈 x\n");
stackClear(&s);
stackTraverse(s);
return 0;
}
打印结果:
栈初始化
[栈顶-栈底]Stack:
栈是空的
构建栈
[栈顶-栈底]Stack: 2 5 8 11 14 17
栈的元素个数为 6
栈顶的元素数据为 17
入栈 ← 666
[栈顶-栈底]Stack: 2 5 8 11 14 17 666
出栈 → 666
[栈顶-栈底]Stack: 2 5 8 11 14 17
出栈 → 17
[栈顶-栈底]Stack: 2 5 8 11 14
清空栈 x
[栈顶-栈底]Stack:
3 链式存储栈
3.1 结构定义
/*
/// 设置栈的长度
#define MAXSIZE 20
/// 顺序存储栈的结构
typedef struct {
ElementType data[MAXSIZE]; // 顺序存储的数组来l保存栈的数据元素
int top; // 栈顶下标
} Stack;
*/
/// 栈节点结构
/// next 指向先入栈的元素
struct StackNode {
ElementType data; // 数据域
struct StackNode *next; // 后继指针
};
typedef struct StackNode *StackNodePtr;
/// 链式存储栈的结构
typedef struct {
StackNodePtr top; // 栈顶数据元素
int count; // 栈的数量
} Stack;
链式存储相对于顺序存储的优势在于不受初始空间的限制,所以不需要设置 MAXSIZE。
首先定义栈中数据元素的结构 struct StackNode。可以根据需要使用双向链表来定义节点。
在节点的基础上定义栈的结构。相对于顺序存储栈的结构,链式存储栈保存的是栈顶元素与元素个数,要区别记忆。
3.2 栈常用方法
/// 初始化栈
static Status stackInit(Stack *s) {
s->top = NULL;
s->count = 0;
return SUCCESS;
}
/// 清空栈
static Status stackClear(Stack *s) {
StackNodePtr top = s->top;
StackNodePtr temp;
// 释放所有数据元素
while (top) {
temp = top;
top = top->next;
free(temp);
}
// 修改栈顶,避免野指针
s->top = NULL;
// 修改数量
s->count = 0;
return SUCCESS;
}
/// 判断栈是否为空
static int stackIsEmpty(Stack s) {
if (s.count <= 0) {
return 1;
}
return 0;
}
/// 获取栈的长度
static int stackLength(Stack s) {
return s.count;
}
/// 获取栈顶元素
static Status getTopElement(Stack s, ElementType *e) {
if (s.count <= 0) {
return ERROR;
}
// 栈顶元素
*e = s.top->data;
return SUCCESS;
}
3.3 入栈
/// 数据元素入栈
static Status stackPush(Stack *s, ElementType e) {
// 栈溢出判断
if (s->top >= MAXSIZE - 1) {
return ERROR;
}
// 改变栈顶位置
s->top++;
// 栈顶设置新的数据
s->data[s->top] = e;
return SUCCESS;
}
3.4 出栈
/// 数据元素出栈
static Status stackPop(Stack *s, ElementType *e) {
// 空栈判断
if (s->count <= 0) {
return ERROR;
}
// 获取栈顶数据元素
StackNodePtr top = s->top;
// 释放前改变栈顶位置
s->top = top->next;
// 返回数据元素的数据
*e = top->data;
// 释放数据元素
free(top);
// 改变栈的数量
s->count--;
return SUCCESS;
}
2.5 使用
int main() {
Stack s;
printf("栈初始化\n");
stackInit(&s);
stackTraverse(s);
if (stackIsEmpty(s)) {
printf("\n栈是空的\n");
} else {
printf("\n栈不为空\n");
}
printf("\n构建栈\n");
for (int i = 0; i < 6; i++) {
ElementType element = i * 3 + 2;
stackPush(&s, element);
}
stackTraverse(s);
printf("栈的元素个数为 %d\n", stackLength(s));
ElementType topE;
getTopElement(s, &topE);
printf("栈顶的元素数据为 %d\n", topE);
ElementType e = 666;
printf("\n入栈 ← %d\n", e);
stackPush(&s, e);
stackTraverse(s);
ElementType e1;
stackPop(&s, &e1);
printf("\n出栈 → %d\n", e1);
stackTraverse(s);
ElementType e2;
stackPop(&s, &e2);
printf("\n出栈 → %d\n", e2);
stackTraverse(s);
printf("\n清空栈 x\n");
stackClear(&s);
stackTraverse(s);
return 0;
}
打印结果:
栈初始化
[栈顶-栈底]Stack:
栈是空的
构建栈
[栈顶-栈底]Stack: 17 14 11 8 5 2
栈的元素个数为 6
栈顶的元素数据为 17
入栈 ← 666
[栈顶-栈底]Stack: 666 17 14 11 8 5 2
出栈 → 666
[栈顶-栈底]Stack: 17 14 11 8 5 2
出栈 → 17
[栈顶-栈底]Stack: 14 11 8 5 2
清空栈 x
[栈顶-栈底]Stack:
参考文章:十张GIFs让你弄懂递归等概念