栈的原理
栈是一种线性表,仅在表尾进行插入和删除的线性表,称为栈顶。是按照先进后出的原则储存数据,后进者先出,先进者后出,这就是典型的“栈”结构。
当某个数据集合只涉及在一端插入和删除数据, 并且满足后进先出、先进后出的特性,我们就应该首选“栈”这种数据结构。
栈的实现
用数组实现的栈,我们叫作顺序栈,用链表实现的栈,我们叫作链式栈。
数组栈的实现代码
// 基于数组实现的顺序栈
public class ArrayStack {
private String[] items; // 数组
private int count; // 栈中元素个数
private int n; //栈的大小
// 初始化数组,申请一个大小为n的数组空间
public ArrayStack(int n) {
this.items = new String[n];
this.n = n;
this.count = 0;
}
// 入栈操作
public boolean push(String item) {
// 数组空间不够了,直接返回false,入栈失败。
if (count == n) return false;
// 将item放到下标为count的位置,并且count加一
items[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;
}
}
数组栈的时间空间复杂度分析
不管是顺序栈还是链式栈,我们存储数据只需要一个大小为 n 的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。(空间复杂度是对一个算法在运行过程中临时占用存储空间大小的量度, 临时!)
支持动态扩容的顺序栈
如果要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。
对于出栈操作来说,我们不会涉及内存的重新申请和数据的搬移,所以出栈的时间复杂度仍然是 O(1)。
但是,对于入栈操作来说,情况就不一样了。当栈中有空闲空间时,入栈操作的时间复杂度为 O(1)。 但当空间不够时,就需要重新申请内存和数据搬移,所以时间复杂度就变成了 O(n)。
入栈操作的均摊时间复杂度就为 O(1)。 均摊时间复杂度一般都等于最好情况时间复杂度。
栈在函数调用中的应用
局部变量和方法调用储存在栈内存中,对象储存在堆内存中。
参考博客
单调栈的应用和案例详解