1.“栈”是什么?
生活中显而易见的例子:一摞叠在一起的盘子。放盘子:从下往上一个一个放;取盘子:从上往下一个一个取,不能从中间任意抽取。即后进者先出,先进者后出。
1) 从栈的操作特性来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。
2)栈存在的其中一个原因是什么:首先,从功能上来说,数组或链表确实可以代表栈,但是他们有太多的操作接口,操作上的确灵活,使用时就比较不可控,自然也就容易出错。
3)使用场景:当某个数据集合只涉及在一端插入和删除数据,并满足后进先出、先进后出的特性。
2.“栈”的实现`
1) 从栈的定义:后进先出,先进后出——>栈主要包含两个操作:入栈和出栈,即在栈顶插入一个数据和从栈顶删除一个数据。
2)实现:用数组实现的栈叫做顺序栈;用链表实现的栈叫做链式栈。
3)代码实现:老师用Java来写的,小白表示没学过,大概能看懂。
//基于数组实现的顺序栈
public class ArrayStack {
private String[] items; //数组
private int count; //栈中元素个数
private int n; // 栈的大小
//初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n) {
this.item = new String[n];
this,n = n;
this.count = 0;
}
//入栈操作
public boolean push(String item) {
//数组空间不够了,直接返回false,入栈失败。
if (count == n) retrun false;
//将item 放到下标为count的位置,并且count加一
item[count] = item;
++count;
return true;
}
//出栈操作
public String pop() {
//栈为空,直接返回null
if (count == 0) return null;
//返回下标为count-1的数组元素,并且栈中元素个数count减一
String tmp = items[count-1];
--count;
return tmp;
}
}
写完代码,一般就是要分析时间、空间复杂度:
(i)空间复杂度:无论是顺序栈还是链式栈,存储数据只需要一个大小为n的数组(原本的数据存储空间),在入栈和出栈过程中,只需要一两个临时变量储存空间(算法运行还需要额外的存储空间),所以空间复杂度为O(1)。
ps:空间复杂度是除了原本数据存储空间外,算法运行还需要额外的存储空间。
(ii)时间复杂度:无论是顺序栈还是链式栈,出栈入栈只涉及栈顶个别数据的操作,时间复杂度为O(1)。
3.支持动态扩容的顺序栈
以上举栗子基于数组实现的栈,是大小固定的,在初始化栈时需要事先指定栈的大小,当栈满之后,就不能再往栈里面添加数据,现在考虑如何基于数组实现一个可以支持动态扩容的栈。
既然是基于数组实现的,那就从数组如何实现动态扩容思考:当数组空间不够时,需要重新申请一块更大的内存,将原来数组中数据拷贝过去。故要实现一个支持动态扩容的栈,只需要底层依赖一个支持动态扩容的数组。当栈满了,就申请一个更大的数组,将原来的数据搬移到新的数组,萌萌的图又来了:
分析时间和空间复杂度:
(I)出栈:不涉及内存的重新申请和数据的搬移,时间复杂度是O(1);
(ii)入栈:最好情况时间复杂度是O(1),即当栈中有空闲时;最坏情况时间复杂度是O(n),即当空间内存不够时,需要重新申请内存和数据搬移;平均情况的时间复杂度:利用摊还分析法来分析,为简化分析做以下假定:栈空间不够时,重新申请一个是原来大小两倍的数组;假设只有入栈操作无出栈操作;定义不涉及内存搬入的入栈操作为simple-push操作,时间复杂度为O(1)。开始分析摊还法:如果当前栈大小为K,并且已经满了,再有新的数据要入栈,需要重新申请2倍大小的内存,并且做K个数据的搬移操作,再入栈。但接下来的K-1次入栈操作,都不需要再重新申请内存和搬移数据,所以这K-1次入栈操作只需要一个simple-push操作就完成。
K次入栈操作,共涉及K个数据的搬移,以及K次simple-push操作。将K个数据搬移均摊到K次入栈操作,每个入栈操作只需要一个数据搬移和一个simple-push操作,以此类推,入栈操作的均摊时间复杂度是O(1)。均摊时间复杂度一般都等于最好情况时间复杂度。
4.栈在表达式求值中的应用
编译器通过两个栈来实现:其中一个保存操作数的栈,另一个保存运算符的栈。从左向右遍历表达式,遇到数字,直接压入操作数栈;遇到运算符,与运算符栈的栈顶元素进行比较。若比运算符栈顶元素的优先级高,就将当前运算符压入栈,若比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取2个操作数,进行计算,再把计算完的结果压入操作数栈,继续比较。如:3+5*8-6
反正我没想到用两个栈来做。。。路漫漫其修远兮
参考资料:极客时间——数据结构与算法之美