目录
一.栈的概念
二.栈的实现
1.基本结构
2.常见接口
1.StackInit函数-对栈结构进行初始化
2.StackPush函数-入栈,即向栈顶内存放数据
3.StackPop函数-出栈,即删除栈顶元素
4.StackTop函数-获取栈顶元素
5.StackSize函数-获取栈的大小,即有效数据个数
6.StackEmpty函数-判断栈是否为空
7.StackDestroy函数-栈的销毁
一.栈的概念
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。 栈中的数据元素遵守后进先出 LIFO ( Last In First Out )的原则。其中有以下概念:
栈顶(Top):线性表允许进行插入和删除的一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素。
二.栈的实现
我们之前学习过两种常用线性表,即顺序表和链表。这两种数据结构都能被我们用于实现栈。由于栈只需要在栈顶进行插入和删除元素,采用顺序表实现的话,把顺序表尾部作为栈顶,即对应顺序表的尾插和尾删,其时间复杂度为O(1)。 而采用链表实现的话,则需要把链表头作为栈顶,对应链表的头插和头删,其时间复杂度也为O(1)。为了使栈更加实用,我们通常采用动态开辟的空间来存放数据。
下面给出栈的顺序表实现:
1.基本结构
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
其中STDataType为重定义数据类型,这样定义有利于我们通过简便的修改,达到使栈存放不同类型数据的目的。
变量_a-指向动态开辟空间的指针,以实现动态内存开辟存放数据
变量_top-为栈顶位置。一般实现为空间内存放有效数据的个数。因此作为下标,其指向末尾元素的下一个元素的位置。
变量_capacity-为空间容量。若_capacity<=_top,想要继续添加数据,则对空间进行扩容。
2.常见接口
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
1.StackInit函数-对栈结构进行初始化
这里需要我们先创建一个栈结构变量,再调用StackInit函数传入参数对其进行初始化。也可实现为StackCreate函数-在函数内利用动态内存开辟完成对栈空间指针的创建,并在进行初始化后返回该指针,这里我们介绍前者。
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_top = ps->_capacity = 0;
}
首先由于传入形参为指针,故先用assert进行断言,防止其为空指针(后续函数实现也均需在开头进行断言)。然后对前文定义的结构体内变量_a,_top,_capacity进行初始化。由于尚未使用动态内存分配开辟空间,因此自然初始化_a为NULL,_top和_capacity均为0。
2.StackPush函数-入栈,即向栈顶内存放数据
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->_top == ps->_capacity)
{
int newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->_a,sizeof(STDataType)*newCapacity);
if (ps->_a == NULL)
{
printf("realloc error\n");
exit(-1);
}
ps->a = tmp;
ps->_capacity = newCapacity;
}
ps->_a[ps->_top] = data;
ps->_top++;
}
首先我们需要判断是否有足够空间存放数据,即_capacity是否大于_top,若不大于,我们需要调用realloc函数进行空间扩容。为了避免频繁进行扩容,这里扩容实现为将原来的空间扩大为2倍,即newCapacity=2*ps->capacity。但由于我们初始化_capacity=0,因此在第一次扩容时扩大为2倍是有问题的,此处使用三目操作符进行判断,若_capacity=0,则赋值其为4。若扩容失败,则直接返回错误信息。其对应代码如下:
int newCapacity = ps->_capacity == 0 ? 4 : ps->_capacity * 2;
STDataType* tmp= (STDataType*)realloc(ps->_a,sizeof(STDataType)*newCapacity);
if (ps->_a == NULL)
{
printf("realloc error\n");
exit(-1);
}
ps->_a = tmp;
ps->_capacity = newCapacity;
若扩容成功,则将data压入栈顶:ps->_a[ps->top]=data ,然后有效数据个数_a自增1。
3.StackPop函数-出栈,即删除栈顶元素
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top);
ps->_top--;
}
和顺序表一样,要删除末尾元素,只需要将有效数据个数_top自减1。当然首先我们得判断栈是否为空栈,这里也用断言实现。
4.StackTop函数-获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top);
return ps->_a[ps->_top - 1];
}
两个断言不必多说,只需要返回栈顶元素的值就行了(_top-1为栈顶元素的下标)
5.StackSize函数-获取栈的大小,即有效数据个数
虽然我们能通过_top直接进行访问,但在实际使用中,我们一般将其封装为函数。
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
非常简单,直接返回_top的值
6.StackEmpty函数-判断栈是否为空
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top==0;
}
ps->_top==0为条件表达式,若_top为0,条件表达式的值为真,即栈为空。若_top不为0,则条件表达式的值为假,即栈不为空。返回值为bool类型,在C语言中使用需要包含头文件stdbool.h。
7.StackDestroy函数-栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_top = ps->_capacity = 0;
}
只需要free(ps->_a)是否动态内存开辟的空间,然后将结构体变量置为初值即可。
栈的实现就介绍至此,感谢阅读。