第二次博客,希望能有第三次。这次主要聊聊表、列表、List(同一种事物)。
抽象数据类型 ADT
抽象数据类型(abstract data type,ADT),是带有一组操作的一些对象的集合。说人话:好几个相同类型的对象凑在一起,可以对这些对象进行一些操作,这样形成的数据结构就是抽象数据类型。一堆的Integer在一起,无论是按列表排开,还是键值对形式存储形成一个集合,在这个集合上可以进行一些添加、删除、查找等的一些操作。这样的组织方式是ADT。
常见ADT有表、栈、队列、Map、图、树等。常见操作有添加、删除、查找、合并、包含。
列表
概念:
A0
,
A1
,
A2
……
An−1
这样的形式称为表,表的大小是n。
空表:列表中没有一个元素是空表。
前驱后继:
A0
是
A1
的前驱,
A1
是
A0
的后继。
位置:
Ai−1
在列表的第i位。
实现方式
列表实现方式有数组和链表,对应Java中的ArrayList和LinkedList。一般使用双链表实现表。
基本操作
Java的Collection是ADT的基类,包含的操作有。
public interface Collection<E> extends Iterable<E> {
int size();
boolean isEmpty();
void clear();
void add(AnyType x);
boolean contains(AnyType x);
boolean remove(AnyType x);
Iterator<AnyType> iterator();
}
Iterator接口
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Iterator接口每次对next的调用都给出集合的下一项。第1次调用next,得到第1项 A0 ,第2次调用next,得到第2项 A1 ……. hasNext 返回是否存在下一项。
当编译器见到一个正在用于Iterable对象增强for循环的时候,它调用iterator方法得到一个Iterator对象,然后调用hasNext和next。
对代码:
public static <T> void print(Collection<T> coll){
for(T item : coll){
System.out.println(item);
}
}
编译器改写为:
public static <T> void print(Collection<T> coll){
Iterator<T> iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
Iterator的remove
Collection有remove,Iterator也有remove。Iterator的remove方法有两个优点。
1 Collection的remove需要先找到元素位置,再删除;Iterator的remove是在一边遍历过程中一边删除的。效率更高。
2 当正在迭代一个Iterator的时候,不能改变集合的结构(add,remove,clear),否则会报错ConcurrentModificationException。使用Iterator自己的remove方法则不会报错。
表的所有操作
int size();
boolean isEmpty();
boolean contains(Object o);
boolean add(E e);
void add(int index, E element);
boolean remove(Object o);
E remove(int index);
void clear();
E get(int index);
E set(int index, E element);
Iterator<E> iterator();
ListIterator<E> listIterator();
数组与链表实现分析
场景 | 数组 | 链表 | 备注 |
---|---|---|---|
set | O(1) | 麻烦 | |
get | O(1) | 麻烦 | |
add(int index, E element) | 麻烦 | O(1) | |
在末端添加数据 | O(1) | O(1) | |
remove | 麻烦 | O(1) | 在表的两端操作比较方便 |
求和场景
public static int sum(List<Integer> lst){
int total = 0;
int n = lst.size();
for(int i=0;i<n;i++){
total += lst.get(i);
}
return total;
}
ArrayList (数组)运行时间O(n)。LinkedList(链表)运行时间是O( n2 ),因为每次get操作为O(n)。如果代码改为迭代器,计算时间都是O(n)。这个概念是我之前没有的,看到这里,想起自己以前写的代码,一身冷汗。
public static int sum(List<Integer> lst){
int total = 0;
for(Integer val : lst){
total += val;
}
return total;
}
Iterator remove方法的大用处
例题:删除列表中的偶数。例如 nums={2,3,4,5,6,7},返回nums={3,5,7}。
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++;
}
}
从上表分析得知,ArrayList时间复杂度O( n2 ),remove比较耗时。LinkedList时间复杂度O( n3 ),get操作麻烦,当remove的时候,到达位置i也低效。
public static void removeEvensVer2(List<Integer> lst){
Iterator<Integer> itr = lst.iterator();
while(itr.hasNext()){
if(itr.next()%2==0)
itr.remove();
}
}
使用迭代器的remove方法直接删除当前项,程序时间复杂度O(n)。