个人笔记,一点总结,大量使用原文。
使用算法(第4版),如有侵权,请联系删除
将自带一些StdIn,StdOut 之类的改回了scanner,System.out.println 方便在不导入库的情况下使用
1.定容栈
定容栈:一种表示容量固定的字符串栈的抽象数据类型
它的实例变量为一个用于保存栈中的元素的数组 a[],和一个用于保存栈中的元素数量的整数 N。要删除一个元素,我们将 N 减 1 并返回 a[N]。要添加一个元素,将 a[N] 设为新元素并将 N 加 1。
FixedCapacityStack:固定容量堆栈的字符串
public class FixedCapacityStackOfStrings
{
private String[] a; // stack entries
private int N; // size
public FixedCapacityStackOfStrings(int cap)
{ a = new String[cap]; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
public void push(String item)
{ a[N++] = item; }
public String pop()
{ return a[--N]; }
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
FixedCapacityStackOfStrings s;
s = new FixedCapacityStackOfStrings(100);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
{
String item = scanner.next();
if(!item.equals("-"))
s.push(item);
else if (!s.isEmpty())
System.out.print(s.pop() + " ");
}
System.out.println("(" + s.size() + " left on stack)");
}
}
输入测试如图所示。测试用例会从标准输入读取多 个字符串并将它们压入一个栈,当遇到 - 时它会将栈的内容弹出并打印结果。
结果如下
但有几个缺点限制了它作为万能的工具,要改进的也是这一点。经过一些修改,得出一个适用性更加广泛的实现。(下面涉及到Java的泛型)
2.泛型
FixedCapacityStackOfStrings 的第一个缺点是它只能处理 String 对象。如果需要一 个 int 值的栈,你就需要用类似的代码实现另一个类Integer,也就是把所有的 String 都替换为Integer 。Java 的参数类型(泛型)就是专门用来解决这个问题的
下面中的代码展示了如何实现一个泛型的栈的细节。它实现了一个 FixedCapacityStack 类,该类和 FixedCapacityStackOfStrings 类的区别仅在于所有的 String 都替换为 Item(一个地方除外,会在稍后讨论)
public class FixedCapacityStack<Item>
{
private Item[] a; // stack entries
private int N; // size
public FixedCapacityStack(int cap)
{ a = (Item[]) new Object[cap]; }
public boolean isEmpty() { return N == 0; }
public int size() { return N; }
public void push(Item item)
{ a[N++] = item; }
public Item pop()
{ return a[--N]; }
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
FixedCapacityStackOfStrings<String> s;
s = new FixedCapacityStackOfStrings<>(100);
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext())
{
String item = scanner.next();
if(!item.equals("-"))
s.push(item);
else if (!s.isEmpty())
System.out.print(s.pop() + " ");
}
System.out.println("(" + s.size() + " left on stack)");
}
}
Item 是一个类型参数,用于表示用例将使用的某种具体类型的占位符。可以将 FixedCapacityStack 理解为某种元素的栈。
在实现 FixedCapacityStack 时,我们并不知道 Item 的实际类型,但用例只要在创建栈时提供具体的数据类型,就能使用栈处理任意数据类型。
实际的类型必须是引用类型,但用例可以依靠自动装箱将原始数据类型转换为相应的封装类型。Java 会使用类型参数 Item 来检查类型不匹配的错误,即使具体的数据类型还不知道,赋给 Item 类型变量的值也必须是 Item 类型的。
在这里有一个非常重要的细节:我们希望在 FixedCapacityStack 的构造函数实现中创建一个泛型数组的代码如下:
a = new Item[cap];
由于某些历史和技术原因,创建泛型数组在 Java 中是不允许的,需要使用类型转换:
(但 Java 编译器会给出一条警告,不过可以忽略它)
a = (Item[]) new Object[cap];
3.调整数组大小
选择用数组表示栈内容意味着用例必须预先估计栈的最大容量。
在 Java 中,数组一旦创建, 其大小是无法改变的,因此栈使用的空间只能是这个最大容量的一部分。 选择大容量的用例在栈为空或几乎为空时会浪费大量的内存。
例如,一个交易系统可能会涉及数十亿笔交易和数千个交易的集合。即使这种系统一般都会限制每笔交易只能出现在一个集合中,但用例必须保证所有集合都有能力保存所有的交易。另一方面,如果集合变得比数组更大那么用例有可能溢出。
因此修改了数组的实现,动态调整数组 a [] 的大小,使得它既足以保存所有元素,又不至于浪费过多的空间:
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() 中,检查数组是否太小。具体来说,我们会通过检查栈大小 N 和数组大小 a.length 是否相等来检查数组是否能够容纳新的元素。如果没有多余的空间,我们会将数组的 长度加倍。然后就可以和从前一样用 a[N++] = item 插入新元素了:
public void push(Item item)
{ // 将元素压入栈顶
if (N == a.length) resize(2*a.length);
a[N++] = item;
}
类似地,在 pop () 操作中,首先删除栈顶的元素,然后如果数组的大小过大,我们将其长度减半。只需稍加思考,就可以理解正确的检测条件是栈的大小是否小于数组长度的四分之一。在数组长度减半后,它的状态近似为半满,在下一次需要改变数组大小之前,仍然可以执行多次 push () 和 pop () 操作。
public Item pop()
{ // 从栈顶删除元素
Item item = a[--N];
a[N] = null; // 避免对象游离(请见下节)
if (N > 0 && N == a.length/4) resize(a.length/2);
return item;
}
在这个实现中,栈永远不会溢出,使用率也永远不会低于四分之一(除非栈为空,那种情况下数组的大小为 1)。
public class FixedCapacityStack<Item> {
private Item[] a;
private int N;
public FixedCapacityStack(int cap)
{ a = (Item[]) new Object[cap]; }
public boolean isEmpty() { return N == 0; }
public int size() { return N;}
public void push(Item item) {
if (N == a.length) resize(2*a.length);
a[N++] = item;
}
public Item pop()
{ Item item = a[--N];
a[N] = null;
if (N > 0 && N == a.length/4)
resize(a.length);
return item;
}
private void resize(int max){
Item[] temp = (Item[]) new Object[max];
for (int i = 0; i < N; i++)
temp[i] = a[i];
a = temp;
}
}
push() 和 pop() 操作中数组大小调整的轨迹见图: