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的元素

  1. 根据数组长度 mid 确认为 4,get(4) == 11 < 16
  2. 根据二分算法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();
        }
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐