C++的区间是左闭右开的,关于这样做的优势,做了一个笔记整理,也处理下之前一直比较模糊的区间二分的问题。

左闭右开的区间第一个优势是,当需要取中间元素的时候,mid=begin+end/2的定位问题。如果区间元素的个数是奇数个,那么mid永远是指向中间的元素;如果区间元素是偶数个,那么mid永远指向后半段区间的首元素。这样做在二分查找等一些算法的实现上特别有优势。mid的另一个等效的写法是mid=begin+(end-begin)/2

比如区间的下标是0,1,2,3,是偶数个,那么begin=0,end=4,所以mid=(begin+end)/2=(0+4)/2=2,正好是后半段首元素。再看区间下标是0,1,2,3,4,是奇数个,那么begin=0,end=5,所以mid=(begin+end)/2=(0+5)/2=2,此时2正好是中间的元素。
在任意合理的区间[begin,end)上,总是有mid=(begin+end)/2把区间分成[begin,begin+mid)[mid,end)两个部分。

第二个优势在于方便迭代器快速的进行终止判别。使用左闭右开的区间,迭代终止的条件是begin==end(或者begin>=end),这样仅需要一个条件就能终止迭代判断。

第三个优势在于快速统计区间元素的个数,n=end-begin即为元素的个数。

上述两个优势对于特殊情况,只有一个或者两个元素的区间(这一般发生在二分之类的算法快要终止的时候),也有更好的效果。如果当前只剩下一个元素,位置是index,则所在的区间是[index,index+1),因为这是奇数个元素,所以mid=(2*index+1)/2=index,所以前半个区间是mid元素,后半段区间是空。如果有两个元素,[index,index+1],那么mid=(2*index+2)/2=index+1,正好把区间分成两个元素,又回到了仅有一个元素的情况了。

根据上述的描述,可以更好的理解二分算法了:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

int BinarySearch(int Arr[], int first, int last, int val) {
    while(first != last) {      // 终止条件
        int mid = first + (last - first) / 2;  // 区间二分
        // int mid = (first + last) / 2;       // 另一种等效的写法
        if(Arr[mid] == val) {   // 找到
            return mid;
        } else if(mid > val) {  // 左侧区间
            last = mid;
        } else {                // 右侧区间
            first = mid + 1;
        }
    }
    return -1; // 没找到
}

int main() {

    int A1[5] = {0, 1, 2, 3, 4};
    cout << "--------------测试奇数个元素----------------\n";
    for(int i = 0; i < 5; ++i) {
        cout << BinarySearch(A1, 0, 5, i) << endl;
    }
    cout << BinarySearch(A1, 0, 4, -1) << endl;
    cout << BinarySearch(A1, 0, 4, 5) << endl;

    int A2[5] = {0, 1, 2, 3};
    cout << "--------------测试偶数个元素----------------\n";
    for(int i = 0; i < 5; ++i) {
        cout << BinarySearch(A2, 0, 4, i) << endl;
    }
    cout << BinarySearch(A2, 0, 4, -1) << endl;
    cout << BinarySearch(A2, 0, 4, 4) << endl;

    return 0;
}