1.1栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
想要理解栈,可以把栈想象成一个杯子,把数据放进去,最先进去的数据就被放到了杯子的最下面,而后放入的数据就放在了杯子的上面。所以要想将数据取出来只能先去上面的数据。这也是栈的特点:先入后出,后入先出。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
后进先出即后进去的要先出来
例如:
进栈的顺序是1234,
出栈顺序可能是4321,即全部进去再出栈
出栈的顺序也可能是1234,即边进边出.
也可以是2134 ,即12先进去,再出来,然后进3出3,进4出4.
1.2栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的代价比较小,下面程序中使用的是数组。
如果用单链表实现,最好是用头做栈顶,头插头删比较简单
需要注意的是在实用顺序表实现栈的时候
top的位置,如果想要top指向栈顶元素的下一个,top应该初始化为0,每次是先放数据再++;
如果想要top指向栈顶元素,top应该初始化为-1,此事时先++再放数据;
好了,下面用程序来实现栈,让大家更详细的了解栈的操作。
我们使用数组来操作栈,数组是一块连续的内存。我们要在每一个栈中存储数据那么我们就要使用到结构体,这个结构体里面放些什么呢?先来思考一下。
操作栈,我们是不是需要申请空间来储存数据,既然要申请空间来储存数据那么程序员申请空间就需要申请堆区的空间,使用malloc来申请。申请完空间我们就需要知道空间的首地址,首地址就可以保存在结构体里面。
我们需要在一端插入或删除数据并及时对其访问,因此我们这里使用动态顺序表来实现。在这个结构中 top 由于初始值设定为 0 所以代表的是栈顶下一位的下标,也可以设置为-1,则代表的是栈顶的下标。使用capacity是为了时刻检测内存的大小便于及时开辟。
typedef int STDataType;
typedef struct Stack
{
int* a;//栈空间的起始地址
int top;//栈顶指针
int capacity;
}ST;
需要实现的接口:
//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);
//入栈
void STPush(ST* ps,STDataType);
//出栈
void STPop(ST* ps);
//有效数据的个数
int STSize(ST* ps);
//检测是否为空,不为空则返回0
bool STEmpty(ST* ps);
//获取栈顶元素
STDataType STTop(ST* ps);
栈的初始化
首先为内部储存的数据开辟一段空间,(这里我设置的初始值是4根据需要可以进行调整),开辟成功后,用结构体内的指针指向这片空间,同时为 top 与 capacity 赋上初始值。这样便完成了栈的初始化。
既然创建了栈的节点,那么我们下一步需要对结构体进行初始化。这里我选择封装函数来完成。在写函数之前我们先来写一个主函数。
int main()
{
ST st;
STInit(&st);
return 0;
}
首先我们要了解我们在初始化的时候是需要对st里面的值进行改变的,改变实参的值就需要使用地址传参,所以我们需要将st的地址传过去
//初始化
void STInit(ST* ps)
{
assert(ps);//断言,避免传过来的指针为空
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)//判断是否开辟成功
{
perror("malloc fail");
return;
}
ps->capacity = 4;
ps->top = 0;//top是栈顶元素的下一个位置
//ps->top = -1;//top是栈顶元素位置
}
assert断言,需要包含头文件
#include<assert.h>
初始化栈时必须要断言,避免穿过来的指针为空,同时也避免了当定义变量为ST* st=NULL; 对其进行初识化StackInit(st)这种情况的发生。
同时也需要注意
top初始化为0,表示指向栈顶元素的下一个位置,也可以表示栈中的元素个数;top初始化为-1,表示指向栈顶元素。
压栈
一切开始前首先应该判断传入的指针是否为空指针,避免出现对空指针解引用的情况。后判断这个空间是否已满,不然增大空间的容量。之前说过 top 指向的是栈顶的下一位,因此直接赋值后再对 top ++ 就可以了。
圧栈时要检查数组是否已满,是否需要对其进行扩容。
从下标的角度上来说,top 是比栈顶多 1 的,由于下标的从 0 开始因此 top 正好可以表达此刻数据的数量。当 top 与 capacity 相等时,即开辟的空间已满。所以使用 realloc 调整新空间的大小(我把默认设为每次增加都是原来的两倍)。开辟成功后把新的地址给 ps->a ,调整 capacity 为新容量的大小。
//压(入)栈
void STPush(ST* ps, STDataType x)
{
assert(ps);//断言
if (ps->top == ps->capacity)//判断空间是否已满
{
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType)*ps->capacity*2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
出栈
出栈的操作相当的简便,只要 top-- 这样未来访问的时候就不会再访问到,也没有必要再对其进行赋值,因为下次插入的时候就会用新值覆盖原来的值。但是断言还是要记得断言的。
//出栈
void STPop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
获取栈顶元素
这里我们通过top访问到栈顶的元素之后并将其返回,就完成了栈顶元素的获取。
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
判空
这里使用了 bool 值要引用头文件 stdboo.h ,前面讲到 top 在栈中的意义就相当于内置数据的数量,即 top 不等于 0 则栈里就是还有数据的,所以返回false。
//检测栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//为真返回true
}
销毁
栈结束使用后,要进行销毁避免内存泄漏。断言后释放a 后再将top跟capacity赋值为初始值。
//销毁
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
完整代码:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
//定长静态栈结构,不实用
//#define N 10
//struct Stack
//{
// int a[N];
// int top;
//};
typedef int STDataType;
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
//初始化
void STInit(ST* ps);
//销毁
void STDestroy(ST* ps);
//入栈
void STPush(ST* ps,STDataType);
//出栈
void STPop(ST* ps);
//有效数据的个数
int STSize(ST* ps);
//检测是否为空,不为空则返回0
bool STEmpty(ST* ps);
//获取栈顶元素
STDataType STTop(ST* ps);
#include"Stack.h"
//初始化
void STInit(ST* ps)
{
assert(ps);//判空
ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (ps->a == NULL)//判断是否开辟成功
{
perror("malloc fail");
return;
}
ps->capacity = 4;
ps->top = 0;//top是栈顶元素的下一个位置
//ps->top = -1;//top是栈顶元素位置
}
//销毁
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
//入栈
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType)*ps->capacity*2);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity *= 2;
}
ps->a[ps->top] = x;
ps->top++;
}
//出栈
void STPop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
ps->top--;
}
//获取栈中有效元素个数
int STSize(ST* ps)
{
assert(ps);
return ps->top;//top+1
}
//检测栈是否为空
bool STEmpty(ST* ps)
{
assert(ps);
return ps->top == 0;//为真返回true
}
//获取栈顶元素
STDataType STTop(ST* ps)
{
assert(ps);
assert(!STEmpty(ps));
return ps->a[ps->top - 1];
}
#pragma once
#include"Stack.h"
int main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
STPush(&st, 5);
//出数据不能写print
while (!STEmpty(&st))
{
printf("%d ", STTop(&st));
STPop(&st);
}
STDestroy(&st);
return 0;
STDestroy(&st);
return 0;
}