《数据结构与算法分析Java语言描述》第二章3.1到3.5节读书笔记
3.1 抽象数据类型
抽象数据类型(ADT):是带有一组操作的一些对象的集合
对于集合ADT,可以有像添加(add)、删除(remove)、与包含(contain)这样的一些操作。也可以只要两种操作,并(union)和查找(find)。
ADT的实现一般取决与程序设计者,当ADT被程序设计者正确的实现后,使用该语言可以不必要知道他们如何实现。
3.2表ADT
形如A 0 , A 1 , A 2 , … , A N − 1 A_0,A_1,A_2,\ldots, A_{N-1} A 0 , A 1 , A 2 , … , A N − 1 ,这个表的大小为N,如果该表的大小为0,则该表称为空表 ,除空表外的任何表,A i A_i A i 后继A i − 1 A_{i-1} A i − 1 (或继A i − 1 A_{i-1} A i − 1 之后,i<N),并称A i − 1 A_{i-1} A i − 1 前驱A i ( i > 0 ) A_i(i>0) A i ( i > 0 ) 。
常用操作(不同的语言有不同的实现):
printList :java没有直接实现,如果是List,可以直接打印,因为其父类AbstractCollection重写了toString方法。
如果是数组,可以通过Arrays.toString(数组),方法实现打印。
makeEmpty :如果是List,有clear()方法,将所有元素置为null。
find :找到某元素第一次出现的位置,jdk好像没实现
insert :jdk实现的是add方法,有重载,不指定index,则默认最后,可以指定index。
findKth :查找某个index下的元素,如果是List,有get(index)方法,数组则int[index]。
remove :移除某个index的元素
3.2.1表的简单数组实现
数组的实现,使printList可以用线性的时间O(N)执行,findKth则花费常数时间。插入和删除开销昂贵,在最坏的情况下,在索引为0的位置插入,需要将所有元素向后移动一位。而删除一个位置则需要将所有元素向前移动一位。两种操作的时间都为O(N)。但是如果所有元素都操作在表的高端(末端,数组有容量的情况下,否则需要昂贵的扩容)。添加和删除只需要O(1)的时间。
3.2.2简单链表
链表由一系列节点组成,这些节点在内存中可以不相连,每个节点均含有表元素和包含该元素后继节点的链 link(指针),称之为next链。最后一个节点的next链为null。
执行printList与find(x),可以从表的头的第一个节点开始使用后继的next遍历该表即可,这种操作和数组一样,是线性的时间。
remove方法通过修改一个next指针实现,insert方法使用new操作符从系统中新增一个节点,此后执行两次引用调整。如图所示,链表的插入与删除都只需要花费常数的时间。
双向链表:让每一个节点持有一个指向它在表中的前驱节点的链
3.3 Java Collections API中的表
表ADT是在Collections API中的实现数据结构之一。
3.3.1 Collections接口
Collection接口扩展Iterable接口(可迭代),实现该接口的类可以拥有简单的for循环,
1 2 3 4 5 6 public static <AnyType> void print (Collection<AnyType> coll) { for (AnyType item : coll) { System.out.println(item); } }
3.3.2 Iterator接口
实现Iterator接口的集合必须实现iterator接口方法,返回一个iterator,该iterator将当前位置的概念在对象内部存储。该iterator是java.util包中定义。
每次对next调用,都给出该集合的下一项,hasNext()用来返回是否存在下一项。对于实现Iterable接口的增强for循环,编译器会重写,将其转换为由迭代器iterator进行迭代。所以前面的print被编译器改写为。
1 2 3 4 5 6 7 public static <AnyType> void print (Collection<AnyType> coll) { Iterator<AnyType> iterator = coll.iterator(); while (iterator.hasNext()) { AnyType item = iterator.next(); System.out.println(item); } }
iterator接口还有一个remove方法,该方法删除next返回的项,虽然Collection接口也有remove方法,但是Iterator方法有更多优点。Collecction的remove方法,必须要指定删除那一项,而Iterator可以在遍历的过程中删除。如果直接使用for循环remove集合中的某个元素,则会抛出异常,这时就应该使用迭代器的remove方法。
3.3.3 List接口与继承的类
List接口继承了Collection接口,如下包括其自己定义的一些重要的方法
1 2 3 4 5 6 7 8 public interface MyList <T> extends Collection <T> { T get (int index) ; T set (int index, T newVal) ; void add (int index, T x) ; void remove (int index) ; ListIterator<T> listIterator () ; }
List ADT有两种流行的实现方式,ArrayList是可变数组的具体实现,具有数组的特点,get与set花费常数的时间,add与remove的花费线性O(N)的时间(不在末端的情况)。LinkedList则实现了一种双向链表。具有一系列操作首尾位置的方法,其具有链表的特点,get的时间近似线性。
构造一个新的List
1 2 3 4 5 6 public static void makeList1 (List<Integer> lst, int N) { lst.clearn(); for (int i = 0 ; i < N; i++) { lst.add(i); } }
该构造,无论传入的是ArrayList还是LinkedList都是O(N),每次都是在末尾添加。不考虑ArrayList的扩容。
在表的前端插入元素
1 2 3 4 5 6 public static void makeList (List<Integer> lst, int N) { lst.clearn(); for (int i = 0 ; i < N; i++) { lst.add(0 , i); } }
该构造,对于ArrayList是O ( N 2 ) O(N^2) O ( N 2 ) ,因为其每次插入花费时间O(N),对于LinkedList则花费时间O(N)
求表中所有元素的和
1 2 3 4 5 6 7 public static int sum (List<Integer> lst { int total = 0 ; for (int i = 0 ; i < N; i++) { total += lst.get(i); } return total; }
对于ArrayList其时间为O(N),LinkedList时间为O ( N 2 ) O(N^2) O ( N 2 ) ,因为其每次获取元素需要O(N)的时间。如果使用增强for循环,则两个都是O(N),将使用迭代器一直迭代下去。对搜索而言,两者都是低效的,contains与remove方法均花费线性时间。
3.3.4 LikedList中的remove方法
考虑如下一个例子,删除表中的所有偶数。表[6,5,1,4,2],删除后表中仅有5,1两个元素。
对于ArrayList,其删除的操作是O(N),不是,一个好策略,对于LinkedList,如果不使用迭代器则是O(N)(首先需要找到该元素才能删除),如果使用迭代器,则一次删除为O(1)。 如果使用普通的while循环,那么对于ArrayList是O ( N 2 ) O(N^2) O ( N 2 ) 的时间,对于LinkedList也是O(N^2)的时间。
1 2 3 4 5 6 7 8 9 10 11 12 public static void removeEvensVer1 (List<Integer> lst) { int i = 0 ; while (i < lst.size()) { if (lst.get(i) % 2 == 0 ) { lst.remove(i); } else { i ++; } } }
如下,该方法使用了增强for循环(即使用了迭代器),但是使用了collection的remove方法,该方法必须再次搜索该项,因此remove执行时间仍然为O(N),总体时间为O ( N 2 ) O(N^2) O ( N 2 ) ,并且同上,对增强for循环使用的基础迭代器中,collection的remove元素也是非法的
1 2 3 4 5 6 7 8 9 public static void removeEvensVer1 (List<Integer> lst) { for (Integer x : lst) { if (x % 2 == 0 ) { lst.remove(x); } } }
如下使用迭代器,对于LinkedList是O(N)的复杂度,对于ArrayList则依旧是O ( N 2 ) O(N^2) O ( N 2 ) 的复杂度(remove后,数组必须整体向前移动)
1 2 3 4 5 6 7 8 9 public static List<Integer> removeEvensVer3 (List<Integer> lst) { Iterator<Integer> itr = lst.iterator(); while (itr.hasNext()) { if (itr.next() % 2 == 0 ) { itr.remove(); } } return lst; }
3.3.5 ListIterator接口
该接口只能被List使用,其扩展了Iterator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface ListIterator <AnyType> extends Iteerator <AnyType> { boolean hasPrevious () ; AnyType previous () ; boolean hasNext () ; AnyType next () ; void add (AnyType x) ; void set (AnyType newVal) }
3.4 ArrayList类的实现
此处手动实现一个MyArrayList,该类独立实现。将具有以下特点:
将保持基础数组,数组的容量,存储当前项数
将提供方法改变数组的容量,(将改变容量后的数组拷贝到新数组)
提供get与set实现
提供基本的例程,size()与isEmpty()实现,clear()实现,提供remove(),add()
实现Iterator接口,提供next,hasNext,remove。
3.4.1 基本类
该类不检测非法的迭代器remove方法。会在之后的3.5节实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 public class MyArrayList <AnyType> implements Iterable <AnyType> { private static final int DEFAULT_CAPACITY = 10 ; private int theSize; private AnyType[] theItems; public MyArrayList () { doClear(); } public void clear () { doClear(); } private void doClear () { theSize = 0 ; ensureCapacity(DEFAULT_CAPACITY); } public int size () { return theSize; } public boolean isEmpty () { return size() == 0 ; } public void trimToSize () { ensureCapacity(size()); } public AnyType get (int idx) { if (idx < 0 || idx >= size()) { throw new ArrayIndexOutOfBoundsException (); } return theItems[idx]; } public AnyType set (int idx, AnyType newVal) { if (idx < 0 || idx >= size()) { throw new ArrayIndexOutOfBoundsException (); } AnyType old = theItems[idx]; theItems[idx] = newVal; return old; } public void ensureCapacity (int newCapacity) { if (newCapacity < theSize) { return ; } AnyType[] old = theItems; theItems = (AnyType[]) new Object [newCapacity]; for (int i = 0 ; i < size(); i++) { theItems[i] = old[i]; } } public boolean add (AnyType x) { add(size(), x); return true ; } public void add (int idx, AnyType x) { if (theItems.length == size()) { ensureCapacity(size() * 2 + 1 ); } for (int i = theSize; i > idx; i--) { theItems[i] = theItems[i-1 ]; } theItems[idx] = x; theSize++; } public AnyType remove (int idx) { AnyType removeItem = theItems[idx]; for (int i = idx; i < size() - 1 ; i++) { theItems[i] = theItems[i + 1 ]; } theSize--; return removeItem; } @Override public Iterator<AnyType> iterator () { return new ArrayListIterator (); } private class ArrayListIterator implements Iterator <AnyType> { private int current = 0 ; @Override public boolean hasNext () { return current < size(); } @Override public AnyType next () { if (!hasNext()) { throw new NoSuchElementException (); } return theItems[current++]; } @Override public void remove () { MyArrayList.this .remove(current-1 ); } } }
3.4.2 嵌套类与内部类
迭代器-内部类 :可以使用,推荐这种(就是上面的实现)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class MyArrayList2 <AnyType> implements Iterable <AnyType> { private int theSize; private AnyType[] theItems; @Override public Iterator<AnyType> iterator () { return new ArrayListIterator (); } private class ArrayListIterator implements Iterator { private int current = 0 ; @Override public boolean hasNext () { return current < size(); } @Override public AnyType next () { return theItems[current++]; } @Override public void remove () { MyArrayList2.this .remove(--current); } } }
迭代器-嵌套类 :就是把内部类申明为静态的,同时指定泛型,构造函数传入外部参数,可以使用,但不推荐,使用上面的。故不再列出。
3.5 LinkedList实现
同上,此处将手动实现一个LinkedList
MyLinkedList类本身,包含到两端的链、表的大小和一些方法
Node类,私有嵌套(静态内部)类,一个Node节点包含一个数据以及到前一个节点链和到下一个节点的链,还有构造方法
LinkedListIterator类,抽象了位置的概念,私有类,实现Iterator,提供next方法、hasNext和remove实现
3.5.1 基本类
该链表主要由Node节点组成,迭代器去遍历实现,迭代器存储当前节点的引用。该实现真实的链表前端额外创建一个节点,称为头节点,末端也额外创建一个节点,称为尾节点 。使用这些节点的好处在于简化编码,删除头节点与尾节点都不再是特殊情况(删除算法也需要访问被删除的前一个节点)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 public class MyLinkedList <AnyType> implements Iterable <AnyType> { private static class Node <AnyType> { public AnyType data; public Node<AnyType> prev; public Node<AnyType> next; public Node (AnyType d, Node<AnyType> p, Node<AnyType> n) { data = d; prev = p; next = n; } } private int theSize; private int modCount = 0 ; private Node<AnyType> beginMaker; private Node<AnyType> endMarker; public MyLinkedList () { doClear(); } public void clear () { doClear(); } private void doClear () { beginMaker = new Node <>(null , null , null ); endMarker = new Node <>(null , beginMaker, null ); beginMaker.next = endMarker; theSize = 0 ; modCount++; } public int size () { return theSize; } public boolean isEmpty () { return size() == 0 ; } public boolean add (AnyType x) { add(size(), x); return true ; } public void add (int idx, AnyType x) { addBefore(getNode(idx, 0 , size()), x); } public AnyType get (int idx) { return getNode(idx).data; } public AnyType set (int idx, AnyType newVal) { Node<AnyType> p = getNode(idx); AnyType oldVal = p.data; p.data = newVal; return oldVal; } public AnyType remove (int idx) { return remove(getNode(idx)); } private void addBefore (Node<AnyType> p, AnyType x) { Node<AnyType> newNode = new Node <>(x, p.prev, p); newNode.prev.next = newNode; p.prev = newNode; theSize++; modCount++; } private AnyType remove (Node<AnyType> p) { p.next.prev = p.prev; p.prev.next = p.next; theSize--; return p.data; } private Node<AnyType> getNode (int idx) { return getNode(idx, 0 , size() -1 ); } private Node<AnyType> getNode (int idx, int lower, int upper) { Node<AnyType> p; if (idx < lower || idx > upper) { throw new IndexOutOfBoundsException (); } if (idx < size() / 2 ) { p = beginMaker.next; for (int i = 0 ; i < idx; i++) { p = p.next; } } else { p = endMarker; for (int i = size(); i > idx; i--) { p = p.prev; } } return p; } @Override public Iterator<AnyType> iterator () { return new LinkedListIterator (); } private class LinkedListIterator implements Iterator <AnyType> { private MyLinkedList.Node<AnyType> current = beginMaker.next; private int expectedModCount = modCount; private boolean okToRemove = false ; @Override public boolean hasNext () { return current != endMarker; } @Override public AnyType next () { if (modCount != expectedModCount) { throw new ConcurrentModificationException (); } if (!hasNext()) { throw new NoSuchElementException (); } AnyType nextItem = current.data; current = current.next; okToRemove = true ; return nextItem; } @Override public void remove () { if (modCount != expectedModCount) { throw new ConcurrentModificationException (); } if (!okToRemove) { throw new IllegalStateException (); } MyLinkedList.this .remove(current.prev); expectedModCount++; okToRemove = false ; } } }