目录
一、栈的定义
二、顺序栈 链栈比较
三、栈的实现(顺序栈)
3.1 ❥ 定义栈结构
3.2 ❥ 初始化
3.3 ❥ 销毁
3.4 ❥ 插入(入栈)
3.5 ❥ 删除 (出栈)
3.6 ❥ 获取栈顶元素
3.7 ❥ 判空
3.8 ❥ 获取数据个数
四、完整运行代码
stack.h
stack.c
test.c
一、栈的定义
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。
栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
- 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
- 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
举例:
生活中我们装羽毛球的筒就是后进先出的例子,后放进的羽毛球先拿出来。
二、顺序栈 链栈比较
栈可以用数组实现,也可以用单链表或者双向链表实现。
用数组实现的栈称为顺序栈,用链表实现的栈称为链式栈。
现在共有3种结构,选哪种更优呢?
首先排除双向链表。
因为单链表能实现,用双向链表就会浪费空间,少维护一个指针也更方便。
而数组和单链表实现栈各有好处,二者效果等同。
如果非要选择一个,选择数组更优一些。
数组的缺点只有一个:就是扩容。扩容虽然自身有消耗,但影响不大。况且并不是有数据插入就要扩容。
而数组的更优在于:cpu高速缓存命中率更高,所以选数组更好一些。(若是没有缓存率这一点,选链表)
三、栈的实现(顺序栈)
3.1 ❥ 定义栈结构
代码如下:
typedef int STDataType;
//定义栈结构
typedef struct stack
{
STDataType* a; //指向数组元素的指针
int top; //有效数据个数
int capacity; //容量大小
}ST;
3.2 ❥ 初始化
为了防止使用出现错误,首先我们要对栈进行初始化操作,构造一个空栈。
思路:
- 先将结构体变量的地址传给初始化函数
- 然后将结构体里的数组指针初始化为NULL
- 最后再把数据个数和容量大小都初始化为0
代码如下:
//初始化
void STInit(ST* ps)//传的是实参的地址,因为形参是实参的一份临时拷贝
{
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
易错点:关于top指针指向栈顶元素还是指向栈顶元素的下一个位置?
这里取决于自己定义。
- top如果指向栈顶元素,那初始化top的时候就不能为0 (因为top为0时无法确定到底是有数据还是没有数据)
- top如果指向栈顶元素的下一个,那初始化top的时候可以指向0
两种初始化方式想取哪一种都可以,但要搞清楚它们之间的关系(前后要求匹配)
3.3 ❥ 销毁
销毁的目的是:当我们使用完栈后,就要释放栈所占用的内存空间,还给操作系统
思路:
- 首先进行断言,防止传入空指针(空地址)
- 释放动态开辟的空间,并把指针置空,防止野指针发生未定义行为
- 最后把容量和数据个数置为0(也可以不管,但是一般为了规范,都会把所有的成员做清理,除非是在销毁函数中做访问操作会出现错误)
代码如下:
//销毁
void STDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
3.4 ❥ 插入(入栈)
思路:
- 我们要进行插入操作,就要动态申请空间,申请空间前,要先进行断言,防止传入空指针
- 动态开辟一块空间,进行插入操作。为了防止开辟空间不够,我们需要持续扩容,所以用realloc函数,且每次开辟空间为原来的2倍
- realloc前应先判断是否为首次开辟,因为若是头一次开辟的话,0*2一直为0,就等于没有开辟
- 所以这里我们用三目运算,若是头一次开辟,直接给4个空间大小;若不是,就2倍增长。
- 开辟完之后我们还要进行指针判断,防止开辟失败传入空指针
- 若开辟成功,我们将数据入栈(也就是写入数组)
代码如下:
//插入(入栈)
void STPush(ST* ps, STDataType x)
{
assert(ps);
//扩容 开辟空间
if (ps->capacity == ps->top)
{
// 三目运算符 等于0开辟4个字节 不等于0原空间大小*2
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
//开辟失败
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
//开辟成功
ps->capacity = newcapacity;
ps->a = tmp;
}
//入栈
ps->a[ps->top] = x;
ps->top++;
}
易错提醒:
开辟空间的条件这里写的是:ps->capacity==ps->top
原因:
因为我们这里是把top初始化为0了。当top初始化为0时,top跟size的意思一样(注意下标)
- 若top=-1,top+1=capacity
- 若top=0,top=capacity
哪种写法都可以,看初始化时top为-1还是0
3.5 ❥ 删除 (出栈)
思路:
- 删除前应先进行断言,防止传入空指针
- 还要断言栈内元素是否为空,如果为空的话,就没办法进行出栈操作
- 删除只需要将top指针往前挪动一位即可(因为top代表有效元素个数)
代码如下:
// 删除(出栈)
void STPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
插入删除端的固定:
具体插入删除端在哪里根据自己所选的结构有关。
- 数组:插入删除端在尾
- 单链表:插入删除端在头
- 双向链表:插入删除端在头尾都可以
3.6 ❥ 获取栈顶元素
思路:
- 获取之前先进行断言是否为空指针
- 也需要断言栈内是否有元素
- 然后返回top前一个下标位置(因为top用作下标表示的是栈顶下一个元素的位置)
代码如下:
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top--];
}
3.7 ❥ 判空
思路:
- 先进行断言
- 判断top是否为0,为0则栈空,不为0则栈不为空
代码如下:
//判断栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
3.8 ❥ 获取数据个数
思路:
- 先进行断言
- 返回top(top表示有效数据个数)
代码如下:
//获取数据个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
四、完整运行代码
stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
//定义栈结构
typedef struct stack
{
STDataType* a; //指向数组元素的指针
int top; //有效数据个数
int capacity; //容量大小
}ST;
//初始化
void STInit(ST*ps); //传的是实参的地址,因为形参是实参的一份临时拷贝
//销毁
void STDestory(ST*ps);
//插入(入栈)
void STPush(ST* ps, STDataType x);//插入端固定,只能在一端进行插入
// 删除(出栈)
void STPop(ST * ps);//删除端也固定,只能在一端进行删除
//获取栈顶元素
STDataType STTop(ST* ps);
//判断栈是否为空
bool STEmpty(ST* ps);
//获取数据个数
int STSize(ST* ps);
stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
//初始化
void STInit(ST* ps)
{
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//销毁
void STDestory(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//插入(入栈)
void STPush(ST* ps, STDataType x)
{
assert(ps);
//扩容 开辟空间
if (ps->capacity == ps->top)
{
// 三目运算符 等于0开辟4个字节 不等于0原空间大小*2
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
//开辟失败
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
//开辟成功
ps->capacity = newcapacity;
ps->a = tmp;
}
//入栈
ps->a[ps->top] = x;
ps->top++;
}
// 删除(出栈)
void STPop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top--];
}
//判断栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;
}
//获取数据个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
int main()
{
//创建结构体变量s
ST s;
//初始化
STInit(&s);
//插入(入栈)
STPush(&s, 1);
STPush(&s, 2);
STPush(&s, 3);
STPush(&s, 4);
// 删除(出栈)
STPop(&s);
//获取栈顶元素
STDataType ret=STTop(&s);
//判断栈是否为空
bool h=STEmpty(&s);
//获取数据个数
int size=STSize(&s);
//销毁
STDestory(&s);
return 0;
}