3.2 表 ADT -3.3 Java Collection API 中的表

时间:2022-06-07 07:40:06

3.2 表 ADT

处理形如 A0, A1, A2, ……, AN-1 的一般的表。我们称这个表大小为N。将大小为0的特殊表称为空表

对于除空表以外的任何表,称 Ai-1 前驱 Ai,Ai 后继 Ai-1

表ADT上进行操作有:

printList 打印整个表

makeEmpty 清空整个表

find 返回某一项首次出现的位置

insert 从表的某个位置插入元素

remove 从表的某个位置删除元素

findKth 返回某个位置上的元素

数组实现表

对表的操作都可以通过数组来实现。虽然数组由固定容量创建,但是需要的时候可以使用双倍的容量再创建一个数组。

在这种实现下,printList花费时间为 O(N),findKth 花费时间是常数。但是插入和删除也会花费O(N)的时间,因为在 0 位置插入或删除,会将数组元素整体后移或前移。平均下来都需要移动表的一半的元素。

表的另一种实现方式:链表

链表由一系列节点组成,每个节点均含有表元素和到包含该元素后继的节点的链。链只有后继元素的信息,并没有前驱节点的信息。

和数组实现一样,printList 花费时间为 O(N),findKth(i) 花费时间 O(i)。remove 方法可以通过修改一个 next 引用来实现,insert 则需要改变两个next 引用。

实际中更为常用的是双链表。

3.3 Java Collections API 中的表

Collection接口
	public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
void clear();
boolean contains(Object o);
boolean add(E e);
boolean remove(E e);
Iterator<E> iterator();
}

Collection 实现了 Iterable 接口,因此可以对其使用增强 for 循环

    public static <E> void enhanceFor(Collection<E> coll) {
for (E e : coll) {
//对每一个元素操作
}
}
Iterator接口
	public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}

通过 iterato r方法,每个集合都可以创建并返回一个实现 Iterator 的对象

next 方法返回下一项,hasNext 方法返回是否存在下一项。编译器遇见一个用于 Iterable 对象的增强 for 循环时,它用 iterator 方法的方法来替代增强 for 循环。如下

	public static <E> void iter(Collection<E> coll) {
Iterator<E> iter = coll.iterator();
while (iter.hasNext()) {
E item = iter.next();
//对每一个元素操作
}
}

remove 方法将删除由 next 最新返回的元素。Collection 的 remove 方法必须先找出要被删除的项。而 Iterator 的 remove 方法要删除的就是当前迭代器所在位置的元素。

当直接使用 Iterator 而不是通过一个增强 for 循环间接使用时,如果对正在被迭代的集合进行结构上的改变( 即对该集合使用 add 、remove 或 clear 方法), 那么迭代器就不再合法。

只有在需要立即使用一个迭代器的时候,才应该获取迭代器。如果迭代器调用了自己的 remove 方法,那么这个迭代器依然合法,且自己的 remove 方法也只应该被调用一次。

List 接口、ArrayList 类和 LinkedList 类

List 接口继承了 Collection 接口

	public interface List<E> extends Collection<E> {
E get(int index);
E set(int index, E element);
void add(int index, E element);
E remove(int index);
}

listIterator 方法将产生比通常认为还要复杂的迭代器。

ArrayList 类提供了 List ADT 的一种可增长数组的实现,对 get 和 set 的调用花费常数时间。缺点是新项插入和现有项删除代价高。LinkedList 类提供的是 List ADT 的双链表实现,优点是新项的插入和删除都是常数时间的操作,这里假设变动项的位置是已知的。LinkedList 对 get 的调用代价高。

不论 ArrayList 或是 LinkedList 作为参数被传递,makeList1 的运行时间都是 O(N) ,因为每次对add调用都是在表的末端。从而均花费常数时间(忽略ArrayList扩容的时间)

    public static void makeList1(List<Integer> lst, int N) {
lst.clear();
for (int i = 0; i < N; i++) {
lst.add(i);
}
}

对于 LinkedList ,makeList2 的运行时间还是 O(N),而对于 ArrayList 运行时间是 O(N2),因为在 ArrayList 中在前端添加是一个 O(N) 操作。

    public static void makeList2(List<Integer> lst, int N) {
lst.clear();
for (int i = 0; i < N; i++) {
lst.add(0,i);
}
}
ListIterator 类

扩展了 List 的 Iterator 的功能。方法 previous 和 hasPrevious 能够使表从后向前遍历

	public interface ListIterator<E> extends Iterator<E> {
boolean hasPrevious();
E previous();
}

add 方法将一个新的项以当前位置放入表中,set 方法改变被迭代器看到的最后一个值,对于 LinkedList 来说很方便