数据结构之栈
Posted 捕获一只小肚皮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构之栈相关的知识,希望对你有一定的参考价值。
文章目录
前言
经历了算法效率(复杂度)后,我们开始学习了单双链表,然后到达了现在,而此次博主要讲解的便是数据结构中的
栈
,
栈
是什么样子的呢? 它又具有哪些特性 ? 可以做哪些事情呢 ? 等一系列问题,博主将会在下面意一一进行解答.
栈是什么样子?
既然出现了栈这种数据结构,我们就需要知道它是什么样子. 那么,
栈
到底是什么样子呢?答 : 数据
先进入后出来
; 请看下图:
通过动图我们清晰的窥见了栈
的结构,那现在我们就需要用一些专业术语进行描述栈
的行为和特性
栈
:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作.- 进行数据插入和删除操作的一端称为
栈顶
,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则.- 压栈:栈的插入操作叫做
进栈/压栈/入栈
,入数据在栈顶
。- 出栈:栈的删除操作叫做
出栈
。出数据也在栈顶
清楚怎样实现栈
还记得博主当时讲解顺序表时候,提到的
逻辑结构
和物理结构
吗? 上面我们所看见的栈的示意图就是我们的逻辑结构
,而逻辑结构
只是方便我们理解,我们需要用某种方法去实现它,这,就是我们的物理结构
了.那我们怎么去实现呢? 大家再想想我们在讲解
顺序表
和链表
时候,它们各有优势,具有哪些优势呢?
- 顺序表: 地址连续,尾插尾删即为方便,但是其他地方头插头删不方便.— --- —实现方法:
malloc
开辟数组- 链表: 头插头删即为方便,但是尾插尾删不方便(双链表除外)— --- —实现方法:链接单个动态空间
现在我们回头过来看看,对于栈来说,我们最主要用的是什么操作?? 答案显而易见,我们用的是尾删尾插
操作,所以我们怎么实现栈
呢?
答曰: 与顺序表一样,利用malloc
开辟数组.
栈结构的代码实现
清楚了怎样实现
栈
,我们就可以定义栈
结构了.
typedef int STDataType; //方便以后修改存储元素的类型
typedef struct Stack
{
STDataType* data; //接收动态开辟的数组地址
int top; //栈顶,代表此时栈空间中的实际元素数量
int capacity; //动态开辟的容量
}ST; //修改个简短的名字
大家有木有发现,栈
的定义与顺序表
几乎是一模一样的???,哈哈哈,没错,栈
的结构和顺序表
就是几乎一样,只是它少了一些方法而已,主要突出尾插尾删
栈项目文件搭建
老规矩,博主习惯用
vs2019
,这里的讲解仍然用这个编译器.既然是我们自己手动实现
栈
,那下面开始建立项目吧~需要怎么写呢?看过博主前几篇的人可能已经知道了,那就是
3板斧:
- 建立头文件
Stack.h
: 用来定义结构体,引头文件,函数的声明- 建立源文件
Stack.c
: 用来写函数定义- 建立测试文件
test.c
: 用来测试各个函数是否成功
如图:
栈的各种操作实现
栈的主要操作有哪些?
答曰:
入栈, 出栈, 判空, 获取栈顶元素,获取栈元素数量
而为了实现上面主要操作,我们还少不了一些基本操作:
检查容量, 初始化等
下面博主一次性声明完所有函数方法,至于为什么这样声明,大家不要着急,在定义函数时候会讲解.
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* data;
int top;
int capacity;
}ST;
void StackInit(ST* ps); //初始化
void StackCheckCapacity(ST* ps); //检查容量
void StackPop(ST* ps); //尾删
void StackPush(ST* ps,STDataType elem); //尾插
bool StackEmpty(ST* ps); //判空
size_t StackSize(ST* ps);//栈元素数量
STDataType StackTop(ST* ps);//获取栈顶元素
void StackDestroy(ST* ps);//销毁空间
1 栈之初始化
由于结构体ST是把动态数组包含住的,所以我们在外面就只需要定义一个ST 结构体变量就行了,然后传结构体变量的地址.
而我们初始化的内容是 需要把
data
指针置为NULL
,top
和capacity
初始化为0.
代码:
void StackInit(ST* ps)
{
assert(ps); //传进来的地址一定不能为空
ps->data = NULL;
ps->top = ps->capacity = 0;
}
测试是否成功:
成功!!!
2 栈之检查容量
为什么要检查容量呢? 因为我们不知道栈空间是否满了,如果满了就无法再
入栈
,所以需要检查.那我们在这里面要做的事情就是检查容量吗? 肯定不是哇~~,如果没有满容就不需要管,如果满了就需要增加空间了
那怎么判断容量是否满了呢?? 没错! 答案就是:
ps->top == ps->capacity
.
注意点
当满足上述条件时候,也不能一定说是满容哦,大家想想为什么~~~ 对,那就是top==capacity==0
void StackCheckCapacity(ST* ps)
{
assert(ps); //ps不可以为空指针
if(ps->top == ps->capacity)
{
//如果此时容量为0,就给4个,否则就翻倍
int newcapacity = (ps->capacity == 0 ? 4 : 2 * ps->capacity );
//注意这里要新给一个指针接收realloc调整的空间,至于为啥博主之前讲解过哦~下面有链接
STDataType* newdata = (STDataType*)realloc(ps->data,newcapacity*sizeof(STDataType));
if(newdata == NULL)
{
perror("错误原因:");
exit(-1);
}
ps->data = newdata; //重新交接给ps->data
ps->capacity = newcapacity;
}
}
解释: 有人会问上面代码为什么要用一个newcapacity
变量接收新的空间数量再去下面调整,大家想想,如果空间本就是0,你去下面调整管用吗?不管用,因为0乘以任何数还是0,永远也开辟不了空间
测试:
成功~~
3 栈之判空
栈什么时候为空呢? 这个实在简单,博主就不太详细解释了,直接上代码
void StackPush(ST* ps,STDataType elem)
{
assert(ps); //ps不能为空
return ps->top == 0; //如果等于0就返回真,如果不等于就返回假
}
4 栈之尾插
这个时候有人问了,肚皮~,为何你的函数声明名字写成
StackPush
而不写成StackPushBack
呢? 因为栈只有一个插入操作啊,虽然也是尾插,但是忘记了栈有一个专业术语叫入栈
吗? 既然是入,写成push
是不是更符合语义呢?
既然是插入数据,所以函数的形参设置就多了个需要插入的形参elem
void StackPush(ST* ps,STDataType elem)
{
assert(ps); //ps一定不可以为空
StackCheckCapacity(ps); //检查容量是否满了
ps->data[ps->top++] = elem;
}
测试:
成功!!
5 栈之尾删
尾删很简单,只需要
ps->top
减去1就行,不需要真的删除,因为我们能访问的权限最高就是ps->top
但是有个注意点就是,如果栈已经空了,就不可以删除了
另外,对于名字比较疑惑的,参照上面的尾删
void StackPop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
6 栈之获取元素数量
这个也是过于简单,直接把
ps->top
的值返回就行
size_t StackSize(ST* ps)
{
assert(ps);
return ps->top;
}
7 栈之读取栈顶
这个函数的定义比较简单,直接通过
ps->top-1
的索引,把值返回,但是注意栈为空的时候哦
STDataType StackTop(ST* ps)
{
assert(ps);
assert(!StackEmpty(ps));
return ps->data[ps->top - 1];
}
8 栈之销毁空间
直接free掉data所指向的空间即可
void StackDestroy(ST* ps)
{
assert(ps);
free(ps->data);
//当ps->data是NULL时候,free什么都不会做
ps->data = NULL;
ps->top = ps->capacity = 0;
}
测试:
成功!!!
以上是关于数据结构之栈的主要内容,如果未能解决你的问题,请参考以下文章