一.什么是栈
栈是一种重要的线性结构,从数据结构看,它也是一种线性表,其特殊性体现在它是操作受限的线性表:只允许在一端进行元素的插入和删除操作。我们将插入和删除元素的一端称为栈顶,另一端称为栈底。
因为只允许在一端进行插入删除操作,所以栈的数据元素遵循后进先出LIFO(last in first out)的原则。
大家可以将栈想象成一个书箱,数据元素就像一本本书一样,只能从顶部放书或取书,最后放进去的书,最先拿到。
或者如视频演示:
栈后进先出演示
二.栈的实现
由干栈本身就是一个线性表,那么前几篇文章我们讨论的线性表的顺序存储和链式存储对于栈来说也是同样适用的。
顺序存储实现就相当于顺序表的尾插尾删,不需要移动元素并且结构简单。
链式存储实现如果是单向链表,用头做栈顶更好,如果是双向链表,用尾做栈顶更好。
本文讲述的是栈的顺序存储实现。
1.栈结构定义:
我们使用动态数组存储数据元素,这样空间不够时可以动态申请空间。
然后定义一个top变量来指示栈顶元素在数组中的位置,top就如同中学物理学过的游标卡尺的游标,如下图,它可以来回移动,意昧着栈顶的top可以根据数据元素入栈、出栈移动,但无论如何游标不能超出尺的长度。同理,若栈的长度为capacity,则栈顶位置top必须小于等于capacity,通常把空栈的判定条件定为top等于0。
typedef int STDataType;
typedef struct Stack
{
STDataType* _arr; //数组
int top; //栈顶下标
int capacity; //容量大小
}ST;
2.栈的初始化:
与顺序表类似,使用malloc函数为数组申请一块空间,将top置为0,表示初始时数组下标为0的位置是栈顶(也可以赋别的值),capacity的大小与malloc申请的空间大小一致。
//初始化函数
void StackInit(ST* ps)
{
assert(ps);
ps->_arr = (STDataType*)malloc(sizeof(STDataType) * 4);//初始给4个STDataType大小的空间
//检查是否成功申请空间
if (!ps->_arr)
{
perror("malloc fail!");
exit(1);
}
//走到这里说明申请成功
ps->capacity = 4;
ps->top = 0;
}
3.栈的销毁:
栈的销毁即将动态申请的空间释放掉,将指针置为空,其余都置为0即可。
//销毁函数
void StackDestory(ST* ps)
{
assert(ps);
free(ps->_arr);
ps->_arr = NULL;
ps->capacity = ps->top = 0;
}
4.插入函数:
因为栈这个结构规定只能在栈顶插入,所以不存在头插,尾插,任意位置插入这些函数。
插入元素的操作也被称为入栈。
入栈前先检查空间是否足够,不足的话一般二倍扩容。容量足够则直接在栈顶插入数据即可(注意插入完数据之后将栈顶位置++)。
//入栈(插入函数)
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//入栈前先检查容量
if (ps->capacity == ps->top)
{
//一般二倍扩容
STDataType* tmp = (STDataType*)realloc(ps->_arr, ps->capacity * 2 * sizeof(STDataType));
//检查是否成功申请空间
if (!tmp)
{
perror("realloc fail!");
exit(1);//直接退出程序
}
//走到这里说明成功扩容
ps->_arr = tmp;
ps->capacity *= 2;
}
ps->_arr[ps->top] = x;
ps->top++;
}
5.删除函数:
因为栈这个结构规定只能在栈顶删除,所以不存在头删,尾删,任意位置删除这些函数。
删除元素的操作也被称为出栈。
注意栈为空时不能出栈,可以直接返回,也可以使用断言。
//出栈(删除函数)
void StackPop(ST* ps)
{
assert(ps);
//如果栈为空不能出栈
if (ps->top == 0)
{
return;
}
else
ps->top--;
}
6.求栈顶元素:
求栈顶元素直接返回数组中栈顶位置的下标即可,栈为空无栈顶元素。
//求栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
//栈为空不能求栈顶元素
assert(ps->top > 0);
return ps->_arr[ps->top - 1];
}
7.求数据个数:
求数据个数直接返回栈顶值即可。
//求数据个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
8.判空:
直接根据栈顶位置是否为0判断是否为空。
//判空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//栈为空时返回true,不为空时返回false
}
三.总结
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。
在我们软件应用中,栈这种后进先出的数据结构的应用是非常普遍的。比如你用浏览器上网时,不管什么测览器都有一个‘‘后退’’键,你单击后可以按访问顺序的逆序加载浏览过的网页。很多类似软件,比如word、Photoshop等文档或图像编辑软件,都有撤销(undo)的操作,也是用栈这种方式来实现的,当然不同的软件具体实现代码会有很大差异,不过原理其实都是—样的。
以下是全部源码
Stack.h:
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<stdlib.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* _arr; //数组
int top; //栈顶下标
int capacity; //容量大小
}ST;
//初始化函数
void StackInit(ST* ps);
//销毁函数
void StackDestory(ST* ps);
//入栈(插入函数)
void StackPush(ST* ps, STDataType x);
//出栈(删除函数)
void StackPop(ST* ps);
//求栈顶元素
STDataType StackTop(ST* ps);
//求数据个数
int StackSize(ST* ps);
//判空
bool StackEmpty(ST* ps);
Stack.c:
#include"Stack.h"
//初始化函数
void StackInit(ST* ps)
{
assert(ps);
ps->_arr = (STDataType*)malloc(sizeof(STDataType) * 4);//初始给4个STDataType大小的空间
//检查是否成功申请空间
if (!ps->_arr)
{
perror("malloc fail!");
exit(1);
}
//走到这里说明申请成功
ps->capacity = 4;
ps->top = 0;
}
//销毁函数
void StackDestory(ST* ps)
{
assert(ps);
free(ps->_arr);
ps->_arr = NULL;
ps->capacity = ps->top = 0;
}
//入栈(插入函数)
void StackPush(ST* ps, STDataType x)
{
assert(ps);
//入栈前先检查容量
if (ps->capacity == ps->top)
{
//一般二倍扩容
STDataType* tmp = (STDataType*)realloc(ps->_arr, ps->capacity * 2 * sizeof(STDataType));
//检查是否成功申请空间
if (!tmp)
{
perror("realloc fail!");
exit(1);//直接退出程序
}
//走到这里说明成功扩容
ps->_arr = tmp;
ps->capacity *= 2;
}
ps->_arr[ps->top] = x;
ps->top++;
}
//出栈(删除函数)
void StackPop(ST* ps)
{
assert(ps);
//如果栈为空不能出栈
if (ps->top == 0)
{
return;
}
else
ps->top--;
}
//求栈顶元素
STDataType StackTop(ST* ps)
{
assert(ps);
//栈为空不能求栈顶元素
assert(ps->top > 0);
return ps->_arr[ps->top - 1];
}
//求数据个数
int StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
//判空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//栈为空时返回true,不为空时返回false
}
test.c:
#include"Stack.h"
//测试各个函数
void test01()
{
ST st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
//入栈后依次取栈顶数据观察
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
//取完栈顶元素后出栈,这样下次才能取到后续的元素
StackPop(&st);
}
StackDestory(&st);
}
int main()
{
test01();
return 0;
}
感谢阅读!