zoukankan      html  css  js  c++  java
  • 二分查找算法基本思想

    1.  

    转载http://www.cppblog.com/converse/archive/2009/10/05/97905.html

    二分查找算法基本思想
    二分查找算法的前置条件是,一个已经排序好的序列(在本篇文章中为了说明问题的方便,假设这个序列是升序排列的),这样在查找所要查找的元素时,首先与序列中间的元素进行比较,如果大于这个元素,就在当前序列的后半部分继续查找,如果小于这个元素,就在当前序列的前半部分继续查找,直到找到相同的元素,或者所查找的序列范围为空为止.

    用伪代码来表示, 二分查找算法大致是这个样子的:

    1 left = 0, right = n -1
    2 while (left <= right)
    3     mid = (left + right) / 2
    4     case
    5         x[mid] < t:    left = mid + 1;
    6         x[mid] = t:    p = mid; break;
    7         x[mid] > t:    right = mid -1;
    8 
    9 return -1;
    View Code



    第一个正确的程序
    根据前面给出的算法思想和伪代码, 我们给出第一个正确的程序,但是,它还有一些小的问题,后面会讲到

    int search(int array[], int n, int v)
    {
        int left, right, middle;
    
        left = 0, right = n - 1;
    
        while (left <= right)
        {
            middle = (left + right) / 2;
            if (array[middle] > v)
            {
                right = middle;
            }
            else if (array[middle] < v)
            {
                left = middle;
            }
            else
            {
                return middle;
            }
        }
    
        return -1;
    }
    



    下面,讲讲在编写二分查找算法时可能出现的一些问题.

    边界错误造成的问题
    二分查找算法的边界,一般来说分两种情况,一种是左闭右开区间,类似于[left, right),一种是左闭右闭区间,类似于[left, right].需要注意的是, 循环体外的初始化条件,与循环体内的迭代步骤, 都必须遵守一致的区间规则,也就是说,如果循环体初始化时,是以左闭右开区间为边界的,那么循环体内部的迭代也应该如此.如果两者不一致,会造成程序的错误.比如下面就是错误的二分查找算法:


    这个算法的错误在于, 在循环初始化的时候,初始化right=n,也就是采用的是左闭右开区间,而当满足array[middle] > v的条件是, v如果存在的话应该在[left, middle)区间中,但是这里却把right赋值为middle - 1了,这样,如果恰巧middle-1就是查找的元素,那么就会找不到这个元素.

    下面给出两个算法, 分别是正确的左闭右闭和左闭右开区间算法,可以与上面的进行比较:

    下面这两个算法是正确的



    死循环
    上面的情况还只是把边界的其中一个写错, 也就是右边的边界值写错, 如果两者同时都写错的话,可能会造成死循环,比如下面的这个程序:

     1 int search_bad2(int array[], int n, int v)
     2 {
     3     int left, right, middle;
     4 
     5     left = 0, right = n - 1;
     6 
     7     while (left <= right)
     8     {
     9         middle = (left + right) / 2;
    10         if (array[middle] > v)
    11         {
    12             right = middle;
    13         }
    14         else if (array[middle] < v)
    15         {
    16             left = middle;
    17         }
    18         else
    19         {
    20             return middle;
    21         }
    22     }
    23 
    24     return -1;
    25 }
    View Code


    这个程序采用的是左闭右闭的区间.但是,当array[middle] > v的时候,那么下一次查找的区间应该为[middle + 1, right], 而这里变成了[middle, right];当array[middle] < v的时候,那么下一次查找的区间应该为[left, middle - 1], 而这里变成了[left, middle].两个边界的选择都出现了问题, 因此,有可能出现某次查找时始终在这两个范围中轮换,造成了程序的死循环.

    溢出
    前面解决了边界选择时可能出现的问题, 下面来解决另一个问题,其实这个问题严格的说不属于算法问题,不过我注意到很多地方都没有提到,我觉得还是提一下比较好.
    在循环体内,计算中间位置的时候,使用的是这个表达式:

    middle = (left + right) / 2;
    


    假如,left与right之和超过了所在类型的表示范围的话,那么middle就不会得到正确的值.
    所以,更稳妥的做法应该是这样的:

    middle = left + (right - left) / 2;


    更完善的算法
    前面我们说了,给出的第一个算法是一个"正确"的程序, 但是还有一些小的问题.
    首先, 如果序列中有多个相同的元素时,查找的时候不见得每次都会返回第一个元素的位置, 比如考虑一种极端情况:序列中都只有一个相同的元素,那么去查找这个元素时,显然返回的是中间元素的位置.
    其次, 前面给出的算法中,每次循环体中都有三次情况,两次比较,有没有办法减少比较的数量进一步的优化程序?
    <<编程珠玑>>中给出了解决这两个问题的算法,结合前面提到溢出问题我对middle的计算也做了修改:

     1 int search4(int array[], int n, int v)
     2 {
     3     int left, right, middle;
     4 
     5     left = -1, right = n;
     6 
     7     while (left + 1 != right)//这个循环维持的条件是left<right && array[left]<v<=array[right],所以到最后的时候,
     8     {//如果可以找到目标,则只剩下两个数,并且满足 array[left]<v<=array[right],是要查找的数是right
     9         middle = left + (right - left) / 2;
    10 
    11         if (array[middle] < v)//必须保证array[left]<v<=array[right],所以left = middle;
    12         {//如果left =middle+1,则有可能出现 array[left]<=v的情况
    13             left = middle;
    14         }
    15         else
    16         {
    17             right = middle;
    18         }
    19     }
    20 
    21     if (right >= n || array[right] != v)
    22     {
    23         right = -1;
    24     }
    25 
    26     return right;
    27 }

    这个算法是所有这里给出的算法中最完善的一个,正确,精确且效率高.

    但是这个算法的还是不能很好的理解

    可以用下面的算法,可以找出满足条件的数

     

     1 int Bi_Search(int a[],int n,int b)//   
     2 {//返回等于b的第一个   
     3     if(n==0)  
     4         return -1;  
     5     int low = 0;  
     6     int high = n-1;  
     7     int last = -1;//用last记录上一次满足条件的下标   
     8     while (low<=high)  
     9     {  
    10         int mid = low +(high-low)/2;  
    11         if (a[mid]==b)  
    12         {  
    13             last = mid;  
    14             high = mid -1;  
    15         }  
    16         else if(a[mid]>b)  
    17             high = mid -1;  
    18         else  
    19             low = mid +1;  
    20     }  
    21   
    22     return last;  
    23   
    24 }  
    25 int Bi_Search1(int a[],int n,int b)//大于b的第一个数   
    26 {  
    27     if(n<=0)  
    28         return -1;  
    29     int last = -1;  
    30     int low = 0;  
    31     int high = n-1;  
    32     while (low<=high)  
    33     {  
    34         int mid = low +(high - low)/2;  
    35         if(a[mid]>b)  
    36         {  
    37             last = mid;  
    38             high = mid -1;  
    39         }  
    40         else if (a[mid]<=b)  
    41         {  
    42             low =mid +1;  
    43         }  
    44     }  
    45   
    46     return last;  
    47 }  
    48 int Bi_Search(int a[],int n,int b)//
    49 {//返回等于b的第一个
    50     if(n==0)
    51         return -1;
    52     int low = 0;
    53     int high = n-1;
    54     int last = -1;//用last记录上一次满足条件的下标
    55     while (low<=high)
    56     {
    57         int mid = low +(high-low)/2;
    58         if (a[mid]==b)
    59         {
    60             last = mid;
    61             high = mid -1;
    62         }
    63         else if(a[mid]>b)
    64             high = mid -1;
    65         else
    66             low = mid +1;
    67     }
    68 
    69     return last;
    70 
    71 }
    72 int Bi_Search1(int a[],int n,int b)//大于b的第一个数
    73 {
    74     if(n<=0)
    75         return -1;
    76     int last = -1;
    77     int low = 0;
    78     int high = n-1;
    79     while (low<=high)
    80     {
    81         int mid = low +(high - low)/2;
    82         if(a[mid]>b)
    83         {
    84             last = mid;
    85             high = mid -1;
    86         }
    87         else if (a[mid]<=b)
    88         {
    89             low =mid +1;
    90         }
    91     }
    92 
    93     return last;
    94 }View Code 
    View Code
     
  • 相关阅读:
    处理键盘弹出
    纯手码自动布局
    ios 随机色 宏定义
    linux下自定义pid实现任意数据采集
    http://blog.chinaunix.net/uid-9845710-id-1996675.html snmpd配置
    http://www.360doc.com/content/10/0928/12/11991_57014502.shtml
    pingall脚本
    http://lihuipeng.blog.51cto.com/3064864/643960
    elf 文件
    php中获取周几的方法
  • 原文地址:https://www.cnblogs.com/gongxijun/p/3216911.html
Copyright © 2011-2022 走看看