思考题:假设有 1000 万个整数数据,每个数据占 8 个字节,如何设计数据结构和算法,快速判断某个整数是否出现在这 1000 万数据中?希望不要占用太多的内存空间,最多不要超过 100MB
二分思想
查找算法 | 特点 |
二分查找 | 1、二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。 2、时间复杂度O(logn)的 查找速度 |
对数时间复杂度在大数据量级的情况下有时候比常量级别查找速度O(1)还要高效
二分查找的实现
假设数组中不存在重复的元素:
public int bsearch(int[] a, int n, int value) {int low = 0;int high = n - 1;while (low <= high) {int mid = (low + high) / 2;if (a[mid] == value) {return mid;} else if (a[mid] < value) {low = mid + 1;} else {high = mid - 1;}}return -1;
}
- 注意循环退出条件
- 注意mid的取值:mid=(low+high)/2 这种写法是有问题,如果high和low都比较大容易造成内存溢出。改进的写法是:mid 的计算方式写成 low+(high-low)/2;更进一步提升性能的写法:可以将这里的除以 2 操作转化成位运算 low+((high-low)>>1)
- low和high的更新:low=mid+1,high=mid-1
二分查找的应用局限性
- 二分查找依赖的是顺序表结构,简单点说就是数组。二分查找只能用在数据是通过顺序表来存储的数据结构上。如果你的数据是通过其他数据结构存储的,则无法应用二分查找
- 二分查找针对有序数据:如果数据没有序,我们需要先排序。排序的时间复杂度最低是 O(nlogn)。因此对于插入、删除操作不频繁,一次排序、多次查找的场景,比较适合二分查找。
- 针对动态变化的数据集合二分查找不再适合。
- 数据量太大不适合二分查找:二分查找依赖数组结构,数组为了随机访问特性,对内存空间的连续性要求较高,如果剩余空间无法满足,那么无法使用二分查找
查找算法 | 局限性 |
二分查找 | 1、依赖数组,如果数据存在链表上,不能使用 2、依赖数组,对内存空间有要求,如果剩余连续空间过小,数据量大,也不能用二分查找 3、动态变化的数据不适合用二分查找(还是因为数据结构的特点对插入、删除操作不友好,维护成本太高) 4、对于插入、删除操作较少(或者是历史静态数据),一次排序多次查找适合二分查找 |
回答开篇
内存限制是 100MB,每个数据大小是 8 字节,最简单的办法就是将数据存储在数组中,内存占用差不多是 80MB,符合内存的限制。借助今天讲的内容,我们可以先对这 1000 万数据从小到大排序,然后再利用二分查找算法,就可以快速地查找想要的数据了。
用散列表和二叉树是无法解决的原因:
- 散列表、二叉树这些支持快速查找的动态数据结构。用散列表和二叉树实际上是不行的。大部分情况下用二分查找可以解决的问题,用散列表、二叉树都可以解决。但是,不管是散列表还是二叉树,需要比较多的额外内存空间。
- 如果用散列表或者二叉树来存储这 1000 万的数据,用 100MB 的内存肯定是存不下的。而二分查找底层依赖的是数组,除了数据本身之外,不需要额外存储其他信息,是最省内存空间的存储方式,所以刚好能在限定的内存大小下解决这个问题