浅谈二分搜索

来源:互联网 发布:美工刀片厚度 编辑:程序博客网 时间:2024/06/05 02:28

二分搜索算法在搜索以及ACM中是很常用的。二分搜索算法是分治算法的一个典型的应用。但是二分搜索算法的正确性确实我们必须注意的一个问题。下面就介绍下二分搜索算法:

二分搜索算法的主要思想:对于一个已排序的元素序列,我们每次选取包含问题解的区间,然后求解其区间的中点,然后将中点元素的值与对应的键值进行比较,找出其对应的包含问题解的子区间。这样逐步将包含问题解的区间缩小,直至找到问题的解。

例如:从一堆一排序的元素1,3,5 ,7 ,8,9中查找元素7,如果存在则返回其下标,否则返回-1。由于已排序,我们先找出区间中间元素的下标,为1 + (6-1)/2 = 3,故而中间元素为5,而5小于7,区间[1 ,3]内的元素都小于5,故而问题转化为从区间[4,6]中找元素。这样再次找中间位置的元素为8,区间[5,6]中比不包含键值7,故而包含问题解的区间变为[4,4],即4号位置元素,对比发现相同,然后返回下标4。

源代码1: 

int binary_search(int array[],int key){

   int begin = 0;

   int end = array.size()-1;

   while(begin < end)

   {

       int mid = begin + (end-begin)/2;

       if(array[mid] < key)

           begin = mid+1;

       else if(array[mid] > key)

           end = mid-1;

       else return mid;

   }

    return -1;

}

这段代码是在网上看到的,初看时,感觉没问题,不过细细一看,就有问题了。这里可以是一组这样的数据:1 ,3 ,5 ,7, 9 ,然后查找元素7。这里结果会得到-1,但是7确实在这组元素中。不知读者有没发现,这段代码有漏判元素,其原因就在于:begin = mid +1 和end = mid -1 这两句上,虽然判断条件没错,但是这两句会使得begin和end的很容易漏判,上述情况就是这样,当循环到begin==end的时候,就停止循环,而程序却没判断begin==end的情况,这里就导致错误了。我们可以在return -1 之前加一句判断,这样就没错了。我写一个代码:

int b_search(int * A , int x , int y , int key)
{
 int v ;
 while(x < y)
  {
        v = x + (y-x) /2 ;    
     if(A[v] < key)
            y = v - 1 ;
     else if(A[v]>key)
       x = v + 1 ;
     else 
            return v ;
   }
   if(A[x] == key)
      return x ;
   return -1 ;
}

不过也可以换种方式写,下面的代码是刘汝佳那本白书上的代码:

int bsearch(int * A , int x , int y , int v)
{
  int m ;
  while(x < y)
  {
     m = x + (y - x)/2 ;
     if(A[m] == v)    
         return m ;
       
     else if(A[m]>v)
           y = m ;    
     else 
            x = m + 1 ;      
    }
     return -1 ;
}

  在很多情况下,我们不仅要找出某个元素是不是存在,我们还得找出这个元素的区间,及找出下界,与上界。我们让然可以利用二分的思想来解决这些问题。这里我们先看代码:

int lower_case(int * A , int x , int y , int key)

{

    int v ;

    while(x < y)

    {

        v = x + (y - x) /2 ;  

        if(A[v] > key)

            y = v - 1 ;

        else if(A[v] < key)

            x = v + 1 ;

        else

            y = v ;     

    }

    if(A[x]==key)

        return x ;

    return -1 ;

}

分析如下:如果元素A[v] > key 说明区间[v , y]中一定没有与key相等的元素。所以此时区间变为[x ,v-1];如果元素A{v] < key 说明区间[x , v]中一定没有与key相等的元素,所以区间变为[v+1 , y];当A[v] = key的时候说明已经存在key元素,但是其左侧仍然可能存在相同的key值,所以区间变成[x,v]。到这里还没结束,我们仍然要继续验证这个正确性,假若区间[x , v-1]或者[x,v]或者[v+1 , y]与区间[x,y]重复就会出现死循环,但是我们可以利用v的值来验证区间不会重复。当然在最后还要多判断一次,如果最后循环结果得到的A{x]的值与key不相同,则说明不存在key元素。这里我将刘汝佳白书上的代码提供在这里,虽有不同但是本质都是一样的:

int lower_bound(int *A , int x , int y, int key)

{

    int m ;

    while(x < y)

    {

        m = x + (y-x)/2 ;    

        if(A[m]>= key)

            y = m ;  

        else

            x = m + 1 ; 

    } 

    return x ;

}
  再看找区间的上界。这里同样,我直接将代码贴在这里,原理跟上面的差不多,

int upper_case(int * A , int x  , int y , int key)

{

    int v ;  

    while(x < y)

    {

        v = x + (y - x + 1)/2 ;//这里需要注意

        if(A[v] > key)

            y = v - 1 ;

        else if(A[v] < key)

            x = v + 1 ;

        else 

            x = v ;     

    }

    if(A[x]==key)

        return x ;

    return -1 ;

}

刘汝佳白书上的代码:这里注意,程序求出来的结果是元素区间的上界+1 。

int upper_bound(int * A , int x , int y , int key)

{

    int m ;

    while(x < y)

    {

        m = x + (y-x)/2 ;

        if(A[m] > key)

            y = m ;

        else

            x = m + 1 ; 

    }

    return x ;

}

上述算法其实在STL库中都有实现,直接调用函数lower_bound()和upper_bound(),可以得到对应的上下界。头文件:#include<algorithm>。在使用二分搜索的时候我们必须注意先对元素进行排序,然后在使用二分搜索技术。利用二分搜索的思想我们还可以解决很多问题。比如高次方程的根的逼近求解,以及求单调函数的解等等,这些问题我们可以结合数学知识,利用函数的单调性,求解问题的解。这里有一个关于求解方程解的问题的一个大致模板,用于解决浮点数问题:

double bsearch()

{

    double x = min ;

    double y = max ; 

    double v ; 

    while(fabs(x - y) < 1e-9 )

    {

        v = (y - x)/2 + x ;

        if(f(v) > 0)

            x = v ;

        else

            y = v ; 

    } 

    return v ;

}

    在写利用二分搜索思想的程序时,要注意以下几点:1 , 当前区间一定包含问题的解或者说是目标元素。2,每次待查询的元素区间不断的缩小。有一篇关于二分查找的不错的文章,可以看一下(不过,可能有些许错误,需要读者自己辨别):

http://duanple.blog.163.com/blog/static/709717672009049528185/