目录
一,栈的介绍
我们都知道一个杀伤力极大的武器——枪。枪击案、枪击事件屡见不鲜,即便没有见过,也会有各色各样的玩具枪,模型枪。我们今天要带来的线性表之一——栈,它类似于子弹放入弹夹一样:先进去的子弹后出来,后进去的子弹先出来。
在我们软件应用中,栈这种先进后出的数据结构的应用是非常广的。比如我们使用的浏览器
在我们浏览网页上网时,总会有一个“ 后退 ”键是以供我们使用的,假如你正在轻松的刷着视频,突然看到一个让自己很感兴趣的链接,当你迫不及待地打开它观看时却发现是假的,于是你就可以点击“后退”键返回到你的视频页面。即便你点击了十几个链接进行跳转,当你又连续单击”后退“键的时,就i可以回到之前浏览的某个页面。
对于栈的定义是:栈是限定仅在表尾进行插入和删除操作的线性表。
我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。
栈又称为后进先出的线性表,简称LIFO(Last in First out) 结构。
而定义中的表尾指的不是栈底,而是栈顶,这点要牢记。
对于栈的插入操作——即给栈中放入数据,我们称之为进栈,也叫压栈。好比子弹入弹夹
对于栈的删除操作——即删除栈中的数据,我们称之为出栈,也叫弹栈。好比子弹出弹夹
其两种操作图如下展示:(因为我们以顶部底部来描绘栈,所以我们竖直其模型方便理解)
那么是不是说最先进栈的元素只能最后出栈呢?答案是不一定的,比如当1,2,3三个元素依次进栈时,我们可以1进栈接着2接着3,出栈顺序就是3、2、1。但是我们也可以1进栈后直接出,然后2进栈,2出栈,3进栈,3出栈。也可以1进,2进,2出,1出,3进,3出等等。
(注意,这里的栈不是我们空间里的栈区,前者是数据结构,后者是空间内存区)
二,栈及其操作的实现
1. 初始化空间
由上图进栈出栈的图示可知,我们需要一个变量top来记录栈顶,由于是线性表,则需要一段连续的空间。
于是对栈的基本含参实现如下:
typedef int STDatatype;
typedef struct Stack
{
STDatatype* a; //开辟连续空间
int capacity; //栈的大小
int top; //栈内元素个数
}Stack;
我们在此基础上为其开辟四个大小为int的空间。
//初始化空间
void StackInit(Stack* ps)
{
assert(ps);
ps->a = (STDatatype*)malloc(sizeof(STDatatype) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->capacity = 4;
ps->top = 0;
}
因为要修改栈的大小,我们传入结构体指针。开辟完成后我们便得到了一个空栈,大小为16字节。
2. 进栈和出栈
我们先来实现前面所讲到的进出栈操作,这里我们push表示进栈,pop表示出栈。在初始化栈空间时,我们将top放置为0表示此时栈内没有元素,所以此时只需将元素进栈并在达到栈满容量时扩容即可,进栈实现如下:
void StackPush(Stack* ps,STDatatype x)
{
assert(ps);
//检查容量
if (ps->top == ps->capacity)
{
STDatatype* tmp = (STDatatype*)realloc(ps->a, 2 * ps->capacity * sizeof(STDatatype));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
//进栈
ps->a[ps->top] = x;
ps->top++;
}
可以观察到我们的栈不再是原来的空栈了,我们放入了1~5五个元素并且容量变为8,此时栈顶元素为Top,也同时表示着栈中有5个元素。
再来实现出栈效果,当我们为空栈时就不能进行出栈操作,所以我们需要判断栈是否是空栈。之后便是像我们顺序表删除元素类似的操作,实现如下:
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
可以观察到我们栈中元素只剩一个。
3. 栈的其余操作
作为一个拿的上台面的数据结构,它显然不能只有两种相关的操作,返回栈顶元素,返回栈中个数以及判断栈是否为空,销毁栈等四种操作也是极其重要的。接下来我们一一实现。
判断栈是否为空:
我们使用布尔值来反映情况,前面提到,top表示的就是栈的元素个数,所以当top为0时就是空栈,top不为零就不是空栈。相关实现如下:
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
返回栈顶元素:
在我们往后的学习中会有涉及到极其重要的知识——用栈来模拟递归。这时我们的返回栈顶元素操作就必不可少。注意,当栈为空栈时,我们就无需返回,所以这里可以复用我们空栈判断函数来对栈是否为空进行断言,实现如下:
STDatatype StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
在我们入栈五个元素并出栈四个元素后,可知栈内唯一元素就是1,所以此时我们可以提取栈顶元素。
返回栈的元素个数:
由于返回的数值一定是整数,所以我们返回值设置为int型,返回top即可。
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
销毁栈:
由于我们使用到malloc和realloc函数,它们是在栈空间上开辟内存我们仍然需要主动释放空间
实现如下:
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
三,整体代码
这里小编只附着Stack.c和Stack.h的文件,测试文件读者可以自己复制两个Stack文件后自行创建使用。
//Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDatatype;
typedef struct Stack
{
STDatatype* a;
int capacity;
int top;
}Stack;
void StackInit(Stack* ps);
void StackDestroy(Stack* ps);
void StackPush(Stack* ps, STDatatype x);
void StackPop(Stack* ps);
STDatatype StackTop(Stack* ps);
bool StackEmpty(Stack* ps);
int StackSize(Stack* ps);
//Stack.c
#include"Stack.h"
//初始化空间
void StackInit(Stack* ps)
{
assert(ps);
ps->a = (STDatatype*)malloc(sizeof(STDatatype) * 4);
if (ps->a == NULL)
{
perror("malloc fail");
exit(-1);
}
ps->capacity = 4;
ps->top = 0;
}
//销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void StackPush(Stack* ps,STDatatype x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDatatype* tmp = (STDatatype*)realloc(ps->a, 2 * ps->capacity * sizeof(STDatatype));
if (tmp == NULL)
{
perror("realloc fail");
exit(-1);
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
//assert();
ps->top--;
}
//返回栈顶元素
STDatatype StackTop(Stack* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top-1];
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
这便是栈的介绍啦,希望大家多多支持。