二分查找(上):如何用最省内存的方式实现快速查找功能

本文是学习算法的笔记,《数据结构与算法之美》,极客时间的课程

今天先提一个问题:如何设计数据结构和算法,快速判断某个整数是否出现在这100万数据中?带着这个问题,咱们来学习下二分查找(Binary Search)。

无处不在的二分思想

二分查找是一种非常简单易懂的查找算法,生活中到处可见。比如说,我们现在来做一个猜字游戏。我随机写一个0~99之间的数字,然后你来猜我写的是什么。你每猜一次,我就会告诉你猜的大了还是猜的小了,直到猜中为止。假设我写的数字是23,你可以按下面的步骤试试。在这里插入图片描述
7次就猜出来了。按照这个思想,即便我让你猜的是0~999的,最多只要10次就可以猜中,不信你可以试试。

说一个实际开发的场景,假设有1000条订单数据,已经按照订单金额从小到大排序,每个订单金额都不同,并且最小单位是元。我们现在想知道是否存在等于19元的订单。如果存在,返回订单数据,如果不存在,则返回null。

最简单的办法当然是从第一个订单开始,一个一个遍历所有的订单,直到找到金额等于19元的订单为止。但这样会比较慢,最坏的下,可能要遍历完这1000条记录才能找到。那用二分查找能不能快速地解决呢?

我们简化下模型,假设只有10个订单,订单金额分别是:8,11,19,23,27,33,45,55,67,98。用图来表示,其中low 和 high 表示查找区间的下标,mid 表示待查找区间的中间元素下标。在这里插入图片描述
看懂这个例子,你现在对二分的思想应该掌握的妥妥的。二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为0。

O(logn) 惊人的查找速度

二分查找是一种非常高效的查找算法。我们假设数据大小为n ,每次查找后数据都会缩小为原来的一半,最坏情况下,直到查找区间缩小为空,才停止。在这里插入图片描述
如上图,经过 k 次缩小操作,时间复杂度就是O(k)。n/2^k = 1,可得时间复杂度是O(logn)。

二分查找是我们目前遇到的第一个时间复杂度是O(logn)的算法。后面章节我们还会讲堆、二叉树的操作等等,它们的时间复杂度也是O(logn)。这种时间复杂度,,有的时候甚至比时间复杂度是常量级的O(1)的算法还要高效。为什么这么说呢?

logn是一个非常“恐怖”的数量级,即便是n 非常非常大,对应的logn 也很小。比如从42亿个数据中用二分查找一个数据,最多需要32次而已。

二分查找的递归与非递归实现

实际上,简单的二分查找并不难写,注意这里的“简单”二字。下一节,会讲到二分查找的变体问题,那才是真正的烧脑。下面是java实现的一个最简单的二分查找

最简单的情况,就是有序数组中不存在重复元素,我们在其中用二分查找等于给定值的。

	private int binarySearch(int[] a, int value) {
		int n = a.length;
		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;
	}

二分查找就用场景的局限性

1、二分查找依赖的是顺序表结构,简单点说就是数组
那二分查找能否依赖其他数据结构呢?比如链表。答案是不可以的,主要原因是二分查找算法需要按照下标随机访问元素。我们前面讲数组和链表时说过,数组按照下标随机访问的时间复杂度是O(1),而链表随机访问的时间复杂度是O(n)。所以,如果数据使用链表存储,二分查找的时间复杂度会变得很高。

2、二分查找针对的是有序的数据
二分查找针对的数据,必须是有序的。如果数据没有序,我们需要先排序。我们针对的是一组静态数据,没有频繁的插入删除,我们可以进行一次排序,多次二分查找。这样排序的成本可被分摊,二分查找的边际成本就会比较低。

但如果数据集合有频繁的插入删除操作,要想用二分查找,要么每次插入、删除操作之后保证数据仍然有序,要么每次二分查找之前进行排序操作。针对这种动态的数据集合,无论哪种方法,维护有序的成本都是很高的。如何在动态数据集合中查找某个数据?等到了二叉树那环节再细讲。

3、数据量太小,不适合二分查找。
如果要处理的数据量很小,完全没有必要二分查找,遍历就足够了。比如在一个大小为10的数组中查找一个元素,顺序遍历也不显得慢。

4、数据量太大也不适合二分查找。
二分查找的底层需要依赖数组这种数据结构,而数组为了支持随机访问的特性,要求内存空间连续,对内存的要求比较苛刻,比如,我们有1GB 大小的数据,如果希望用数组来在你,那就需要1GB 连续内存空间。太大的数据用数组来存储,对内存来说比较吃力,也就不能用二分查找了。

解答开篇

对于开篇的问题:如何在1000万个整数中快速查找某个整数。

这个问题并不难。我们的内存限制是100MB,每个数据大小是8字节,最简单的办法就是将数据存储在数组中,内存占用差不多是80MB。结合今天讲的内容,可以先对这1000万数据从小到大排序,然后利用二分查找算法,就可以快速地查找想要的数据了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值