文章目录
Collections工具类二分查找
- ArrayList 类签名
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- LinkedList 类签名
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- Collections 对 RandomAccess 进行继续下标的二分查找
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
RandomAccess 接口
空接口
ArrayList 二分查找
内在原因:
ArrayList.get(index)
时间复杂度:O(1)
LinkedList.get(index)
时间复杂度:O(n)
。后文将二分查找时如何优化这个O(n)
的复杂度
演示:
ArrayList
寻找value:16
的元素
- 根据数组长度
mid
确认为 4,get(4) == 11 < 16 - 根据二分算法
mid
确认为 6,get(6) == 16
value | 1 | 5 | 7 | 8 | 11 | 15 | 16 | 28 | 39 |
---|---|---|---|---|---|---|---|---|---|
index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
相关代码:
private static <T> int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
int low = 0;
int high = list.size()-1;
while (low <= high) {
int mid = (low + high) >>> 1;
Comparable<? super T> midVal = list.get(mid);
int cmp = midVal.compareTo(key);
if (cmp < 0)
low = mid + 1;
else if (cmp > 0)
high = mid - 1;
else
return mid; // key found
}
return -(low + 1); // key not found
}
LinkedList 二分查找
value | 1 | 5 | 7 | 8 | 11 | 15 | 16 | 28 | 39 |
---|---|---|---|---|---|---|---|---|---|
迭代器缓存的下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
LinkedList get操作的效率问题
链表获取指定下标的元素,需要从从头遍历
ListIterator 解决问题
LinkedList
的get方法的效率较低,所以Collections
在二分查找的场景使用了 ListIterator
。
ListIterator
可以向前遍历,也可以向后遍历
private static <T> T get(ListIterator<? extends T> i, int index) {
T obj = null; // 待返回的value
int pos = i.nextIndex(); // 向后移动游标获取当前位置
if (pos <= index) { // 当前位置小于index
do {
obj = i.next();
} while (pos++ < index); // 一直向后找,直到到达index的位置,返回index对应的value
} else {
do {
obj = i.previous(); // 反之向前找
} while (--pos > index);
}
return obj;
}
结论
RandomAccess
接口只参与标记作用,目的是让ArrayList
发挥其底层数据结构数组的O(1)
查找能力
根据上文,说明了以下代码是个很低效的代码
LinkedList linkedList = new LinkedList();
for(int i = 0; i < linkedList.size(); i++) {
System.out.println(linkedList.get(i);
}
写成高效的,就是foreach循环的本质。
LinkedList linkedList = new LinkedList();
ListIterator listIterator = linkedList.listIterator(); // 只往后遍历可以写成iterator()
while (listIterator.hasNext()) {
listIterator.next();
}