查找算法
1 二分查找
二分查找一种非常高效的查找算法。它的时间复杂度为 O(logn)。n 表示数据规模大小。有时甚至比O(1)效率更高。因为O(1)表示是一个常量值,当数量大道一定程度,O(logn)反而表现更好。
2 为什么要讲二分查找?
当数据量比较大的时候,相比循环遍历查找,二分查找的时间复杂度为O(logn),这就是优势。
当数据量比较小时,如10个,那跟顺序查找没什么区别了。但是,如果数据间的比较很耗时的时候(如:字符串比对),那么还是推荐二分查找,因为它能减少比较次数。
当数据量比较大的时候,因为二分查找依赖的是连续的数组结构来存储数据,如果超过1G或者2G的内存,那么大概率不会分配到连续的大块内存。
3 实现原理
分治思想,折半查找。
3.1 循环方式
public boolean binarySearch(int[] aar, int n, int searchValue) {
int low = 0;
int high = n - 1;
while (low <= high) {
// int mid = low + (high - low) >> 1; 错误写法
int mid = low + ((high - low) >> 1);
int midValue = aar[mid];
Log.d("binarySearch, mid:", "= " + mid+", value: "+midValue);
if (midValue == searchValue) {
return true;
}
if (searchValue < midValue) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return false;
}
int[] aa = {1, 2, 4, 5, 7, 8, 10, 33, 43, 55};
boolean b = binarySearch(aa, aa.length, 8);
3.2 递归方式
public boolean binarySearch2(int[] aar, int low, int high, int searchValue) {
//注意递归的退出条件
if (low > high) {
return false;
}
int mid = low + ((high - low) >> 1);
int midValue = aar[mid];
if (midValue == searchValue) {
return true;
}
if (searchValue < midValue) {
return binarySearch2(aar, low, mid - 1, searchValue);
} else {
return binarySearch2(aar, mid + 1, high, searchValue);
}
}
4 时间复杂度
O(logn)
5 二分查找使用场景
由于它依赖的 连续有序 且 不重复 的数组,所以应用场景有一定的局限。
数据规模不能太小,太小没有优势。
也不能太大,太大无法申请连续内存。
6 二分查找的变形
在有序重复的数组中:
6.1 查找第一个等于给定值
6.1 查找最后一个等于给定值的元素
6.1 查找第一个等于给定值元素
6.1 查找第一个大于等于给定值的元素
6.1 查找最后一个小于等于给定值的元素
跳表(skip list)
如何在有序的单链表实现时间复杂度为O(logn)的查找方法?
方案:
给链表加索引层,即跳表。 每个几个节点则提取节点到索引层,索引节点指针down指向原链表的节点。 可以建立多个索引层,从而加快查找速度。
跳表浪费内存吗?
由于一般情况不会真正去存储某个对象实例,而是存储对象的引用。因此,不存在浪费很大内存。
索引动态更新
当插入大量数据的时候,如果在某两个索引节点插入的,那么该跳表就会退化为单链表。 因此,在插入的时候需要在当前锁定层数间产生一个随机数,在插入到链表的同时,也要在随机数索引层插入该数据。