1 栈的概念和结构
1.1 栈的概念
栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作,进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵循后进先出(先进后出)原则。特性与栈的结构有关
1.2 栈的结构
上述图就是栈的图,小编说过栈是有栈顶和栈底,栈顶可进行插入和删除操作,另一端是封闭的,不进行任何操作。
2 栈的实现(基于顺序表实现)
在讲解栈之前,由于栈是一种线性表,可以使用顺序表实现,也可以使用链表实现,考虑到成本,顺序表表的实现成本低,在尾部增加数据上操作更简便。
typedef int STDataType;
typedef struct stack {
STDataType* arr; //数组,类型可能不是一定的所以用typedef替换一下
int capacity; //总空间大小
int top; //栈顶表示
}ST;
2.1 栈的初始化
任何一个数据结构对其进行初始化是个必备操作,将数组值为空,同时将空间大小和元素个数都置为0,代码如下:
void STInit(ST* ps)
{
assert(ps);
ps->arr = NULL;
ps->capacity = ps->top = 0; //总空间个数和有用空间个数都初始化为0
}
2.2 栈的销毁
有了初始化操作,自然而然就有着销毁操作,毕竟"有始有终" ,栈的销毁同样也是不难的,我们对于arr,需要判断它是否开辟了空间,如果开辟了就free掉,没有开辟就直接把栈的空间大小和栈顶都置为空就好,下面直接上代码图:
void STDestroy(ST* ps)
{
if (ps -> arr) //先判断是否进行动态内存开辟了
{
free(ps -> arr);
}
ps->capacity = ps->top = 0;
}
2.3 入栈
在进行完初始化操作以后,就要进行一些正式操作了,看着入栈这个名字很高大尚,以小编的话来说这其实就是栈的尾插(栈也只能尾插,因为只能从栈顶插入),就类似下图:
上面这个图就展示了入栈操作,其实就是我们在顺序表阶段写的尾插操作,首先我们需要先设置一个函数,来判断一下栈的总空间大小和栈顶是否是相等,如果相等了那就扩容,这个和顺序表的扩容是一样的,详情可以看小编之前写的那一篇,由于栈的插入只有入栈这一种,所以扩容操作直接写到函数内部就好,我们在进行完插入以后,直接往栈顶插入数据,然后让栈顶在想后走一步就好了,下面直接展示代码:
void STPush(ST* ps, STDataType x) //类似顺序表的尾插
{
if (ps->capacity == ps->top)
{
int newcaopacity = ps->capacity == 0 ? 4 : 2 * ps -> capacity;
STDataType* arr1 = (STDataType*)realloc(ps->arr, newcaopacity * sizeof(STDataType));
assert(arr1);
ps->arr = arr1;
ps->capacity = newcaopacity;
} //扩容完成
ps->arr[ps->top++] = x;
}
2.4 出栈
有入栈肯定就会有出栈,对于栈的出栈,其实就是顺序表的尾删操作,因为栈的元素只能从栈顶出,栈底是不可以出元素的,可以类比下图进行记忆:
2.5 栈是否为空
取栈顶元通过上图我们就可以知道出栈到底是什么东西,对于出栈,我们首先需要判断栈是不是空的,不然拿什么来出栈?所以此时我们得写一个布尔类型函数来判断一下此时的栈是否是空的,如果是空的返回true,不是真的返回false,此时我们进需要判断栈顶是否为0就好,下面小编直接给代码图:
bool panduan(ST * ps)
{
assert(ps);
return ps -> top == 0; //这个是来判断栈是不是空了
}
2.6 取栈顶元素
对于上面的代码,小编其实写的很简略,其实如果写麻烦一点我们需要使用选择语句来判断一下是否为空,这里小编直接使用一句话来跳过选择语句了,这个代码的含义就是如果右边是对的那么直接返回true,如果栈顶不为0直接返回false。当我们判断完以后,可以直接进行尾删操作,很简单,我们只需呀让栈顶减一就好了,此时我们就实现了出栈操作,下面给出代码图:
void STPop(ST* ps)
{
assert(ps);
assert(!panduan(ps));
ps->top--;
}
2.7 获取栈中有效元素个数
int STSize(ST* ps)
{
return ps->top;
}
3 代码展示
3.1 Stack.c
#include"Stack.h"
void STInit(ST* ps)
{
ps->arr = NULL;
ps->capacity = ps->top = 0; //总空间个数和有用空间个数都初始化为0
}
void STDestroy(ST* ps)
{
if (ps -> arr) //先判断是否进行动态内存开辟了
{
free(ps -> arr);
}
ps->capacity = ps->top = 0;
}
void STPush(ST* ps, STDataType x) //类似顺序表的尾插
{
if (ps->capacity == ps->top)
{
int newcaopacity = ps->capacity == 0 ? 4 : 2 * ps -> capacity;
STDataType* arr1 = (STDataType*)realloc(ps->arr, newcaopacity * sizeof(STDataType));
assert(arr1);
ps->arr = arr1;
ps->capacity = newcaopacity;
} //扩容完成
ps->arr[ps->top++] = x;
}
bool panduan(ST * ps)
{
assert(ps);
return ps -> top == 0; //这个是来判断栈是不是空了
}
void STPop(ST* ps)
{
assert(ps);
assert(!panduan(ps));
ps->top--;
}
STDataType STTop(ST* ps)
{
return ps->arr[ps->top - 1];
}
int STSize(ST* ps)
{
return ps->top;
}
3.2 Stack.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h> //存放布尔类型的头文件
//下面来复习一下栈的创建
//它的底层代码是数组(也可以理解为顺序表)
typedef int STDataType;
typedef struct stack {
STDataType* arr; //数组,类型可能不是一定的所以用typedef替换一下
int capacity; //总空间大小
int top; //栈顶表示
}ST;
//初始化栈
void STInit(ST* ps);
//销毁栈
void STDestroy(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//取出栈顶的元素
STDataType STTop(ST* ps);
//获取栈中有效的个数
int STSize(ST* ps);