栈是限定只在表尾进行删除和插入操作的线性表。
怎么理解“栈”这种数据结构呢?这里举个较为贴切的栗子,手枪就是一个典型的“栈”。
电视节目中的各路英雄在用手枪的时候,通常是先填装子弹,再将弹夹最上层的子弹射出。先进入弹夹的子弹最后射出,后进入弹夹的子弹最先射出,这其实就是栈的思想。
先进后出,后进先出,这就是一种典型的“栈”思想。
这是栈最大的特点,也是栈最大的缺点,因为栈这样的一种特性导致了其只能在同一端插入元素和删除元素。
但我们在前面学了链表,栈能实现的操作链表也都可以实现。我们可以说栈是线性表的一种特例,其中线性表的顺序存储也对应着顺序栈(后面会有链式栈,我还会更的)。与此同时,由于栈涉及的操作比较少,实现起来也较为容易。
看到这里,你可能会发出疑问:有链表了我为什么还要用栈呢?链表能做的事情可比栈多得多了。
一开始我也疑惑,但栈存在就一定有他的道理。对比与链表来说,我认为有两点主要原因:
- 链表涉及的操作纷杂繁琐,在使用上稍不留神就会出错。
- 栈在功能上肯定是不如链表全面,但是我们所学的每一种数据结构都不是直接单拎出来讲的,每一种数据结构肯定对应着一种使用场景。在特定的场景之下,栈的实现要比链表简单,这是栈相比链表在时间上的优势。
好吧,说了这么多。栈相比链表来说的优势其实主要就两点:简单容易实现且不容易出错。
那么如何实现“栈”呢?
在实现栈之前,先向大家引入几个概念:
我们把允许插入和删除的一段称为栈顶(表尾),
将另一端称为栈底。
将不含任何数据元素的栈称为空栈。
线性表的顺序存储是依靠数组完成的。这里的顺序栈也与顺序表有着异曲同工之妙。
结合“先进后出,后进先出”的思想,我们将数组的首元素作为栈底元素,将数组的末元素作为栈顶元素,操作时只需要针对栈顶元素即可。
将栈想象为一个容器:
既然栈的操作围绕栈顶元素开展,现在的问题就在于如何表示栈顶元素在数组中的位置?
我们用一个Top变量来表示栈顶元素在数组中的位置,有元素插入到栈顶元素之上,Top就加一,栈顶元素删除后,Top就减一。
结合Top变量来表示栈的几种状态:
(1)栈中有两个元素,Top = 1.
(2)栈中没有元素,Top = -1.
(3)栈中有4个元素(满),Top = 3.
(图画的太丑了,别喷我···)
这里的Top变量实际上代表着栈顶元素的位置,可以理解为一个指针,指向栈顶元素。
但是Top必须小于数组的最大值,而且最小为 0(只有一个元素a[0])。
如果栈中一个元素也没有,我们通常将Top元素置为 -1。
Top栈顶指针其实和数组下标是一一对应的关系。
看了这么长时间的理论知识,下面我们就可以开始实现栈了:
栈的结构定义
一个数组存数据,一个top变量来表示栈顶元素在栈中的位置。
代码实现:
struct SqStack{
SElemType data[MAX];
//SElemType 是数据类型,int、float、char都可,根据实际情况决定
int top;
};
栈的初始化
将top变量置为 -1,表示栈中没有栈顶元素,也就是没有元素。
void InitStack(SqStack *S){
S->top = -1;
}
清除栈
同上。
void ClearStack(SqStack *S){
S->top = -1;
}
检查栈是否为空
如果top变量为 -1,表明栈为空。反之,不为空。
bool EmptyStack(SqStack S){
if(S