💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《数据结构与算法》
期待您的关注
目录
一、引言
🎄栈(Stack)是什么?
🎄为什么使用数组实现栈?
二、定义栈结构
🎄栈的结构
🎄栈顶位置的指向
三、实现栈的基本操作
🍃初始化
🍃销毁
🍃入栈
🍃出栈
🍃查看栈顶元素
🍃对栈判空
🍃获取有效数据个数
四、使用数组实现栈的C语言代码
stack.h 栈的头文件
stack.c 栈的实现源文件
test.c 主函数测试文件
测试结果
五、栈的应用
六、总结
一、引言
🎄栈(Stack)是什么?
- 栈是一种后进先出(LIFO, Last In First Out)的数据结构。
- 栈是一种只能在一端进行插入和删除操作的线性表。
- 允许进行插入和删除操作的一端称为栈顶(top),另一端称为栈底(bottom)。
- 栈中没有元素时,称为空栈。
- 栈的基本操作包括:push(入栈)、pop(出栈)、peek(查看栈顶元素)和isEmpty(判断栈是否为空)等。
🎄为什么使用数组实现栈?
- 数组是一种线性数据结构,能够连续存储数据,且通过索引可以方便地访问任意位置的元素。
- 因为栈只在栈顶增删,所以基于数组实现,既避免了插入需要移动数据的劣势,又保持了数组访问数据的优势,可以实现高效的栈操作。
二、定义栈结构
🎄栈的结构
- 指向数组的指针(动态开辟的空间)
- 标记栈顶位置的变量 top
- 标记栈的大小的变量 capacity
// 支持动态增长的栈
typedef int STDataType;//对数据类型重命名,方便后期修改类型
typedef struct Stack
{
STDataType* a;
int top; // 栈顶
int capacity; // 容量
}Stack;//定义结构同时重命名
🎄栈顶位置的指向
需要注意的是:top的指向应该始终保持一致性
1.如果top指向栈顶元素,初始不能为0,应该指向-1
2.如果top初始为0,其应该指向栈顶元素的下一个元素
对应的判定栈满和栈空有所不同
三、实现栈的基本操作
🍃初始化
- 对形参判空
- 数组指针初始指向空
- top和capacity初始化为0(这里top指向的是栈顶元素的下一个位置)
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
🍃销毁
- 对形参判空
- 释放数组空间
- 数组指针指向空
- top和capacity改为0
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
🍃入栈
判空
判断是否需要扩容(top和capacity相等)
扩容步骤: 空间二倍增长 ,更新数组指针和容量数据插入到top位置,top位置++
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
//判断是否需要扩容
if (ps->top == ps->capacity)
{
int newcapa = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapa);
if (tmp == NULL)
{
perror("realloc\n");
exit(1);
}
ps->a = tmp;
ps->capacity = newcapa;
}
//确定空间足够之后再插入数据
ps->a[ps->top] = data;
ps->top++;
}
🍃出栈
- 对形参判空
- 对栈判空
- top--
(该方法对于栈只存在一个元素的情况也可以正确处理)
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top);
ps->top--;
}
注意:
即使函数只有一两条语句也还是建议封装成函数,这样可以提高程序的可维护性和可读性
🍃查看栈顶元素
- 对形参判空
- 对栈判空
- 返回top前一个位置的元素
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top);
return ps->a[ps->top-1];
}
🍃对栈判空
- 对形参判空
- 返回top==0的结果(因为这里top指向的是栈顶元素的下一个元素,所以栈空时top==0)
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
🍃获取有效数据个数
- 对形参判空
- 返回top (top对应的下标是栈顶的下一个元素,top就是元素的个数)
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
四、使用数组实现栈的C语言代码
stack.h 栈的头文件
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
// 支持动态增长的栈
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);
stack.c 栈的实现源文件
#include"stack.h"
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
//判断是否需要扩容
if (ps->top == ps->capacity)
{
int newcapa = ps->capacity == 0 ? 4 : 2 * (ps->capacity);
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapa);
if (tmp == NULL)
{
perror("realloc\n");
exit(1);
}
ps->a = tmp;
ps->capacity = newcapa;
}
//确定空间足够之后再插入数据
ps->a[ps->top] = data;
ps->top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top);
ps->top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top);
return ps->a[ps->top-1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
test.c 主函数测试文件
#include"stack.h"
void test1()
{
Stack st ;
StackInit(&st);
if (StackEmpty(&st))
{
printf("栈空\n");
}
else
{
printf("栈非空\n");
}
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
if (StackEmpty(&st))
{
printf("栈空\n");
}
else
{
printf("栈非空\n");
}
printf("栈中元素个数:%d\n", StackSize(&st));
printf("%d\n", StackTop(&st));
StackPop(&st);
printf("%d\n", StackTop(&st));
StackPop(&st);
printf("%d\n", StackTop(&st));
StackPop(&st);
printf("%d\n", StackTop(&st));
StackPop(&st);
if (StackEmpty(&st))
{
printf("栈空\n");
}
else
{
printf("栈非空\n");
}
StackDestroy(&st);
}
int main()
{
test1();
return 0;
}
测试结果
五、栈的应用
- 函数调用栈:在程序执行过程中,函数调用是通过栈来实现的。每个函数调用时,其返回地址、局部变量和参数等信息都会被压入栈中,当函数返回时,这些信息会被弹出栈。
- 表达式求值:在编译器中,表达式求值通常使用栈来实现。例如,在解析算术表达式时,可以使用两个栈:一个用于存储操作数,另一个用于存储操作符。
- 浏览器历史记录:浏览器的“前进”和“后退”功能通常使用栈来实现。用户浏览的网页会被压入栈中,当用户点击“后退”按钮时,会从栈中弹出并显示上一个网页。
- 撤销操作:在许多图形编辑器和文本编辑器中,撤销操作通常使用栈来实现。每次编辑操作(如剪切、复制、粘贴等)都会被压入一个撤销栈中,当用户点击“撤销”按钮时,会从栈中弹出并执行相反的操作以撤销上一次编辑。
六、总结
- 使用数组实现栈是一种简单且高效的方法,能够充分利用数组的特性来实现栈的基本操作。
- 在实际应用中,栈具有广泛的应用场景,如函数调用栈、浏览器的前进后退功能以及表达式求值等。