下压栈(LIFO)


算法1.1 可动态调整数组大小的实现

  1. public class ResizingArrayStack<Item> implements Iterable<Item> {
  2. private Item[] a = (Item []) new Object[1];//元素栈
  3. private int N;//元素个数
  4. public boolean isEmpty() {
  5. return N == 0;
  6. }
  7. public int size() {
  8. return N;
  9. }
  10. private void resize(int max) {
  11. Item[] temp = (Item []) new Object[max];
  12. for(int i = 0; i < max; i++) {
  13. temp[i] = a[i];
  14. }
  15. a = temp;
  16. }//调整数组大小
  17. public void push(Item item) {
  18. if(N == a.length) resize(2*a.length);
  19. a[N++] = item;
  20. }//添加元素到栈顶
  21. public Item pop() {
  22. Item item = a[--N];
  23. a[N] = null;//防止对象游离
  24. if(N > 0 && N == a.length/4) resize(a.length/2);
  25. return item;
  26. }//从栈顶删除元素
  27. @Override
  28. public Iterator<Item> iterator() {
  29. return new ReverseArrayIterator;
  30. }
  31. private class ReverseArrayIterator implements Iterator<Item> {
  32. private i = N;
  33. @Override
  34. public boolean hasNext() {
  35. return i > 0;
  36. }
  37. @Override
  38. public Item next() {
  39. return a[--i];
  40. }
  41. @Override
  42. public void remove() {}
  43. }
  44. }

要点

  1. 可变数组下压栈的主类和内部类需要分别实现Iterable,Iterator接口,从而实现LIFO逆序迭代,否则会按照数组FIFO迭代;
  2. algs4书中对于remove()总为空,避免在迭代中穿插修改数据结构的操作
  3. 为保证数组缩减后有一半的空间利用率,判断条件应该是N == a.length/4
  4. 删除栈顶元素时,实际上被弹出元素的引用仍保存在数组中,但该元素永远不会被访问,直到下次push()后,原有引用指向新元素.Java垃圾收集器才会回收空间,期间造成空间浪费,使用a[N] = null避免元素游离.

特点

  1. 算法1.1的每项操作用时与集合大小无关:操作始终只针对栈顶元素;
  2. 空间需求总是不超过集合大小乘一个常数;
  3. 栈永远不会溢出,使用率也不会低于四分之一,除非栈为空时,数组大小为1;

算法1.2 链表实现

  1. import edu.princeton.cs.algs4.StdIn;
  2. import edu.princeton.cs.algs4.StdOut;
  3. import java.util.Iterator;
  4. public class Stack<Item> implements Iterable<Item> {
  5. private Node first;
  6. private int N;
  7. private class Node {
  8. Item item;
  9. Node next;
  10. }
  11. public boolean isEmpty(){ return N == 0;}
  12. public int size(){ return N; }
  13. public void push(Item item){
  14. Node oldFirst = first ;
  15. first = new Node();
  16. first.item = item;
  17. first.next = oldFirst;
  18. N++;
  19. }
  20. public Item pop(){
  21. Item item;
  22. item = first.item;
  23. first = first.next;
  24. N--;
  25. return item;
  26. }
  27. @Override
  28. public Iterator<Item> iterator(){
  29. return new ListIterator();
  30. }
  31. private class ListIterator implements Iterator<Item>{
  32. private Node current = first;
  33. @Override
  34. public boolean hasNext(){ return current != null;}
  35. @Override
  36. public Item next(){
  37. Item item = current.item;
  38. current = current.next;
  39. return item;
  40. }
  41. @Override
  42. public void remove(){}
  43. }
  44. }

要点

  1. 实例变量:头结点first
  2. 完成内部类Node;
  3. isEmpty()可以判断N == 0或first == null;
  4. 迭代实现同上

特点

  1. 可以处理任意类型数据;
  2. 所需空间总是和集合大小成正比;
  3. 操作所需时间和集合大小无关(链式结构);

关于内部类实现泛型接口*

算法内部类
  1. private class ListIterator implements Iterator<Item>{}
  2. //不是private class ListIterator<Item> implements Iterator<Item>

算法外部类
  1. public class Stack<Item> implements Iterable<Item>

当类实现泛型接口时,不能确定接口中的泛型,这时就要求这个类也必须定义泛型,而且泛型名称要一致,因此主类Stack后也要定义泛型;当内部类实现和主类泛型类型一致的接口(本算法中),此时泛型已经可以由主类的定义判断,则内部类的泛型无需在定义泛型,如果重复定义会提示错误.