Basic

Search

Binary Search

二元搜尋

排序好的陣列

  • 在排序好陣列S裡面搜尋某個元素x是否存在
0 1 2 3 4
x

暴力法 \(O(n)\)

vector<int> S;
int search(int x){
    int n = S.size();
    for(int i = 0; i < n; ++i)
        if(S[i] == x) return i;
    return -1;
}

選中間的

  • 假設這邊選到的是y
  • 因為陣列排序過
    我們可以判斷出x在y的左邊還是右邊
y x

選中間的

  • 如果 y < x
  • 顯然x只會出現在y的右半部分
  • 我們只需要在右邊繼續搜尋
y x

選中間的

  • 如果 y > x
  • 顯然x只會出現在y的左半部分
  • 我們只需要在左邊繼續搜尋
x y

分析

  • 我們每次搜尋之後,x的可能範圍就會減少一半
  • 如此不斷做下去,最多只要\(O(\log_2n)=O(\log n)\)的次數
  • 稱為:二分搜尋法

二分搜尋法

vector<int> S;
int search(int x){
    int L = 0, R = int(S.size()) - 1;
    while(L <= R){ // 初學者這裡容易寫壞(無限迴圈)
        int mid = (L+R) / 2;
        if(S[mid] == x) return mid;
        if(S[mid] < x) // x 在右邊
            L = mid + 1;
        else // x 在左邊
            R = mid - 1;
    }
    return -1;
}

如果沒有信心的話

vector<int> S;
int search(int x){
    int L = 0, R = int(S.size()) - 1;
    while(R - L > 5){ // 給一個很小的範圍
        int mid = (L+R) / 2;
        if(S[mid] == x) return mid;
        if(S[mid] < x) // x 在右邊
            L = mid + 1;
        else // x 在左邊
            R = mid - 1;
    }
    for(; L <= R; ++L) //剩下的暴力搜尋
        if(S[L] == x) return L;
    return -1;
}

加強版二分搜尋

資料分類

  • 對於我們要搜尋的x,可以將陣列分成3個部分
  • 兩個重要的位置
< x == x > x

lower bound

  • >= x 的第一個元素位置
< x == x > x

lower bound

  • >= x 的第一個元素位置
< x > x

lower bound

  • >= x 的第一個元素位置
< x

lower bound

vector<int> S;
int lowerBound(int x){
    int L = 0, R = S.size();
    while(L < R){
        int mid = (L + R) / 2;
        if(S[mid] < x)
            L = mid + 1;
        else
            R = mid;
    }
    return L;
}

upper bound

  • > x 的第一個元素位置
< x == x > x

upper bound

  • > x 的第一個元素位置
< x > x

upper bound

  • > x 的第一個元素位置
< x

upper bound

vector<int> S;
int upperBound(int x){
    int L = 0, R = S.size();
    while(L < R){
        int mid = (L + R) / 2;
        if(S[mid] <= x)
            L = mid + 1;
        else
            R = mid;
    }
    return L;
}

例題

例題 1

不見的數字

題目敘述

  • 給你一個排序好,大小為n的整數陣列S
  • S中每個數字只會出現一次,範圍為0 ~ n
  • 該範圍有一個數字不在陣列中,請你找出那個數字

稍微改寫upper_bound

vector<int> S;
int search(){
    int L = 0, R = S.size();
    while(L < R){
        int mid = (L + R) / 2;
        if(S[mid] == mid)
            L = mid + 1;
        else
            R = mid;
    }
    return L;
}

例題 2

題目敘述

  • 派對共準備了a片鹹蛋糕和b片甜蛋糕
  • 來參加派對的人總共有n位
    因此你要把這些蛋糕分配到n個盤子上
  • 每個人都要吃到蛋糕,吃幾片都可以
    大家都只想吃一種口味,不希望有甜又有鹹
  • 另外,當你分配完之後
    你會拿到最少蛋糕的那盤,因為大家會先拿
    你希望自己拿到的蛋糕愈多愈好,所以蛋糕要盡量平均分配
  • 請問你最多可以拿到幾片蛋糕?

暴力解

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, a, b;
    cin >> n >> a >> b;

    int sol;
    for(int i = 1; i <= min(a,b); ++i)
        if(a/i + b/i >= n)
            sol = i;
    cout << sol << '\n';
    return 0;
}

二分搜尋解

#include <bits/stdc++.h>
using namespace std;
int main(){
    int n, a, b;
    cin >> n >> a >> b;

    int L = 1, R = min(a,b) + 1;
    while(L < R){
        int mid = (L+R)/2;
        if(a/mid + b/mid >= n)
            L = mid + 1;
        else
            R = mid;
    }
    cout << L - 1 << '\n';
    return 0;
}

例題 3

int n = nums1.size(), m = nums2.size();
int L = 0, R = n;
int midian;
while(true){
    int mid = (L+R)/2;
    int remain = (n+m+1)/2 - mid;
    if(remain < 0){
        R = mid-1;
        continue;
    }
    if(mid < n && remain > 0 && nums2[remain - 1] > nums1[mid])         
        L = mid + 1;
    else if(mid > 0 && remain < m && nums2[remain] < nums1[mid - 1])
        R = mid - 1;
    else{
        if(mid == 0) midian = nums2[remain - 1];
        else if(remain == 0) midian = nums1[mid - 1];
        else midian = max(nums1[mid - 1], nums2[remain - 1]);
        break;
    }
}
if((n+m)%2) return midian;
if(mid == n) return (midian + nums2[remain]) / 2.0;
if(remain == m) return (midian + nums1[mid]) / 2.0;
return (midian + min(nums1[mid], nums2[remain])) / 2.0;

特殊用法

元素的個數

  • 給一個排序好的陣列
  • 有一些詢問,問你某個元素x在陣列中出現的個數
upperBound(x) - lowerBound(x)

C++ STL 二分搜

  • #include<algorithm>
  • std::lower_bound
  • std::upper_bound

std::lower_bound(L, R, x)

  • L: 左界的指標
  • R: 右界的指標
  • x: 要搜尋的元素
int S[] = {0,1,2,3,4,5};
cout << *lower_bound(S, S + 6, 3) << '\n'; // 3

vector<int> S2 = {4,5,6,7,8,9};
cout << *lower_bound(S2.begin(), S2.end(), 7) << '\n'; // 7

std::upper_bound(L, R, x)

  • L: 左界的指標
  • R: 右界的指標
  • x: 要搜尋的元素
int S[] = {0,1,2,3,4,5};
cout << *upper_bound(S, S + 6, 3) << '\n'; // 4

vector<int> S2 = {4,5,6,7,8,9};
cout << *upper_bound(S2.begin(), S2.end(), 7) << '\n'; // 8

元素出現次數

int S[] = {0,1,3,3,4,5};
cout << upper_bound(S, S + 6, 3) -
        lower_bound(S, S + 6, 3) << '\n'; // 2
Made with Slides.com