Stack 是一种线性的数据结构,它的最大特点是后进先出 (Last In First Out) (LIFO)
每次压入 (push) 元素,元素都储存在 Stack 的最上方,每次取出 (pop || peek) 元素,同样也只能先取出最上端的。
(可以将这些元素想象成一摞叠在一起的盘子)
Stack 的常用方法有 push()
, pop()
, isEmpty()
, peek()
, 这些操作都只花费常数时间 O(1)
Stack 有两种实现方法:通过Array,通过LinkedList。下面来逐一分析
Implementing Stack using Arrays
Stack 最顶端的元素即数组的最后一个元素
push 即向数组末尾添加一个元素;pop 即取出最后一个元素
isEmpty 即判断数组是否为空;size 即获取元素个数
需要注意的是第6行和第10行,按照范型的概念,我们本应该写成这样a = new E[cap]
但由于历史和技术原因,Java 虚拟机并不允许这样做。我们要将其写成这样a = (E[]) new Object[cap]
这样写在编译期间会有 warning,不用在意,尽管放心安全的使用
同样需要注意的是pop()
方法,在该方法中,我们仅用了一行代码:return a[--N]}
这样写结果是对的,a[N] 元素我们不在使用,但仍存有值
这样会导致 Loitering
Loitering: Holding a reference to an object when it is no longer needed
所以更规范的写法是:
但是这种用 Array 来实现 Stack 有一个缺点:数组的 size 是固定的
我们得提供一个用户自定义 size 的constructor ,一个 default constructor 和一个特定的 size 比如1000
如果用户只存储了很少的元素,那么更多的内存空间会被浪费
如果用户存储了超过1000个元素,此时程序会 throw IllegalStateException
所以我们应该提供一个 resizing-array implementation
First try:
push(): increase size of array a[] by 1.
pop(): decrease siez of array a[] by 1.
Too exxpensive:
Need to copy all item to a new array
Inserting first N items taks time proportinal to 1 + 2 + … + N ~ N^2/2
Effective solution:
push(): double size of array a[] when array is full.
pop(): halve size of array a[] when array is one-quarter full
相应的,当 array 中没有空间时,resize 成两倍
当 elements 个数只占四分之一时,resize 成一半
使用这种方法构造 Stack 时size()
, isEmpty()
, peek()
, push()
, pop()
时间复杂度都是 O(1)
使用示例:
Implementing Stack using Singly LinkedList
在使用链表构造 Stack 时,我们要考虑的是,把链表的那一端作为 Stack 的 top?头还是尾?
很显然,我们应该将头作为 top,因为头部的元素可以通过常数时间获取,而获取尾部元素还要遍历整个 List
由于 Stack 的这种 LIFO 特点,可以用来 reverse array element
resizing array vs linked list
Linked-list implementation:
Every operation takes constant time in the worst case.
Uses extra time and space to deal with the links.
Resizing-array implementation:
Every operation takes constant amortized tiem.
Less wasted space.
Reference:
Data Strucuture and Algorithms in Java (7th edition) (Page 230 – Page 234)