Binary Search

二分的原理是利用区间内值有序的特点, 不断让可行区间减半, 最终可行区间长度减到1得到答案
要保证二分能得到正确答案并且不会死循环, 要保证两个条件:
1. 解一直在候选区间里
2. 每次判断后可行区间都会缩小(特别是左右端点相距为1的时候) 

所以写二分的时候,一定要测试只有两个点时,不同情况下区间是否会缩减成一个(那一个就是解),否则算法可能陷入死循环。

 

除了二分寻找某个元素,upper_bound 和 lower_bound 都可以借助二分来做。

upper_bound返回第一个大于的元素的下标;

lower_bound返回第一个大于等于元素的下标。

 

 

闭区间 [low, high]

lower_bound:

初始参数为 low=0, high=n-1

搜索区间为[low, high], 返回值候选区间[low, high+1]

A[mid]<key时,搜索区间更新为[mid+1,high], 返回值候选区间[mid+1, high+1]

A[mid]>=key时,搜索区间更新为[low,mid-1], 返回值候选区间[low, mid]

while (low<=high) 需要包含等于号,这是因为如果while (low<high),终止时low=high,返回值候选区间[low, high+1]有两个值。

while (low<=high) 终止时low>high, 即low>=high+1。又由于low<=high+1,所以low=high+1。此时候选区间只有一个元素low,low就是解。

换一种思路,由于返回值候选区间[low, high+1],只有当low>=high+1时才有唯一解。因此not(low>=high+1),即low<=high时都要循环。

int bsearch(int *A, int low, int high, int key){ //[low,high]
    int mid;
    while (low<=high){
        mid = (low+high)/2;
        if (A[mid]==key) 
            return mid;
        else if (A[mid]<key)
            low = mid+1;
        else
            high = mid-1; 
    }
    return -1;   
} 

int lower_bound(int *A, int low, int high, int key){ //[low,high]
    int mid;
    while (low<=high){
        mid = (low+high)/2;
        if (A[mid]<key)
            low = mid+1;
        else
            high = mid-1; 
    }
    return low;
}

int upper_bound(int *A, int low, int high, int key){ //[low,high]
    int mid;
    while (low<=high){
        mid = (low+high)/2;
        if (A[mid]<key)
            low = mid+1;
        else if (A[mid]==key)
            low = mid+1;
        else
            high = mid-1; 
    }
    return low;
}

 有另一种理解方式,while (low<=high) 终止条件是low>high,此时搜索区间为空。满足终止条件以后,low就是答案。

这是因为这个方法就是维护low的过程,只有A[mid]<key时,low才会前进,逼近最终的答案。而high是单纯缩小数组的空间。

 

 

左闭右开区间 [low, high)

lower_bound:

初始参数为 low=0, high=n

搜索区间为[low, high), 返回值候选区间[low, high]

A[mid]<key时,搜索区间更新为[mid+1,high), 返回值候选区间[mid+1,high]

A[mid]>=key时,搜索区间更新为[low,mid), 返回值候选区间[low, mid]

while (low<high) 终止时low>=high,又由于low<=high,所以low=high。候选区间只有一个元素low即所求。

换一种思路,由于返回值候选区间[low, high],只有当low>=high时才有唯一解。因此not(low>=high),即low<high时都要循环。

// 普通二分查找  
int bsearch(int *A, int low, int high, int key){ //[low, high)  
    int mid;
    while (low<high){  
        mid = (low+high)/2;
        if (A[mid]==key) 
            return mid;  
        else if (A[mid]<key) 
            low = mid+1;
        else 
            high = mid;  
    }  
    return -1;  
}  
  
// 查找下界  
// 当 x 存在时返回它出现的第一个位置,否则返回这样一个下标 i;  
// 在此处插入 val 后序列仍然有序  
int lower_bound(int *A, int low, int high, int key){ //[low, high)  
    int mid;
    while (low<high){  
        mid = (low+high)/2;
        if (A[mid]<key) 
            low = mid+1;
        else 
            high = mid;  
    }  
    return low;  
}  
  
// 查找上界  
// 当 x 存在时返回它出现的最后一个位置后面一个位置,否则返回这样一个下标 i;  
// 在此处插入 val 后序列仍然有序  
int upper_bound(int *A, int low, int high, int key){ //[low, high)  
    int mid;
    while (low<high){  
        mid = (low+high)/2;
        if (A[mid]<key) 
            low = mid+1;
        else if (A[mid]==key)
            low = mid+1;  
        else 
            high = mid;  
    }  
    return low;
}  

 

可以看出,lower_bound和upper_bound只有在对 A[mid]==key时有所不同,其余都是相同的。自己写的时候也可以三种情况分别考虑一下,合不合并都无所谓。

设lower_bound和upper_bound的返回值分别为L和R,则key出现的子序列为[L,R)。这个结论在key不存在时也成立,此时L=R,区间为空。

 

总结:

要准确实现二分查找,要把握下面几个要点:

  high=n-1 => while (low<=high) => high=mid-1;

  high=n => while (low<high) => high=mid;

换言之,算法所操作的区间,是左闭右开区间,还是左闭右闭区间,这个区间,需要在循环初始化。且在循环体终止的判断中,以及每次修改low, high区间值这三个地方保持一致,否则就可能出错。

 

这篇博客总结的也很不错,可做参考 http://www.cnblogs.com/grandyang/p/6854825.html

posted @ 2018-05-12 12:57  約束の空  阅读(220)  评论(0)    收藏  举报