哈喽~大家!今天我们来学习栈的特别节目,精彩马上开始~
目录
前言
一、栈
1 栈的概念
2 栈的结构
3 栈的实现
3.1 栈的定义
3.2 栈的初始化
3.3 入栈
3.4 出栈
3.5 取栈顶元素
3.6 判断栈是否为空
3.7 栈的大小
3.8 栈的销毁
二、源代码
前言
栈和队列均是常见的数据结构。栈的特点是后进先出(即 Last In First Out(LIFO) ),其增删查元素都在栈顶实现。而队列的特点是先进先出,增加元素在队尾实现,删除和查看元素都在队首实现。本期我们来详细学习实现栈的结构。
一、栈
我们先根据下图直观地了解栈:
1 栈的概念
栈是一种特殊的线性表,特点是后进先出,它仅允许在固定一端进行插入和删除元素操作,最先加入的元素最后取出,最后加入的元素最先取出。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。文字描述难免过于死板,为了更好的帮助大家理解,附以下图解:
将数字 1 到 7 依次入栈之后,此时栈顶元素是 7 ,第一个出栈的元素是 7。
2 栈的结构
3 栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小。
一般栈的实现需要完成几种函数的操作:
- 栈的初始化
- 入栈
- 出栈
- 取出栈顶元素
- 判断栈是否为空
- 栈的销毁
//初始化
void StackInit(ST* ps);
//入栈
void StackPush(ST* ps, STDateType x);
//出栈
void StackPop(ST* ps);
//取栈顶元素
STDateType GetTop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//栈大小
int StackSize(ST* ps)
//栈的销毁
void StackDestory(ST* ps);
3.1 栈的定义
//这里我们实现的是动态的栈
typedef int STDateType; //方便数据类型的替换
typedef struct Stack
{
STDateType* a; //动态开辟数组
int top; //栈顶
int capacity; //容量,方便扩容
}ST;
首先,定义动态数组 a,采用动态数组的方式主要是便于后期容量的扩充;然后,定义一个变量 top 用于标识栈顶位置;最后,定义一个变量 capacity 用于统计栈可以容纳的数据个数。
3.2 栈的初始化
//初始化
void StackInit(ST* ps)
{
//判空
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
首先,将指向栈中数据内存空间的指针 a 初始化为NULL;然后,将指向栈顶元素的变量 top初始化为0;最后,再将标识栈当前容量的 capacity 初始化为0即可。
3.3 入栈
//入栈
void StackPush(ST* ps, STDataType x)
{
//判空
assert(ps);
//判断容量是否已满
//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
if (ps->top == ps->capacity)
{
//为空就开辟四个空间,不为空,就扩容至二倍
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//将新开辟的内存空间的首地址tmp赋值给a
ps->a = tmp;
//更新capacity
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
//top指向栈顶元素的下一个位置
ps->top++;
}
在入栈之前,首先需要对顺序栈的空间容量进行检查,这里使用三目运算符进行判断:若当前容量 capacity 为空,则开辟4个数据的内存空间;若当前容量非空但已满,则将 capacity 的大小扩容至 2*capacity。然后调用 realloc 函数开辟新的内存空间,并返回新的内存空间的起始地址 tmp。接着将新开辟的内存空间的起始地址 tmp 赋值给 a,使 a 指向这片新开辟的空间。最后,将 x 插入栈顶位置,并让 top 继续指向栈顶元素的下一个位置。
3.4 出栈
//出栈
void StackPop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//出栈
ps->top--;
}
在出栈之前,首先需要调用 StackEmpty(ps) 函数来判断栈是否为空,若不为空,则可以出栈。出栈操作就是将 top--,这里需要注意的是:出栈的数据还残留在内存中,只是逻辑上被删除了。
3.5 取栈顶元素
//取栈顶元素
STDataType StackTop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//取栈顶元素
return ps->a[ps->top - 1];
}
在取栈顶元素之前,首先需要调用函数 StackEmpty(ps) 判断栈是否为空,若栈不为空则可以取栈顶元素。因为 top 指向栈顶元素的下一个位置,所以 top 需要先进行--,再取栈顶元素。
3.6 判断栈是否为空
//判空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top==0; //返回栈的top,若为0则为空,非0则不为空;
}
判断一个栈是否为空,若为空则返回 true,否则返回 false。这里需要特别说明的一点是,我们将 top 指向栈顶元素的下一个位置,而非指向栈顶元素本身。
注意:
- 当top初始化为:top=0;此时top指向栈顶元素的下一个位置;
- 当top初始化为:top=-1;此时top指向栈顶元素。
3.7 栈的大小
//大小
int StackSize(ST* ps)
{
//判空
assert(ps);
return ps->top;
}
因为 top 指向栈顶元素的下一个位置,而 top 的下标又是从0开始的,所以 top 所在位置的下标就是所求栈的大小,也就是栈中元素个数。
3.8 栈的销毁
//销毁
void StackDestory(ST* ps)
{
//判空
assert(ps);
//realloc开辟的空间,需要调用free释放
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
对于栈的销毁,首先需要释放由 realloc 动态申请开辟的内存空间,使用 free 函数进行释放。然后将 top 和 capacity 初始化为0。
二、源代码
Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
//这里我们实现的是动态的栈
typedef int STDateType; //方便数据类型的替换
typedef struct Stack
{
STDateType* a; //动态开辟数组
int top; //栈顶
int capacity; //容量,方便扩容
}ST;
//初始化
void StackInit(ST* ps);
//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//取栈顶元素
STDataType StackTop(ST* ps);
//判空
bool StackEmpty(ST* ps);
//大小
int StackSize(ST* ps);
//销毁
void StackDestory(ST* ps);
Stack.c
#include"Stack.h"
//初始化
void StackInit(ST* ps)
{
//判空
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//入栈
void StackPush(ST* ps, STDataType x)
{
//判空
assert(ps);
//判断容量是否已满
//top标识的是最后一个数据的下一个位置,如果想要指向最后一个数据,初始时top=-1
if (ps->top == ps->capacity)
{
//为空就开辟四个空间,不为空,就扩容至二倍
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newCapacity);
if (tmp == NULL)
{
printf("malloc fail\n");
exit(-1);
}
//将新开辟的内存空间的首地址tmp赋值给a
ps->a = tmp;
//更新capacity
ps->capacity = newCapacity;
}
//入栈
ps->a[ps->top] = x;
//top指向栈顶元素的下一个位置
ps->top++;
}
//出栈
void StackPop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//出栈
ps->top--;
}
//取栈顶元素
STDataType StackTop(ST* ps)
{
//判空
assert(ps);
//判断栈是否为空
assert(!StackEmpty(ps));
//取栈顶元素
return ps->a[ps->top - 1];
}
//判空
bool StackEmpty(ST* ps)
{
assert(ps);
return ps->top==0; //返回栈的top,若为0则为空,非0则不为空;
}
//大小
int StackSize(ST* ps)
{
//判空
assert(ps);
return ps->top;
}
//销毁
void StackDestory(ST* ps)
{
//判空
assert(ps);
//realloc开辟的空间,需要调用free释放
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
test.c
#include"Stack.h"
void TestStack()
{
ST st;
//初始化
StackInit(&st);
//入栈
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
StackPush(&st, 7);
//出栈
while (!StackEmpty(&st))
{
//读取栈顶元素
printf("%d ",StackTop(&st));
//出栈
StackPop(&st);
}
printf("\n");
//销毁
StackDestory(&st);
}
int main()
{
TestStack();
return 0;
}
本期的内容不多,相信大家学起来也相对容易,提前透露一下下:我们下期来玩队列哈~如果你们觉得本篇文章对自己有所帮助,给博主留下三连支持噢,你的支持是我创作的最大动力!那我们下期再会啦~