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;
}