二分法的邊界選擇

要在有序數組中找某個數,這個數只出現一次

這個寫法有三個點需要注意

  • 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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章