看到一篇很有意思的文章就转过来了。。。
原文:
首先引用一下《编程珠玑》中的两句话:
- 尽管给了那么充裕的时间,只有大约10%的专业程序员能够写出正确的二分查找。
- 尽管第一个二分查找程序于1946年就公布了,但是第一个没有bug的二分查找程序在1962年才出现。
当时看到这的时候,我觉得有点夸张。这里不去讨论是否真的花了20年才人们才写出正确的代码,但这两句话至少告诉我们,不要小看二分搜索。
确实,写过bsearch的人都知道,迭代的出口很难在短时间内想清楚(反正我经常糊涂)。当然,常规的二分搜索大家现在基本都能写了,(写过这么多次,背也背下来了),但能写出来不代表真正理解了二分搜索。比如我今天在做一道题的时候就卡在了这里:从一个有序数组中找到大于x且最接近x的数。
说白了就是二分搜索,但是迭代的出口是什么?
先来看一下常规的二分查找的代码:
-
-
-
-
-
-
-
-
-
-
-
- int bsearch(int *a, int n, int x)
- {
- int m;
- int l = 0, r = n -1;
-
- while( r >= l )
- {
- m = (l+r)/2;
- if(a[m] == x)
- return m;
- else if(a[m] < x)
- l = m+1;
- else
- r = m-1;
- }
- return -1;
- }
考虑一下要查找的元素不在序列中的情况:函数会返回-1,那l和r分别指向什么呢?
可以证明:l指向大于x的第一个元素,r指向小于x的第一个元素。(为什么?迭代的最后一步必然是l和r指向同一元素,然后为什么会变得比r大呢?想想就明白了)
既然得出了这个结论,那么代码就好写了,但是还得注意一些边界条件,比如最后返回的时候检查l和r是否越界,如果越界就说明找不到。
代码如下,如果有错,欢迎讨论:
- #include <iostream>
- using namespace std;
-
-
-
-
-
-
-
-
-
-
-
-
-
- int bsearch(int *a, int n, int x)
- {
- int m;
- int l = 0, r = n -1;
-
- while( r >= l )
- {
- m = (l+r)/2;
- if(a[m] == x)
- return m;
- else if(a[m] < x)
- l = m+1;
- else
- r = m-1;
- }
- return -1;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- int bsearch_more(int *a,int n, int x)
- {
- int m;
- int l = 0, r = n -1;
-
- while( r >= l )
- {
- m = (l+r)/2;
-
- if(a[m] == x)
- return ( (m+1) >= n ? -1 : (m+1) );
- else if(a[m] < x)
- l = m+1;
- else
- r = m-1;
- }
- l = (l >= n ? -1 : l);
- return l;
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- int bsearch_less(int *a,int n, int x)
- {
- int m;
- int l = 0, r = n -1;
-
- while( r >= l )
- {
- m = (l+r)/2;
- if(a[m] == x)
- return ( (m-1) < 0 ? -1 : (m-1) );
- else if(a[m] < x)
- l = m+1;
- else
- r = m-1;
- }
- return r;
- }
-
- int main(int argc, char *argv[])
- {
- int x;
- int arr[] = {8, 17, 26, 32, 40, 72, 87, 99};
- while(cin >> x)
- cout << bsearch_less(arr,8,x)+1
- << " "
- << bsearch_more(arr,8,x)+1<< endl;
- }