栈一种常见的特殊线性数据结构,其特殊之处在于其操作顺序,下面会详细介绍,也正因为其特性,因此栈可以轻松解决表达式求值、括号匹配、递归算法、回溯算法等等问题。
01、定义栈的特殊性表现为操作受限,其一只允许在栈的一端进行元素插入和删除运算,其二栈的运算操作遵循后进先出(Last In First Out,简称LIFO)的原则。
入栈:当把元素插入到栈,这一行为叫做入栈,也称进栈或压栈;
出栈:当把元素从栈中移除,这一行为叫做出栈,也称退栈;
栈顶:允许元素进行插入和删除操作的一端称为栈顶;
栈底:与栈顶对应的另一个端称为栈底,并且不允许进行元素操作;
空栈:当栈中没有元素时叫做空栈。
满栈:当栈是有限容量,并且容量已用完,则称为满栈。
栈容量:当栈是有限容量,栈容量表示栈可以容纳的最大元素数量。
栈大小:表示当前栈中的元素数量。
02、分类栈是逻辑结构,因此以存储方式的不同可以分为顺序栈和链栈。
顺序栈就是使用连续的地址空间存储所有栈元素,通常采用数组实现,因此导致栈的大小是固定的,不易扩扩容,容易浪费空间同时还要注意元素溢出等问题。
链栈顾名思义就是采用链式方式存储,通常采用单向链表实现,因此链栈可以无限扩容,按需使用,内存利用高效,同时也不存在满栈的情况。
03、实现(顺序栈)我们借助数组来实现顺序栈,其核心思想是把数组的起始位置作为栈底,把数组尾方向当作栈顶。
我们知道数组对插入、删除元素是不友好的,因为涉及到已存在元素移动的问题,但是如果直接在数组尾端插入、删除元素还是很方便的,因为不涉及元素移动问题,我们正是利用这一特点,把数组起始位置做为栈底,而插入、删除方便的数组尾端作为栈顶。
下面我们将一步一步实现一个泛型顺序栈。
1、ADT定义我们首先来定义顺序栈的ADT。
ADT Stack{
数据对象:D 是一个非空的元素集合,D = {a1, a2, ..., an},其中 ai 表示栈中的第i个元素,n是栈的长度。
数据关系:D中的元素通过它们的索引(位置)进行组织,索引是从0到n-1的整数,并且遵循元素只能在栈顶操作,元素后进先出的原则。
基本操作:[
Init(n) :初始化一个指定容量的空栈。
Capacity:返回栈容量。
Length:返回栈长度。
Top:返回栈顶元素,当为空栈则报异常。
IsEmpty():返回是否为空栈。
IsFull():返回是否为满栈。
Push():入栈即添加元素,当为满栈则报异常。
Pop():出栈即返回栈顶元素并把其从栈中移除,当为空栈则报异常。
]
}
定义好栈ADT,下面我们就可以开始自己实现的栈。
2、初始化Init初始化结构主要做几件事。
初始化栈的容量;
初始化存放栈元素数组;
初始化栈顶索引;
具体实现代码如下:
3、获取栈容量 Capacity这个比较简单直接把栈容量私有字段返回即可。
4、获取栈长度 Length因为栈顶索引表示数组下标,因此可以通过栈顶索引加1转为栈长度,同时因为我们定义了空栈是栈顶索引为-1,因此此时栈长等于[-1+1]为0,所以栈长度即为[栈顶索引+1]。
5、获取栈顶元素 Top获取栈顶元素可以通过栈顶索引私有字段从数组中直接获取,同时要注意判断栈是否为空栈,如果为空栈则报异常。具体代码如下:
6、获取是否空栈 IsEmpty是否空栈只需判断栈顶索引是否为小于0即可。
7、获取是否满栈 IsFull是否满栈只需判断栈顶索引是否与栈容量减1相等,代码如下:
8、入栈 Push入栈则是在把栈顶索引向数组后移动一位后,再把新元素赋值到栈顶索引所对应的元素上,同时还需要检查是否为满栈,如果是满栈则报错,具体实现代码如下:
9、出栈 Pop出栈则是首先取出栈顶元素后,然后把栈顶索引向数组前移动一位,最后返回取出的栈顶元素,同时还需要检查是否为空栈,如果是空栈则报错,具体实现代码如下:
04、实现(链栈)我们借助链表来实现链栈,其核心思想是把链表尾节点作为栈底,把链表首元节点当作栈顶。
这是因为如果我们想拿到链表的尾节点需要变量整个链表才可以拿到,但是要想获取首元节点则可以通过头指针直接获取到(无头节点链表),因此对于链表尾节点来说操作时不友好的适合来做栈底,链表首元节点对操作友好适合做为栈顶。
下面我们将一步一步实现一个泛型链栈。
1、ADT定义相对于顺序栈的ADT来说,链栈的ADT少了两个方法即获取栈容量和是否满栈,这也是链表特性带来的好处。
2、初始化Init初始化结构主要初始化栈顶节点为空和栈长度为0,具体实现如下:
3、获取栈长度 Length这个比较简单直接把栈长度私有字段返回即可。
4、获取栈顶元素 Top获取栈顶元素可以通过栈顶节点直接获取,但是要注意判断栈是否为空栈,如果为空栈则报异常。具体代码如下:
5、获取是否空栈 IsEmpty是否空栈只需判断栈顶节点是否为空即可。
6、入栈 Push入栈则是首先创建一个新节点,然后把原栈顶节点赋值给新节点的指针域,最后把新节点变更为栈顶节点,同时栈长加1,具体实现代码如下:
7、出栈 Pop出栈则是首先取出栈顶节点对应的数据后,然后把栈顶节点指向原栈顶节点对应的下一个节点,同时栈长度减1,当然如果空栈则报错,具体实现代码如下:
注:测试方法代码以及示例源码都已经上传至代码库,有兴趣的可以看看。https://gitee.com/hugogoos/Planner