本讲内容
栈的定义
栈的操作
栈的实现
栈的应用
问题引入:
思考一下浏览器的前进和后退功能有什么特征?是怎么实现的?
页面A-》页面B-》页面C
回退:页面C-》页面B-》页面A
前进:页面A-》页面B-》页面C
栈的定义
栈是一种“操作受限”的线性表,只允许在一端插入和删除数据
当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,这时应该首选“栈”这种数据结构。
为什么有个数组和链表,还需要栈,而且栈的操作还受限制?
从功能上说,数组和链表即能满足需求了,引入栈的原因是特定的数据结构是对特定场景的抽象,而且,数组或链表暴露了太多的操作接口,操作上的确灵活自由,但使用时就比较不可控,自然也就更容易出错。
看来太自由了也不好,有时候还是需要强约束来保证正确性
栈的操作
入栈和出栈
栈的实现
用什么实现一个栈?
基础的数据结构有数组和链表,所以一般是用数组和链表来实现栈
用数组实现的栈叫作顺序栈,用链表实现的栈,我们叫作链式栈。
示例:
用数组实现一个固定长度的栈
// 基于数组实现的顺序栈
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;
}
}
下面分析空间和时间复杂度
空间复杂度:
在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。
注:我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间。
时间复杂度:
不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。
支持动态扩容的顺序栈
如果要实现一个支持动态扩容的栈,我们只需要底层依赖一个支持动态扩容的数组就可以了。当栈满了之后,我们就申请一个更大的数组,将原来的数据搬移到新数组中。即数组支持动态扩容。前面讲过数组动态扩容时,是申请一个更大的数组空间(比如是原有空间的2倍),把原数组先copy至新数组内。
思考题:时间复杂度和空间复杂度分析?
栈的应用
栈在函数调用中的应用
操作系统给每个线程分配了一块独立的内存空间,这块内存被组织成“栈”这种结构, 用来存储函数调用时的临时变量。每进入一个函数,就会将临时变量作为一个栈帧入栈,当被调用函数执行完成,返回之后,将这个函数对应的栈帧出栈。
为什么要使用栈?
栈在表达式求值中的应用
计算机如何计算34+13*9+44-12/3
栈在括号匹配中的应用
借助栈来检查表达式中的括号是否匹配。