2015-2016年第一学期第一周协会活动 二分搜索

二分搜索也叫折半搜索。它是一种高效的算法,应用广泛。下面通过一个例子来介绍二分搜索。

有序数列中查找某数

给出一个升序的整数数列 a0,a1,...,an1 。问小于等于某个整数 k 的最大数的位置。为了简化问题我们假设这个数组中元素是两两不同的,并且数组中最小的数小于等于 k
当然我们可以使用朴素算法。


int search(int a[], int n, int k)
{
  for (int i = 0; i < n; i++)
    if (a[i] > k)
      return i - 1;
  return n - 1; // 所有数都小于等于 k
}

这个函数返回所求位置。时间复杂度是 O(n)
为了提高效率,我们可以考虑折半查找:我们假设要求的位置是 p 。先看看数组中间的元素(am)跟 k 的大小关系。
1. 如果 am>k,那么所求的位置肯定不在 m 或者它之后。这个是因为 apk
2. 如果 amk ,那么所求位置肯定不在 m 之前。这个是因为 ap 应该是小于等于 k 的最大数,而 am 比它之前的大。
我们仔细归纳一下发生了什么。一开始我们可以肯定 p[0,n) 。然后进行了上面的判断之后,如果 1 发生了,就可以肯定 p[0,m) ;如果 2 发生了,就可以肯定 p[m,n) 。总之,通过这次判断,我们把 p 可能在的位置区间缩小了一半。
那么我们对于这剩下的一半区间自然可以故技重施,使得区间再缩减一半。一直重复下去,我们可以肯定很快就可以使得‘有嫌疑’是答案的区间只剩下一个位置。那这个位置自然就是正确结果。

int binary_search(int a[], int n, int k)
{
  // [l, r) 是有嫌疑是答案的区间
  int l = 0;
  int r = n;
  for (; (r - l) > 1; ) { // 区间有多个元素时,就要继续循环
    // 这个循环体将嫌疑区间缩减至一半
    int m = (l + r) / 2; // 区间的中间位置
    if (a[m] > k)
      r = m; // 区间变为 [l, m)
    else l = m; // 区间变为 [m, r)
  }
  // 现在区间长度是 1,[l, r) 中只有一个位置 l
  return l;
}

容易发现这个算法的复杂度是 O(logn),是比朴素算法更优的。

二分搜索的本质

二分搜索是由上面这个问题引发的算法,但是勿思维僵化,认为二分搜索只是在一列数中查找数的算法。
这个问题的实质有两部分:
1. 答案可以确定在某个区间中
2. 可以通过某种判断将上述的区间缩减成一半。
在引入部分的题目里面,第二步是对中间的数与 k 比较。二分搜索的关键就是确定判断,这个良好的判断排除了一半的答案。
下面通过另一道题目介绍二分搜索更广泛的用法。

最小化最大值

将一个正整数序列 a1,a2,...,an 划分成 m(mn) 个子序列,并设 si 是第 i 段的和。对于使得 M(partition)=max(s1,s2,...,sn) 最小的划分方法,求 M
我们设命题 C(x): 可以划分原来的序列,使得 Mx
我们考虑如果 C(x) 真,也就是说, M 还有点可能是更小的,比 x 大的答案不够优秀。
反之,也就是 M>x ,说明 x 太小了,答案一定比这个大。
我们首先可以估计一个答案所在的范围,不难发现答案肯定比 0 大,不大于所有数的和小。如果我们可以判断 C(x),那么我们就解决了这个问题。
要解决 C(x) ,应该认识到, x 越大,越难划分成较多的份数,同时,如果划分出了少于 m 的份数,自然可以通过再划分其中某些段使得出现划分成 m 段的划分法。所以 C(x) 其实等价于:每段最大值不超过 x 的情况下,可以划分的最少段数小于等于 m
上述的最小段数其实不难解决。我只要从数列头开始划分,直到这一段不能再多(再多的话和就会超过 x ),则划分出一段;在剩下的部分继续这样划分,就可以求出最小段数。
算法的总复杂度是 O(nlog(a1+a2+...+an))
这个问题是二分搜索的经典题目。下面这道题跟我的描述几乎一样,请同学们通过这道题。
http://poj.org/problem?id=3273
下面给出另一个经典例题供大家思考,下次课的开始我们会讲这个题目。

最大化性价比

现有 n 种商品,每种商品有其价格 Pi 和 价值 Vi (都用正整数表达)。现在你要选择 k(kn) 个商品,使得性价比最高。
注:设选择的商品价格是 P1,P2,...,Pk ,价值分别是 V1,V2,...,Vk 。性价比指的是 ViPi
另外,由于性价比不见得是整数,本题只需要你输出保留小数点后三位输出最高性价比。

本文作者

张静之

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值