90%的程序员无法正确实现二分查找

"二分查找可以解决(预排序数组的查找)问题:只要数组中包含T(即要查找的值),那么通过不断缩小包含T的范围,最终就可以找到它。一开始,范围覆盖整个数组。将数组的中间项与T进行比较,可以排除一半元素,范围缩小一半。就这样反复比较,反复缩小范围,最终就会在数组中找到T,或者确定原以为T所在的范围实际为空。对于包含N个元素的表,整个查找过程大约要经过log(2)N次比较。
多数程序员都觉得只要理解了上面的描述,写出代码就不难了;但事实并非如此。如果你不认同这一点,最好的办法就是放下书本,自己动手写一写。试试吧。
我在贝尔实验室和IBM的时候都出过这道考题。那些专业的程序员有几个小时的时间,可以用他们选择的语言把上面的描述写出来;写出高级伪代码也可以。考试结束后,差不多所有程序员都认为自己写出了正确的程序。于是,我们花了半个钟头来看他们编写的代码经过测试用例验证的结果。几次课,一百多人的结果相差无几:90%的程序员写的程序中有bug(我并不认为没有bug的代码就正确)。
我很惊讶:在足够的时间内,只有大约10%的专业程序员可以把这个小程序写对。但写不对这个小程序的还不止这些人:高德纳在《计算机程序设计的艺术 第3卷 排序和查找》第6.2.1节的“历史与参考文献”部分指出,虽然早在1946年就有人将二分查找的方法公诸于世,但直到1962年才有人写出没有bug的二分查找程序。 "——乔恩·本特利,《编程珠玑(第1版)》第35-36页。

于是我尝试了一下,也不能说不正确,但是确实干得不够漂亮(或者说自作聪明)。

下面是我用Ruby写的:

  class Array
  # Ord a -> [a] -> a -> Maybe Int
  def ibsearch(x)
    return nil if self.empty?
    low, heigh = 0, self.length - 1
    while low < heigh
        mid = (low + heigh) / 2
        x <= self[mid]? heigh = mid : low = mid + 1
    end
    x == self[low]? low : nil
  end
end

这是测试用例:

class TestBsearch < Test::Unit::TestCase
  def test_bsearch
    count = 100
    count.times do
      xs = Array.new(SecureRandom.random_number(count))
      xs.map! { |e| SecureRandom.random_number(count) }
      xs.sort!
      x = xs.sample
      index = xs.index(x)
      assert_equal(index,xs.ibsearch(x))
    end
  end

  def test_bsearch_not_exit
    count = 100
    count.times do
      xs = Array.new(SecureRandom.random_number(count))
      xs.map! { |e| SecureRandom.random_number(count) }
      xs.sort!
      x = count + 1
      assert_equal(nil,xs.ibsearch(x))
    end
  end
end

测试都能通过,聪明的你,能看出哪里不够漂亮么?

下面是维基百科上的C/C++写的:

int binary_search(int A[], int key, int imin, int imax)
{
  // continue searching while [imin,imax] is not empty
  while (imin <= imax)
    {
      // calculate the midpoint for roughly equal partition
      int imid = midpoint(imin, imax);
      if(A[imid] == key)
        // key found at index imid
        return imid;
      // determine which subarray to search
      else if (A[imid] < key)
        // change min index to search upper subarray
        imin = imid + 1;
      else         
        // change max index to search lower subarray
        imax = imid - 1;
    }
  // key was not found
  return KEY_NOT_FOUND;
}

三点差异

抛开语法层面的不同,有三点显著的差异。

1.while循环的判断条件不同,我的没有等于。

2.等于A[mid]判断,我放在循环外面。

3.高位改变时,我没有+1。

所有这些不同都基于一个自作聪明的想法:

标准版对于[..2,2,2...]这样有相同元素的数组,返回值是随机的,可能是相同元素中任何一个元素的位置。

于是,我为了准确的定义这种情况下,决定返回相同元素的第一个。

简单讲,标准版在对半缩小目标空间的时候,只要找到相等的元素就停止搜索了,这时候目标空间>=1。而我的版本,即使找到相等的也会接着搜索下去,直到目标空间缩小到1。

乍一看,好像我的定义更精确。然而在实际应用中,这种做法其实很愚蠢。

举例说明

白名单过滤中,假设[...lyq...]中lyq前面还有一百万个元素,标准版只要找到lyq就停止了,而我的版本为了确认这个lyq是否唯一,要把剩下的一百万个元素也搜索一遍。当你确定数组中每个元素都不同时,我这种版本简直就是没事找事干。

聪明的你,也许会问,那如果有很多个lyq怎么办?二分查找对半缩小空间也很快,貌似没什么不妥。

其实不然,就算要判断是否唯一,也很简单,根本不需要改造标准版。答案就是,对找到的元素lyq,看他前一位是否也是lyq。如果是,说明不唯一。这样做根本不用破坏核心。

感悟

经典的算法,看起来好像很简单。但是在这简单的背后,有很多边边角角的细节是被隐藏了的。活学活用,结合特定的场景扩展算法才是王道,不要总想着自己设计个新算法出来,搞个大新闻。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容