N++与++N
假如我有一个定长的数组 a =[0,3,2,1] ,其中a.length = 4
那么我如果N++的话,先输出4,再输出5,6,7
那么如果是++N的话,先输出5,然后输出6,7,8
同理--N与N--与上面是一样的逻辑
定容栈的实现是许多用例(集合数据类型--Bag,Stack,Queue )的基础模块
定容栈:
只能处理String值,并且容量要指定一个固定的数字,且不支持迭代
public class 定容栈 {
private String[] a; //栈的容量
private int N; //集合的长度
定容栈(int cap){ a =new String[cap];} //容量的大小
public boolean isEmpty(){return N==0;}
int Size(){return N;}
void push(String item){
a[N++] = item;
}
String pop(){
return a[--N];
}
}
泛型:
定容栈的缺点是只能处理String类型的值,因此我们使用泛型,让他可以处理其余的数据类型(将String替换为Item)
public class 定容栈_泛型<Item> {
private Item[] a;
private int N;
//创建泛型数组在Java中是不可以实现的,因此我们要使用,类型转换
定容栈_泛型(int cap) {
a = (Item[]) new Object[cap];
}
public int Size(){ return N; }
public void push(Item item){
a[N++]=item;
}
public Item pop(){
return a[--N];
}
}
调整数组的大小
选择用数组表示栈,意味者用例必须要预估栈的大小,同时还需要时刻检测栈是否满,防止push的时候溢出,这样很不好。
为此我们需要修改数组的实现,动态调整数组a[]的大小,使得它即足以保存所有元素,又不至于浪费过多的空间
1、因此我们需要实现一个方法将栈移动到另一个大小不同的数组中。
private void resize(int max)
{
Item[] temp = (Item[])new Object[max];//将大小为N<= max的栈移动到一个新的大小为max的数组中
for(int i=0;i<N;i++) temp[i]=a[i];//将老数组中的数据赋值给新数组中的数据
a=temp;//将老数组赋值给新数组
}
2、现在,在push()中,检查数组是否太小。通常是使用栈的大小N与数组的大小a.length是否相等来检查数组是否能够容纳新的元素,
如果没有多余的空间,就将数组的长度加倍,然后就可以使用以前的办法:a[N++]=item来插入新的元素了
public void push(Item item){
//将元素压入栈中
if(N==a.length) resize(2 * a.length)
a[N++] = item;
}
3、类似在pop()中,首先删除栈顶的元素.
public Item pop(){
//从站定删除元素
Item item = a[--N];
a[N] = null; //避免对象游离,下节会讲
下面这么写的意义:
前提我对数组长度定义100
首先假如原数据中的N是有100个的话,我中间如果没有pop的话,那么此时我resize后数组长度是200
但是我如果中间有pop的话,那么此时我触发了--N,对应的我N的数量就会发生变化(减少),但我数组是不会变得
此时,就有可能会出现N的长度等于 数组长度得1/4,那么相差就很大了,我们可以动态得对其进行调整,让他们始终是
相差2倍得关系。
if ( N>0 && N==a.length / 4 ) resize(a.length/2)
}
4、综上所述,栈永远不会一处,使用率也永远不会低于四分之一。
那么什么情况下会低于1/4呢?栈为空也就是N为0,那么数组得大小就是1了即int[] a = new int[1],这里不能为0否则报错
因为长度没有为0一说。
对象游离
上节讲到a[N]==null,寓意是将弹出的元素设置成了游离。
定义:
即使用例已经不再需要这个元素了,数组中仍然可以让他继续存在,这种情况(保存一个不需要的对象的引用),我们称为“游离”
理由:
被弹出的元素的引用仍然存在于数组中,但因为已经被弹出了,所以该元素已经是一个“孤儿了”--它永远不会被访问了,但Java的垃圾回收机制没办法
知道这一点,除非该引用被覆盖。
解决办法:
只需要将弹出的数组元素的值设为null即可,此时系统就会回收被弹出元素的内存。
迭代
本节开头已经提过,集合类数据类型的基本操作之一就是,能够使用Java的foreach语句迭代遍历处理集合中的每个元素。
例如:
前面所做的栈(Stack)
Stack<String> stack = new Stack<String>()
...
for(String s :stack)
{
StdOut.println(s)
}
...
那么如果让对象可以迭代,要求是
1、集合数据类型需要实现一个iterator()方法,并返回一个Iterator对象;
2、Iterator类必须包含2个方法:hasNext()(返回一个布尔值)和next()(返回集合中的一个泛型元素)
3、迭代其都是泛型的,因此我们可以使用参数类型Item来帮助用例遍历他们指定的任意类型的对象,因为在前面几节中使用的是数组,因此我们后续如果还要使用
数组来进行迭代的话,需要对数组进行逆序迭代遍历这个数组。(此时我们将迭代器命名为ReverseArrayIterator)
实现iterator的方法代码
在class后加入,implements Iterable<Item>.
1、然后ReverseArrayIterator中添加如下的代码
public Iterator<Item> iterator
{
return new ReverseArrayIterator();
}
2、迭代器是什么?
他是一个实现了hasNext()和next()方法的类的对象.
private class ReverseArrayIterator implements Iterator<Item>
{
private int i=N;
public boolean hasNext(){ return i>0;}
public Item next() { return a[--i]; } //支持后进先出的迭代
public void remove(){}
}
尽管接口指定了一个remove()方法,但在本书中remove()方法总为空,因为我们要避免在迭代中穿插又能够修改数据结构
的操作。
而对于ReverseArrayIterator类中,这些方法都只需要一行代码,他们实现在“栈类的一个嵌套类中”:
为何将如下代码放到“类种的嵌套类中”?
理由:嵌套类可以访问包含它的类的实例变量,在这里就是a[]和N。
同时为了和Iterator的结构保持一致,我们应当在2种情况下抛出异常:
- 如果用例调用了remove()则抛出UnsupportedOperationException
- 如果用例在调用next()时i为0则抛出NoSuchElementException。
为什么在上述2种情况下抛出异常?
因为我们只会在foreach语法中使用迭代器,而在foreach中上述情况都不会出现。
注意:
我们要在开头处加入:
import java.util.Iterator
理由:
因为Iterator不在Java.lang中
例子:
现在我们就通过上面所学的迭代,来重新定义下压栈
{
这份泛型的可迭代的Stack API的实现是所有集合类(实现了 implements Iterable<Item>)抽象数据(Item)类型实现的模板,它将
所有的元素保存到数组中,并动态的调整数组的大小以保持数组大小和栈大小之比小于一个常数。
}
public class ResizingArrayStack<Item> implements Iterable<Item> {
//创建容量数组;默认长度为1,该长度会动态调整。(即栈元素)
private Item[] a = (Item[]) new Object[1];
private int N = 0; //元素数量
public Boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
//复制数组
public Item[] resize(int max) {
Item[] item = (Item[]) new Object[max];
for (int i = 0; i < N; i++) item[i] = a[i];
return a = item; //将老数组变为新数组
}
//往栈中压入元素
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; //此时的N是--N后的N
if (N > 0 && N == a.length / 4) resize(a.length / 2);
return item;
}
public Iterator<Item> iterator() {
return new ReverseArrayIterator();
}
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
public boolean hasNext() {
return i > 0;
}
public Item next() {
return a[--i];
} //支持后进先出的迭代
public void remove() {
}
}
上述算法的缺点在于某些push()和pop()操作会调整数组的大小,这项操作的耗时和栈的大小成正比。
为此我们下面要学习一种能够客服该缺陷的方法,使用完全不同的方式来组织数据。----既 “链表”