有一个面试题是对一个1000万的数字进行快速查找,并且使用内存不能查过100M
答: 现在有1000个数字 每个数子大小为8Kb(为long基本类型) 那么现在占据用的内存 为800M 我们进行算法设计 ,将这个数据进行有序排列,组成为一个数组, 进而进行 折中查找,每一次在查找的时候取中间位置的数据,如下图(图片来源极客时间):
// 二分查找的递归实现
public int bsearch(int[] a, int n, int val) {
return bsearchInternally(a, 0, n - 1, val);
}
private int bsearchInternally(int[] a, int low, int high, int value) {
if (low > high) return -1;
int mid = low + ((high - low) >> 1);
if (a[mid] == value) {
return mid;
} else if (a[mid] < value) {
return bsearchInternally(a, mid+1, high, value);
} else {
return bsearchInternally(a, low, mid-1, value);
}
}
上面这种思想也就是二分法思想,但是还有就是二分法的这种思想的使用场景的局限性还是比较多的
- 二分法依赖的是顺序表结构(数组)
- 那二分查找能否依赖其他数据结构呢?比如链表。答案是不可以的,主要原因是二分查找算法需要按照下标随机访问元素。我们在数组和链表那两节讲过,数组按照下标随机访问数据的时间复杂度是 O(1),而链表随机访问的时间复杂度是 O(n)。所以,如果数据使用链表存储,二分查找的时间复杂就会变得很高
- 二分法针对的是有序数组
- 二分查找对这一点的要求比较苛刻,数据必须是有序的。如果数据没有序,我们需要先排序。前面章节里我们讲到,排序的时间复杂度最低是 O(nlogn)。所以,如果我们针对的是一组静态的数据,没有频繁地插入、删除,我们可以进行一次排序,多次二分查找。这样排序的成本可被均摊,二分查找的边际成本就会比较低。但是,如果我们的数据集合有频繁的插入和删除操作,要想用二分查找,要么每次插入、删除操作之后保证数据仍然有序,要么在每次二分查找之前都先进行排序。针对这种动态数据集合,无论哪种方法,维护有序的成本都是很高的。所以,二分查找只能用在插入、删除操作不频繁,一次排序多次查找的场景中。针对动态变化的数据集合,二分查找将不再适用。那针对动态数据集合
- 数据量太小也是不适合二分法的
- 如果要处理的数据量很小,完全没有必要用二分查找,顺序遍历就足够了。比如我们在一个大小为 10 的数组中查找一个元素,不管用二分查找还是顺序遍历,查找速度都差不多。只有数据量比较大的时候,二分查找的优势才会比较明显。不过,这里有一个例外。
- 如果数据之间的比较操作非常耗时,不管数据量大小,我都推荐使用二分查找。比如,数组中存储的都是长度超过 300 的字符串,如此长的两个字符串之间比对大小,就会非常耗时。我们需要尽可能地减少比较次数,而比较次数的减少会大大提高性能,这个时候二分查找就比顺序遍历更有优势。
- 数据量太大也不适合使用二分法,
- 准确来说数据量太大不适合使用数组存储数据,比如说1GB 的数据,使用数组存储的话,这1GB数据必须是连续的存储,比如你现在剩余内存为1GB 但是这个1GB数据 不一定能存储这1GB的数组 而能存储的下1GB的链表。