前言
许多基础数据类型都和对象的集合有关。具体来说,数据类型的值就是一组对象的集合,所有操作都是关于添加,删除或是访问集合中的对象。接下来,介绍三种数据类型,背包,队列,栈。语言是表达的载体,所以不可避免在表达算法时,依赖于语言的一些特性。
正文
介绍两种表示对象集合的方式,即数组和链表。两种都非常基础,常常被称为顺序存储和链式存储。下面我们先介绍数组形式。
背包:是一种不支持删除元素的集合数据类型,它的目的就是帮助用例收集元素并迭代遍历所有收集到的元素。就像一个袋子里
装弹珠,不过是一次一个。从背包口添加元素,也是从背包口出来,是后进先出。
先进先出队列(简称队列):就像平常所见到的排队,从队尾添加元素,从队的前面删除
元素,就像自来水管通水。先进先出。
下压栈(简称栈):是一种基于后进先出策略的集合类型。就像试管,只能从管口倒水,从管口出水。这说明添加元素是从管口,删除元素也是从管口。
我们先来看一种容量固定的字符串栈的抽象数据类型。它只能处理String值,要求用例指定一个容量且不支持迭代(迭代后面会讲述)。实现一份API的第一步就是选择数据的表达方式,对于这个,我们显然可以选择String数组。
1 public classFixedCapacityStackOfStrings {2 private String[] a; //holds the items
3 private int N; //number of items in stack4 //create an empty stack with given capacity
5 public FixedCapacityStackOfStrings(intcapacity) {6 a = newString[capacity];7 N = 0;8 }9 public booleanisEmpty() {10 return N == 0;11 }12 public booleanisFull() {13 return N ==a.length;14 }15 public voidpush(String item) {16 a[N++] =item;17 }18 publicString pop() {19 return a[--N];20 }21 publicString peek() {22 return a[N - 1];23 }24 }
测试用例为:
1 public static voidmain(String[] args) {2 Scanner in = newScanner(System.in);3 int max =in.nextInt();4 FixedCapacityStackOfStrings stack = newFixedCapacityStackOfStrings(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操作所需的时间独立于栈的长度。通俗来说就是和栈的长度无关,与数据的多少无关,这在操作许多数据时能极大节省时间。
1 /******************************************************************************2 * Compilation: javac FixedCapacityStackOfStrings.java3 * Execution: java FixedCapacityStackOfStrings4 * Dependencies: StdIn.java StdOut.java5 *6 * Stack of strings implementation with a fixed-size array.7 *8 * % more tobe.txt9 * to be or not to - be - - that - - - is10 *11 * % java FixedCapacityStackOfStrings 5 < tobe.txt12 * to be not that or be13 *14 * Remark: bare-bones implementation. Does not do repeated15 * doubling or null out empty array entries to avoid loitering.16 *17 ******************************************************************************/
18
19 importjava.util.Iterator;20 importjava.util.NoSuchElementException;21
22 public class FixedCapacityStackOfStrings implements Iterable{23 private String[] a; //holds the items
24 private int N; //number of items in stack25
26 //create an empty stack with given capacity
27 public FixedCapacityStackOfStrings(intcapacity) {28 a = newString[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 iterator() { return newReverseArrayIterator(); }38
39
40 public class ReverseArrayIterator implements Iterator{41 private int i = N-1;42
43 public booleanhasNext() {44 return i >= 0;45 }46
47 publicString next() {48 if (!hasNext()) throw newNoSuchElementException();49 return a[i--];50 }51
52 public voidremove() {53 throw newUnsupportedOperationException();54 }55 }56
57
58 public static voidmain(String[] args) {59 int max = Integer.parseInt(args[0]);60 FixedCapacityStackOfStrings stack = newFixedCapacityStackOfStrings(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是一个类型参数,用于表示象征性的占位符。可以将
publicclassFixedCapacityStack
理解为某种元素的栈。就是它可以代替任何类型,只是你现在不知道,当你实现这个数据类型时,可以先不管他是这个栈所要收集的类型是什么,通用性特别强,能处理任意数据类型,等到在测试用例中再去定义这个类型,即把Item替换成你想要的类型。比如
FixedCapacityStackstack =newFixedCapacityStack(max)
这个时候就定义为String型的了,代表这个栈是收集字符串类型的数据。就是说在创建栈时提供具体的数据类型,他就能用栈处理任意数据类型。实际的类型必须是引用类型,但用例可以依靠自动装箱将原始数据类型转换为相应的封装类型。Java会使用类型参数Item来检查类型不匹配的错误——尽管具体类型还不知道,赋予Item类型变量的值也必须是Item类型的。由于某些原因,创建泛型数组在Java数组是不允许的,我们需要使用类型转换,
a =(Item[])newObject[cap];
虽然有警告,但可以忽略。
下面给出带有泛型的代码:
1 /******************************************************************************2 * Compilation: javac FixedCapacityStack.java3 * Execution: java FixedCapacityStack4 * Dependencies: StdIn.java StdOut.java5 *6 * Generic stack implementation with a fixed-size array.7 *8 * % more tobe.txt9 * to be or not to - be - - that - - - is10 *11 * % java FixedCapacityStack 5 < tobe.txt12 * to be not that or be13 *14 * Remark: bare-bones implementation. Does not do repeated15 * doubling or null out empty array entries to avoid loitering.16 *17 ******************************************************************************/
18
19 importjava.util.Iterator;20 importjava.util.NoSuchElementException;21
22 public class FixedCapacityStack implements Iterable{23 private Item[] a; //holds the items
24 private int N; //number of items in stack25
26 //create an empty stack with given capacity
27 public FixedCapacityStack(intcapacity) {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 iterator() { return newReverseArrayIterator(); }36
37
38 public class ReverseArrayIterator implements Iterator{39 private int i = N-1;40
41 public booleanhasNext() {42 return i >= 0;43 }44
45 publicItem next() {46 if (!hasNext()) throw newNoSuchElementException();47 return a[i--];48 }49
50 public voidremove() {51 throw newUnsupportedOperationException();52 }53 }54
55
56 public static voidmain(String[] args) {57 int max = Integer.parseInt(args[0]);58 FixedCapacityStack stack = new FixedCapacityStack(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.java3 * Execution: java ResizingArrayStack < input.txt4 * Dependencies: StdIn.java StdOut.java5 * Data files:http://algs4.cs.princeton.edu/13stacks/tobe.txt
6 *7 * Stack implementation with a resizing array.8 *9 * % more tobe.txt10 * to be or not to - be - - that - - - is11 *12 * % java ResizingArrayStack < tobe.txt13 * to be not that or be (2 left on stack)14 *15 ******************************************************************************/
16
17 importjava.util.Iterator;18 importjava.util.NoSuchElementException;19
20 /**
21 * The ResizingArrayStack class represents a last-in-first-out (LIFO) stack22 * of generic items.23 * It supports the usual push and pop operations, along with methods24 * for peeking at the top item, testing if the stack is empty, and iterating through25 * the items in LIFO order.26 *
27 * This implementation uses a resizing array, which double the underlying array28 * when it is full and halves the underlying array when it is one-quarter full.29 * The push and pop operations take constant amortized time.30 * The size, peek, and is-empty operations takes31 * constant time in the worst case.32 *
33 * For additional documentation,34 * see Section 1.3 of35 * Algorithms, 4th Edition by Robert Sedgewick and Kevin Wayne.36 *37 *@authorRobert Sedgewick38 *@authorKevin Wayne39 */
40 public class ResizingArrayStack implements Iterable{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 publicResizingArrayStack() {49 a = (Item[]) new Object[2];50 N = 0;51 }52
53 /**
54 * Is this stack empty?55 *@returntrue if this stack is empty; false otherwise56 */
57 public booleanisEmpty() {58 return N == 0;59 }60
61 /**
62 * Returns the number of items in the stack.63 *@returnthe number of items in the stack64 */
65 public intsize() {66 returnN;67 }68
69
70 //resize the underlying array holding the elements
71 private void resize(intcapacity) {72 assert capacity >=N;73 Item[] temp = (Item[]) newObject[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 *@paramitem the item to add83 */
84 public voidpush(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 *@returnthe item most recently added92 *@throwsjava.util.NoSuchElementException if this stack is empty93 */
94 publicItem 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 returnitem;102 }103
104
105 /**
106 * Returns (but does not remove) the item most recently added to this stack.107 *@returnthe item most recently added to this stack108 *@throwsjava.util.NoSuchElementException if this stack is empty109 */
110 publicItem 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 *@returnan iterator to this stack that iterates through the items in LIFO order.118 */
119 public Iteratoriterator() {120 return newReverseArrayIterator();121 }122
123 //an iterator, doesn't implement remove() since it's optional
124 private class ReverseArrayIterator implements Iterator{125 private inti;126
127 publicReverseArrayIterator() {128 i = N-1;129 }130
131 public booleanhasNext() {132 return i >= 0;133 }134
135 public voidremove() {136 throw newUnsupportedOperationException();137 }138
139 publicItem next() {140 if (!hasNext()) throw newNoSuchElementException();141 return a[i--];142 }143 }144
145
146 /**
147 * Unit tests the Stack data type.148 */
149 public static voidmain(String[] args) {150 ResizingArrayStack s = new ResizingArrayStack();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 publicItem 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 returnitem;9 }
这里
1 a[N-1] = null; //to avoid loitering
避免对象游离,Java的垃圾回收策略是回收所有无法被访问的对象的内存。pop()的实现中,被弹出的元素的引用仍然存在于数组中。这个元素永远不会被访问了,但Java收集器没法知道这一点,除非该引用被覆盖。即使用例已经不再需要这个元素,数组中的引用仍然可以让它继续存在。这种情况称为游离。避免对象游离,只需将被弹出的数组元素的值设为null,这将覆盖无用的引用并使系统可以在用例使用完被弹出的元素后回收它的内存。
2015-11-07 18:56:30