Algorithms (Java) Bags,Queues,and Stacks(1)
前言
许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加,删除或是访问集合中的对象。接下来,介绍三种数据类型,背包,队列,栈。语言是表达的载体,所以不可避免在表达算法时,依赖于语言的一些特性。
正文
介绍两种表示对象集合的方式,即数组和链表。两种都非常基础,常常被称为顺序存储和链式存储。下面我们先介绍数组形式。
背包:是一种不支持删除元素的集合数据类型,它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素。就像一个袋子里
装弹珠,不过是一次一个。从背包口添加元素,也是从背包口出来,是后进先出。
先进先出队列(简称队列):就像平常所见到的排队,从队尾添加元素,从队的前面删除
元素,就像自来水管通水。先进先出。
下压栈(简称栈):是一种基于后进先出策略的集合类型。就像试管,只能从管口倒水,从管口出水。这说明添加元素是从管口,删除元素也是从管口。
我们先来看一种容量固定的字符串栈的抽象数据类型。它只能处理String值,要求用例指定一个容量且不支持迭代(迭代后面会讲述)。实现一份API的第一步就是选择数据的表达方式,对于这个,我们显然可以选择String数组。
1 public class FixedCapacityStackOfStrings { 2 private String[] a; // holds the items 3 private int N; // number of items in stack 4 // create an empty stack with given capacity 5 public FixedCapacityStackOfStrings(int capacity) { 6 a = new String[capacity]; 7 N = 0; 8 } 9 public boolean isEmpty() { 10 return N == 0; 11 } 12 public boolean isFull() { 13 return N == a.length; 14 } 15 public void push(String item) { 16 a[N++] = item; 17 } 18 public String pop() { 19 return a[--N]; 20 } 21 public String peek() { 22 return a[N - 1]; 23 } 24 }
测试用例为:
1 public static void main(String[] args) { 2 Scanner in = new Scanner(System.in); 3 int max = in.nextInt(); 4 FixedCapacityStackOfStrings stack = new FixedCapacityStackOfStrings(max); 5 while (!StdIn.isEmpty()) { 6 String item = StdIn.readString(); 7 if (!item.equals("-")) 8 stack.push(item); 9 else if (stack.isEmpty()) 10 StdOut.println("BAD INPUT"); 11 else 12 StdOut.print(stack.pop() + " "); 13 } 14 StdOut.println(); 15 // print what's left on the stack 16 StdOut.print("Left on stack: "); 17 for (String s : stack) { 18 StdOut.print(s + " "); 19 } 20 StdOut.println(); 21 }
这种实现的主要性能特点是push和pop操作所需的时间独立于栈的长度。通俗来说就是和栈的长度无关,与数据的多少无关,这在操作许多数据时能极大节省时间。
下面给出这段代码的完整版(注意将StdOut.javahttp://introcs.cs.princeton.edu/java/15inout/StdOut.java和StdIn.javahttp://introcs.cs.princeton.edu/java/stdlib/StdIn.java添加加你文件所在的包下):
1 /****************************************************************************** 2 * Compilation: javac FixedCapacityStackOfStrings.java 3 * Execution: java FixedCapacityStackOfStrings 4 * Dependencies: StdIn.java StdOut.java 5 * 6 * Stack of strings implementation with a fixed-size array. 7 * 8 * % more tobe.txt 9 * to be or not to - be - - that - - - is 10 * 11 * % java FixedCapacityStackOfStrings 5 < tobe.txt 12 * to be not that or be 13 * 14 * Remark: bare-bones implementation. Does not do repeated 15 * doubling or null out empty array entries to avoid loitering. 16 * 17 ******************************************************************************/ 18 19 import java.util.Iterator; 20 import java.util.NoSuchElementException; 21 22 public class FixedCapacityStackOfStrings implements Iterable<String> { 23 private String[] a; // holds the items 24 private int N; // number of items in stack 25 26 // create an empty stack with given capacity 27 public FixedCapacityStackOfStrings(int capacity) { 28 a = new String[capacity]; 29 N = 0; 30 } 31 32 public boolean isEmpty() { return N == 0; } 33 public boolean isFull() { return N == a.length; } 34 public void push(String item) { a[N++] = item; } 35 public String pop() { return a[--N]; } 36 public String peek() { return a[N-1]; } 37 public Iterator<String> iterator() { return new ReverseArrayIterator(); } 38 39 40 public class ReverseArrayIterator implements Iterator<String> { 41 private int i = N-1; 42 43 public boolean hasNext() { 44 return i >= 0; 45 } 46 47 public String next() { 48 if (!hasNext()) throw new NoSuchElementException(); 49 return a[i--]; 50 } 51 52 public void remove() { 53 throw new UnsupportedOperationException(); 54 } 55 } 56 57 58 public static void main(String[] args) { 59 int max = Integer.parseInt(args[0]); 60 FixedCapacityStackOfStrings stack = new FixedCapacityStackOfStrings(max); 61 while (!StdIn.isEmpty()) { 62 String item = StdIn.readString(); 63 if (!item.equals("-")) stack.push(item); 64 else if (stack.isEmpty()) StdOut.println("BAD INPUT"); 65 else StdOut.print(stack.pop() + " "); 66 } 67 StdOut.println(); 68 69 // print what's left on the stack 70 StdOut.print("Left on stack: "); 71 for (String s : stack) { 72 StdOut.print(s + " "); 73 } 74 StdOut.println(); 75 } 76 }
(tobe.txt文件内容为:to be or not to - be - - that - - - is)
分析
缺点:只能处理String数组。
如果我们想处理double型,或者其他类型,则得将所有String换成double,这是极其不方便的。但是Java有一种机制可以解决这个问题,就是泛型。就是将所有的String都替换成Item(但在测试用例中不用改,正是这里的类型会将Item替换成你所填入的类型),Item是一个类型参数,用于表示象征性的占位符。可以将
public class FixedCapacityStack<Item>
理解为某种元素的栈。就是它可以代替任何类型,只是你现在不知道,当你实现这个数据类型时,可以先不管他是这个栈所要收集的类型是什么,通用性特别强,能处理任意数据类型,等到在测试用例中再去定义这个类型,即把Item替换成你想要的类型。比如
FixedCapacityStack<String> stack = new FixedCapacityStack<String>(max)
这个时候就定义为String型的了,代表这个栈是收集字符串类型的数据。就是说在创建栈时提供具体的数据类型,他就能用栈处理任意数据类型。实际的类型必须是引用类型,但用例可以依靠自动装箱将原始数据类型转换为相应的封装类型。Java会使用类型参数Item来检查类型不匹配的错误——尽管具体类型还不知道,赋予Item类型变量的值也必须是Item类型的。由于某些原因,创建泛型数组在Java数组是不允许的,我们需要使用类型转换,
a = (Item[]) new Object[cap];
虽然有警告,但可以忽略。
下面给出带有泛型的代码:
1 /****************************************************************************** 2 * Compilation: javac FixedCapacityStack.java 3 * Execution: java FixedCapacityStack 4 * Dependencies: StdIn.java StdOut.java 5 * 6 * Generic stack implementation with a fixed-size array. 7 * 8 * % more tobe.txt 9 * to be or not to - be - - that - - - is 10 * 11 * % java FixedCapacityStack 5 < tobe.txt 12 * to be not that or be 13 * 14 * Remark: bare-bones implementation. Does not do repeated 15 * doubling or null out empty array entries to avoid loitering. 16 * 17 ******************************************************************************/ 18 19 import java.util.Iterator; 20 import java.util.NoSuchElementException; 21 22 public class FixedCapacityStack<Item> implements Iterable<Item> { 23 private Item[] a; // holds the items 24 private int N; // number of items in stack 25 26 // create an empty stack with given capacity 27 public FixedCapacityStack(int capacity) { 28 a = (Item[]) new Object[capacity]; // no generic array creation 29 N = 0; 30 } 31 32 public boolean isEmpty() { return N == 0; } 33 public void push(Item item) { a[N++] = item; } 34 public Item pop() { return a[--N]; } 35 public Iterator<Item> iterator() { return new ReverseArrayIterator(); } 36 37 38 public class ReverseArrayIterator implements Iterator<Item> { 39 private int i = N-1; 40 41 public boolean hasNext() { 42 return i >= 0; 43 } 44 45 public Item next() { 46 if (!hasNext()) throw new NoSuchElementException(); 47 return a[i--]; 48 } 49 50 public void remove() { 51 throw new UnsupportedOperationException(); 52 } 53 } 54 55 56 public static void main(String[] args) { 57 int max = Integer.parseInt(args[0]); 58 FixedCapacityStack<String> stack = new FixedCapacityStack<String>(max); 59 while (!StdIn.isEmpty()) { 60 String item = StdIn.readString(); 61 if (!item.equals("-")) stack.push(item); 62 else if (stack.isEmpty()) StdOut.println("BAD INPUT"); 63 else StdOut.print(stack.pop() + " "); 64 } 65 StdOut.println(); 66 67 // print what's left on the stack 68 StdOut.print("Left on stack: "); 69 for (String s : stack) { 70 StdOut.print(s + " "); 71 } 72 StdOut.println(); 73 } 74 }
可和前面的没有泛型的代码对比理解。
下面讲述另一个缺点:一开始必须预先估计栈的最大容量。选择大的容量会浪费内存,栈的使用空间只能是这个最大容量的一部分。但又不能导致溢出,因此我们必须去判断栈是否已满。因此我们希望解决这个问题,创建一个数组,既要足以保存所有的元素,又不至于浪费过多的空间。
第一步:将栈移动到另一个大小不同的数组,然后比较栈大小N和数组大小a.length是否相等来检查数组是否能容纳新的元素,如果没有多余的空间,我们会将数组的长度加倍。当删除元素时,检测栈大小是否小于数组的四分之一,如果小于,则将数组长度减小为二分之一。这样,栈永远不会溢出,使用率永远不会低于四分之一(除非栈为空)。
1 /****************************************************************************** 2 * Compilation: javac ResizingArrayStack.java 3 * Execution: java ResizingArrayStack < input.txt 4 * Dependencies: StdIn.java StdOut.java 5 * Data files: http://algs4.cs.princeton.edu/13stacks/tobe.txt 6 * 7 * Stack implementation with a resizing array. 8 * 9 * % more tobe.txt 10 * to be or not to - be - - that - - - is 11 * 12 * % java ResizingArrayStack < tobe.txt 13 * to be not that or be (2 left on stack) 14 * 15 ******************************************************************************/ 16 17 import java.util.Iterator; 18 import java.util.NoSuchElementException; 19 20 /** 21 * The <tt>ResizingArrayStack</tt> class represents a last-in-first-out (LIFO) stack 22 * of generic items. 23 * It supports the usual <em>push</em> and <em>pop</em> operations, along with methods 24 * for peeking at the top item, testing if the stack is empty, and iterating through 25 * the items in LIFO order. 26 * <p> 27 * This implementation uses a resizing array, which double the underlying array 28 * when it is full and halves the underlying array when it is one-quarter full. 29 * The <em>push</em> and <em>pop</em> operations take constant amortized time. 30 * The <em>size</em>, <em>peek</em>, and <em>is-empty</em> operations takes 31 * constant time in the worst case. 32 * <p> 33 * For additional documentation, 34 * see <a href="http://algs4.cs.princeton.edu/13stacks">Section 1.3</a> of 35 * <i>Algorithms, 4th Edition</i> by Robert Sedgewick and Kevin Wayne. 36 * 37 * @author Robert Sedgewick 38 * @author Kevin Wayne 39 */ 40 public class ResizingArrayStack<Item> implements Iterable<Item> { 41 private Item[] a; // array of items 42 private int N; // number of elements on stack 43 44 45 /** 46 * Initializes an empty stack. 47 */ 48 public ResizingArrayStack() { 49 a = (Item[]) new Object[2]; 50 N = 0; 51 } 52 53 /** 54 * Is this stack empty? 55 * @return true if this stack is empty; false otherwise 56 */ 57 public boolean isEmpty() { 58 return N == 0; 59 } 60 61 /** 62 * Returns the number of items in the stack. 63 * @return the number of items in the stack 64 */ 65 public int size() { 66 return N; 67 } 68 69 70 // resize the underlying array holding the elements 71 private void resize(int capacity) { 72 assert capacity >= N; 73 Item[] temp = (Item[]) new Object[capacity]; 74 for (int i = 0; i < N; i++) { 75 temp[i] = a[i]; 76 } 77 a = temp; 78 } 79 80 /** 81 * Adds the item to this stack. 82 * @param item the item to add 83 */ 84 public void push(Item item) { 85 if (N == a.length) resize(2*a.length); // double size of array if necessary 86 a[N++] = item; // add item 87 } 88 89 /** 90 * Removes and returns the item most recently added to this stack. 91 * @return the item most recently added 92 * @throws java.util.NoSuchElementException if this stack is empty 93 */ 94 public Item pop() { 95 if (isEmpty()) throw new NoSuchElementException("Stack underflow"); 96 Item item = a[N-1]; 97 a[N-1] = null; // to avoid loitering 98 N--; 99 // shrink size of array if necessary 100 if (N > 0 && N == a.length/4) resize(a.length/2); 101 return item; 102 } 103 104 105 /** 106 * Returns (but does not remove) the item most recently added to this stack. 107 * @return the item most recently added to this stack 108 * @throws java.util.NoSuchElementException if this stack is empty 109 */ 110 public Item peek() { 111 if (isEmpty()) throw new NoSuchElementException("Stack underflow"); 112 return a[N-1]; 113 } 114 115 /** 116 * Returns an iterator to this stack that iterates through the items in LIFO order. 117 * @return an iterator to this stack that iterates through the items in LIFO order. 118 */ 119 public Iterator<Item> iterator() { 120 return new ReverseArrayIterator(); 121 } 122 123 // an iterator, doesn't implement remove() since it's optional 124 private class ReverseArrayIterator implements Iterator<Item> { 125 private int i; 126 127 public ReverseArrayIterator() { 128 i = N-1; 129 } 130 131 public boolean hasNext() { 132 return i >= 0; 133 } 134 135 public void remove() { 136 throw new UnsupportedOperationException(); 137 } 138 139 public Item next() { 140 if (!hasNext()) throw new NoSuchElementException(); 141 return a[i--]; 142 } 143 } 144 145 146 /** 147 * Unit tests the <tt>Stack</tt> data type. 148 */ 149 public static void main(String[] args) { 150 ResizingArrayStack<String> s = new ResizingArrayStack<String>(); 151 while (!StdIn.isEmpty()) { 152 String item = StdIn.readString(); 153 if (!item.equals("-")) s.push(item); 154 else if (!s.isEmpty()) StdOut.print(s.pop() + " "); 155 } 156 StdOut.println("(" + s.size() + " left on stack)"); 157 } 158 }
注意这里:
1 public Item pop() { 2 if (isEmpty()) throw new NoSuchElementException("Stack underflow"); 3 Item item = a[N-1]; 4 a[N-1] = null; // to avoid loitering 5 N--; 6 // shrink size of array if necessary 7 if (N > 0 && N == a.length/4) resize(a.length/2); 8 return item; 9 }
这里
1 a[N-1] = null; // to avoid loitering
避免对象游离,Java的垃圾回收策略是回收所有无法被访问的对象的内存。pop()的实现中,被弹出的元素的引用仍然存在于数组中。这个元素永远不会被访问了,但Java收集器没法知道这一点,除非该引用被覆盖。即使用例已经不再需要这个元素,数组中的引用仍然可以让它继续存在。这种情况称为游离。避免对象游离,只需将被弹出的数组元素的值设为null,这将覆盖无用的引用并使系统可以在用例使用完被弹出的元素后回收它的内存。
2015-11-07 18:56:30