栈
1 概念
栈是一种特殊的线性表,只允许在其固定的一端进行插入和删除元素操作。进行数据插入和删除的一端称为栈顶,另一端称为栈底。其栈中的数据元素遵循先进后出(后进先出)原则。
栈的插入操作也叫做进栈 / 入栈 / 压栈,其操作完成在栈顶。
栈的删除操作也叫做出栈。其操作完成也是在栈顶。
2 特点
只允许在一端进行数据的插入和删除操作(栈顶),其数据元素遵循先进后出(后进先出)原则。
3 实现
3.1 栈实现体系结构的确立
因其栈本质还为线性表,所以其实现既可以使用顺序表也可以使用链表。但其两者实现难度和复杂程度不同,两者其差异主要体现在对栈顶元素的删除上。
例如,链表,如果用单链表作为体系结构,那么在删除时单链表元素时,肯定需要知道该元素的前一个位置。
但单链表要知道某个元素的前一个位置比较麻烦(单向结构),要解决这个问题,可以多定义一个指针来保存元素的前一个位置,或者采用双向链表结构,就比较复杂了。
而对于用顺序表(数组)来说,我不需要知道要删除元素的前一个位置,我只需要知道要删除元素位置的下表即可,因此,对于栈的实现来说,我主要采用了动态的顺序表来进行实现。
3.2 一个具体的栈应该包括什么?
对于一个较为成熟的栈,具体应该包括栈的初始化,插入元素,删除元素,获取栈顶元素,获取栈内元素个数,判断栈是否为空,销毁栈这6大基本部分。
3.3 实现细节
对于栈的实现来说,其根本在于栈顶操作,对于其实现的栈来说,我选择在开辟数组元素时,多开辟一个容量,定义一个top下标进行数据的插入和删除操作。top指向的是栈顶的下一个位置。具体见下图。
当栈内一个元素都没有时,top此时下标为0,其位置指向是在栈顶的下一个位置。
当栈内元素(总共5个)满了时,此top下标数等于其栈内元素总个数。
以此判断栈内元素是为空还是满。并且top还可以记录栈内元素个数,插入就加1,删除就减一。
3.4 具体实现
3.4.1 结构体
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;//开辟的数组总数
}Stack;
3.4.2 栈的初始化
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
初始化内容简单,assert()函数相当于控制语句,当ps等于空的时候,程序就终止了,并且会提示ps会空信息。也可以使用if()语句来进行控制。
3.4.3 栈的插入操作
void StackPush(Stack* ps, STDataType x)
{
assert(ps);
if (ps->top == ps->capacity)//当top下标等于开辟的数组总数时则需要扩容
{
int newcapacity = 0;
if (ps->top == 0)//处理刚开始初始化时top下标为0的情况
{
newcapacity = 4;
}
else
{
newcapacity = ps->capacity * 2;
}
STDataType* tmp = (STDataType*)realloc(ps->a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");//和assert()函数意义差不多
exit(-1);
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;//先给top下标位置元素赋值
ps->top++;//之后再将top下标加1,指向下一个下标。
}
其插入操作相信各位帅哥,美女不难理解,就是如果空间满了,则开辟空间进行扩容。如果未满,则直接插入。
3.4.4 栈的删除操作
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
ps->top--;
}
删除操作则就更为容易,只需要将top下标位置减一即可,空间无须释放,因为可能还会再插入,释放了反而需要再进行开辟,较为麻烦。
需要注意的一点为,如果top下标此时为0,说明栈内是没有元素的,那么就不要去减一了,所以assert(ps->top > 0)相当于控制top,当top大于0时,才会执行下面的减一操作。也可以通过if()语句进行控制,本意都相同。
3.4.5 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
获取栈顶元素也是较为简单,只需要注意的为,因其top下标指向的是栈顶的下一个元素(具体可见上图),所以栈顶为top - 1,也是需要注意top为0时就不能减一了。
3.4.6 判断栈是否为空
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->top == 0;
}
借助前文分析,判断栈是否为空,我们只需要判断top下标是否为0即可。
3.4.7 获取栈内元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
注意,其实top记录的就是栈内元素个数总数,所以返回时直接返回top即可。而不是返回capacity,capacity为开辟的数组个数,不为元素个数。
3.4.8 栈的销毁
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
对于销毁来说,我们只需要将申请的内存空间再返回给系统即可,所以我们只需要销毁掉申请的数组a,然后再将其他的变量回归到初始值即可。
综上,栈的分析到此结束,下一篇我们来讲下队列的具体操作等。