我们先介绍一下api
FixedCapacityStack(int cap) 创建一个容量为cap的空栈
void push(Item item) 添加一个元素
Item pop() 删除最近添加的元素
boolean isEmpty() 栈是否为空
int size() 栈中元素的数量
下面是测试用例
public static void main(String[] args) {
FixedCapacityStack<String> s;
s = new FixedCapacityStack<>(4);
s.push("a");
s.push("b");
s.push("c");
s.push("d");
s.push("e");
Iterator<String> it = s.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
下面是实现代码
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* 用数组模拟一个栈,并动态改变数组的大小
* @author crwen
*
* @param <Item>
*/
public class FixedCapacityStack<Item> {
private Item[] a; // 栈数组
private int N; //容量
public FixedCapacityStack(int cap) {
a = (Item[]) new Object[cap];
}
/**
* 使该类可迭代
* @return
*/
public Iterator<Item> iterator() {
return new ReverseArrayIterator();
}
/**
* 判断栈是否为空
* @return
*/
public boolean isEmpty() {
return N == 0;
}
/**
* 将元素压入栈内
* @param item
*/
public void push(Item item) {
if (a.length == N) {
//如果栈满,扩容为原来的两倍
resize(2 * a.length);
}
a[++N] = item;
}
/**
* 将栈顶元素弹出
*/
public void pop() {
Item item = a[--N];
a[N] = null; //避免对象游离
if (N > 0 && N == a.length / 4) {
//如果数组太大了,我们将长度减半,保证栈使用率不会低于 1/4
resize(a.length / 2);
}
a[--N] = item;
}
/**
* 返回栈的大小
* @return
*/
public int size() {
return N;
}
/**
* 调整数组大小
* @param max
*/
private void resize(int max) {
// 将大小为N <= max 的栈移动到一个新的大小为max的数组中
Item[] temp = (Item[]) new Object[max];
for (int i = 0; i < N; ++i) {
temp[i] = a[i];
}
a = temp;
}
/**
* 逆序迭代器
* 实现逆序遍历数组,即实现按照后进先出的顺序迭代访问所有栈元素
* @author crwen
*
*/
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
/**
* 判断是否还能继续迭代
*/
@Override
public boolean hasNext() {
return i > 0;
}
/**
* 返回当前迭代的值
*/
@Override
public Item next() {
if (i == 0) {
throw new NoSuchElementException();
}
return a[--i];
}
/**
* 删除值
*/
@Override
public void remove() {
if (i == 0) {
throw new UnsupportedOperationException();
}
a[--i] = null;
}
}
}
我们来解释一下代码,首先是 resize(int max)方法。
由于该类的初始容量已经确定了,但是我们不一定能够预测我们所需栈的最大容量。如果我们选择的容量过小,会导致溢出;但是如果我们选择的容量过大,又会造成过多的浪费。所以这里我们动态的改变数组的容量。下面是改变数组容量的方法 resize(int max)
/**
* 调整数组大小
* @param max
*/
private void resize(int max) {
// 将大小为N <= max 的栈移动到一个新的大小为max的数组中
Item[] temp = (Item[]) new Object[max];
for (int i = 0; i < N; ++i) {
temp[i] = a[i];
}
a = temp;
}
接着是入栈、出栈的 push(Item item) 和 pop() 方法。
如果入栈时,栈满了,我们就将数组容量扩大为原来的两倍
如果出栈时,数组值使用了 1/4, 我们将数组容量缩小到原来的 1/2,使数组处于半满状态。
/**
* 将元素压入栈内
* @param item
*/
public void push(Item item) {
if (a.length == N) {
//如果栈满,扩容为原来的两倍
resize(2 * a.length);
}
a[N++] = item;
}
/**
* 将栈顶元素弹出
*/
public void pop() {
Item item = a[--N];
a[N] = null; //避免对象游离
if (N > 0 && N == a.length / 4) {
//如果数组太大了,我们将长度减半,保证栈使用率不会低于 1/4
resize(a.length / 2);
}
a[--N] = item;
}
为了支持 foreach语句按照后进先出的顺序迭代访问所有栈元素,我们定义了一个内部类 ReverseArrayIterator ,并实现了iterator接口。
/**
* 逆序迭代器
* 实现逆序遍历数组,即实现按照后进先出的顺序迭代访问所有栈元素
* @author crwen
*
*/
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
/**
* 判断是否还能继续迭代
*/
@Override
public boolean hasNext() {
return i > 0;
}
/**
* 返回当前迭代的值
*/
@Override
public Item next() {
if (i == 0) {
throw new NoSuchElementException();
}
return a[--i];
}
/**
* 删除值
*/
@Override
public void remove() {
if (i == 0) {
throw new UnsupportedOperationException();
}
a[--i] = null;
}
}
上述算法有如下优点:
- 每项操作的用时都与集合大小无关
- 空间需求总是不超过集合大小乘以一个常数
但是该算法还是存在缺陷,即某些push() 和pop()操作会调整数组的大小:这项操作的耗时和栈大小成正比。