要在有序數組中找某個數,這個數只出現一次
這個寫法有三個點需要注意
- 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;
}