要在有序数组中找某个数,这个数只出现一次
这个写法有三个点需要注意
- end 是指向实际的值的
- begin=m+1, end=m-1
- 循环的条件是 begin<=end, 因为begin,end 都指向数组中的元素,所以相等时依然要再判断一次
int find(int x, int *a, int begin, int end) {
while (begin <= end) {
int m = (begin + end) / 2;
if (a[m] == x) {
return m;
} else if (a[m] < x) {
begin = m+1;
} else {
end = m-1;
}
}
return -1;
}
int main() {
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
cout << find(0, a, 0, 9) << endl;
cout << find(5, a, 0, 9) << endl;
cout << find(9, a, 0, 9) << endl;
return 0;
}
找到一个数字出现的区间,开始位置或者结束位置,或者插入位置
- 数字不存在时的返回值。这种情况下,返回的值可能是不同的,比如当值不存在时,前面的代码返回的是 -1 ,这里很可能要求返回插入位置,
- 返回值的范围。所以如果上面那个有效返回范围是数组下标0到n-1,这种情况的返回值则是 0到 n,由于有n+1 个可插入的位置
- 在begin/end 分别取 0 和 n 后,则 m 只能是begin+(end-begin)/2,也就是(begin+end)/2,如果我们使用 m=end-(end-begin)/2, 也就是中间偏后的数,则会在 begin=n-1,end=n 时 a[m] 越界
- 在 m 不得不取中间偏前的数字的情况下,begin 在更新时也就一定是取m+1,否则 begin=a,end=a+1 时,则会进入死循环
其实下面的这种写法可以改写成满足上面的题目的代码,所以下面这种写法通用性更好。
int find_begin(int x, int *a, int begin, int end) {
while (begin < end) {
int m = (begin + end) / 2;
if (a[m] == x) {
end = m;
} else if (a[m] < x) {
begin = m + 1;
} else {
end = m;
}
}
return begin;
}
int find_back(int x, int *a, int begin, int end) {
while (begin < end) {
int m = (begin + end) / 2;
if (a[m] == x) {
begin = m + 1;
} else if (a[m] < x) {
begin = m + 1;
} else {
end = m;
}
}
return begin;
}
int main() {
int b[] = {0, 0, 0, 2, 2, 3, 3, 3, 3, 3};
assert(find_begin(0, b, 0, 10) == 0);
assert(find_begin(2, b, 0, 10) == 3);
assert(find_begin(3, b, 0, 10) == 5);
assert(find_begin(-1, b, 0, 10) == 0);
assert(find_begin(1, b, 0, 10) == 3);
assert(find_begin(20, b, 0, 10) == 10);
assert(find_back(0, b, 0, 10) == 3);
assert(find_back(2, b, 0, 10) == 5);
assert(find_back(3, b, 0, 10) == 10);
assert(find_back(-1, b, 0, 10) == 0);
assert(find_back(1, b, 0, 10) == 3);
assert(find_back(20, b, 0, 10) == 10);
return 0;
}