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
Basic Search
By jacky860226
Basic Search
- 164