# Search Algorithms
By Colin
---
## What is search algorithm?
- 在資料中尋找特定元素的演算法
- 例如:
- 尋找3是否在 5 1 7 2 9 4 3 8 6 中
- 尋找3在 5 1 7 2 9 4 3 8 6 中的位置
- 尋找在 1 3 5 7 9 中比6大的最小值
- 尋找 5 1 7 2 9 4 3 8 6 中第3大的元素
---
## Linear Search 線性搜尋
- 又稱暴搜、窮舉
- 逐⼀檢查資料集合中的每個項⽬,直到找到⽬標項⽬
- 時間複雜度:$O(n)$
----
Example 1:
尋找3是否在陣列 int arr[9]={5,1,7,2,9,4,3,8,6} 中
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
signed main(){
int arr[9]={5,1,7,2,9,4,3,8,6};
bool ans=0;
for(int i=0;i<9;i++){
if(arr[i]==3){
ans=1; break;
}
}
if(ans) cout<<"Yes\n";
else cout<<"No\n";
return 0;
}
```
----
Example 2:
尋找3在陣列 int arr[9]={5,1,7,2,9,4,3,8,6} 中的位置
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
signed main(){
int arr[9]={5,1,7,2,9,4,3,8,6};
int index;
for(int i=0;i<9;i++){
if(arr[i]==3){
index=i; break;
}
}
cout<<index<<'\n';
return 0;
}
```
----
Example 3:
[CSDC246 不擅長出題的社長](https://csdc.tw/problem/246/)
----
題解:
- 想法:用兩層for迴圈窮舉$t$和$q$,再判斷$k-a\times t-b\times q$是否整除$c$,即可得知是否為一組解
- 窮舉的範圍:因為$k,a,b,c,t,q,r$皆為非負整數,所以$a\times t\le k$,$t$只需窮舉到$\lfloor \frac{k}{a}\rfloor$,同理,$a\times t+b\times q\le k$,$q$只需窮舉到$\lfloor \frac{k-a\times t}{b}\rfloor$
- 注意:$k=0$時,若透過上述方法窮舉,$t$,$q$,$r$會皆為$0$,不符合要求,所以需特別判斷
----
AC code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
signed main(){
int n,k,a,b,c; cin>>n;
while(n--){
int ans=0;
cin>>k>>a>>b>>c;
if(k==0){
cout<<"No solution exists\n";
continue;
}
for(int t=0;t<=(k/a);t++){
for(int q=0;q<=(k-a*t)/b;q++){
if((k-a*t-b*q)%c==0){
int r=(k-a*t-b*q)/c;
cout<<t<<' '<<q<<' '<<r<<'\n';
ans=1; break;
}
}
if(ans) break;
}
if(!ans) cout<<"No solution exists\n";
}
return 0;
}
```
---
## Binary Search 二分搜尋
- 簡稱二分搜
- ⽤於有序資料(具有單調性的資料)的搜尋⽅法
- 反覆將搜尋範圍縮⼩⼀半,通過比較⽬標值與中間元素的⼤⼩,來判斷⽬標值可能在資料的哪⼀半
- 時間複雜度:$O(log_2\ n)$
----
變數假設:
$n$: 資料數量
$x$: 目標數值
$left$: 目前處理的資料左界的編號,初始化為$0$
$right$: 目前處理的資料右界的編號,初始化為$n-1$
$mid$: 左界與右界的中間
----
$mid=(left+right)/2$
//資料總數較大時$(left+right)$可能會overflow<!-- .element: class="fragment" data-fragment-index="1" -->
$mid=left+(right-left)/2$
//Better<!-- .element: class="fragment" data-fragment-index="2" -->
----
$n=10$,$x=48$
$left=0,right=9$<!-- .element: class="fragment" data-fragment-index="1" -->
$mid=0+(9-0)/2=4$<!-- .element: class="fragment" data-fragment-index="2" -->

因為$x>arr[mid]$,所以可以將$arr[left]$ ~ $arr[mid]$這段捨去<!-- .element: class="fragment" data-fragment-index="3" -->
將$left$修改成<!-- .element: class="fragment" data-fragment-index="4" -->$mid+1=5$<!-- .element: class="fragment" data-fragment-index="4" -->
----
$left=5,right=9$
$mid=5+(9-5)/2=7$<!-- .element: class="fragment" data-fragment-index="1" -->

因為$x<arr[mid]$,所以可以將$arr[mid]$ ~ $arr[right]$這段捨去<!-- .element: class="fragment" data-fragment-index="2" -->
將$right$修改成<!-- .element: class="fragment" data-fragment-index="3" -->$mid-1=6$<!-- .element: class="fragment" data-fragment-index="3" -->
----
$left=5,right=6$
$mid=5+(6-5)/2=5$<!-- .element: class="fragment" data-fragment-index="1" -->

因為$x>arr[mid]$,所以可以將$arr[left]$ ~ $arr[mid]$這段捨去<!-- .element: class="fragment" data-fragment-index="2" -->
將$left$修改成<!-- .element: class="fragment" data-fragment-index="3" -->$mid+1=6$<!-- .element: class="fragment" data-fragment-index="3" -->
----
$left=6,right=6$
$mid=6+(6-6)/2=6$<!-- .element: class="fragment" data-fragment-index="1" -->

$x=arr[mid]$,完成搜尋,得到$x$所在編號為<!-- .element: class="fragment" data-fragment-index="2" -->$mid=6$<!-- .element: class="fragment" data-fragment-index="2" -->
----
code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int BinarySearch(int arr[],int n,int x){
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(x==arr[mid]) return mid;
else if(x<arr[mid]) r=mid-1;
else l=mid+1;
}
return -1;
}
signed main(){
int n,x; cin>>n>>x; int arr[n];
for(int i=0;i<n;i++) cin>>arr[i];
cout<<BinarySearch(arr,n,x)<<'\n';
return 0;
}
```
----
Example 1:
[CSDC65 你的日常裡需要抄書](https://csdc.tw/problem/65/)
----
題解:
因為輸入的$N$個數必為有序序列,所以對於每個輸入的$X$,二分搜其在陣列內的位置即可
----
AC code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int BinarySearch(int arr[],int n,int x){
int l=0,r=n-1;
while(l<=r){
int mid=l+(r-l)/2;
if(arr[mid]==x) return mid;
else if(x<arr[mid]) r=mid-1;
else l=mid+1;
}
}
signed main(){
int n,m; cin>>n>>m; int arr[n+1];
for(int i=1;i<=n;i++) cin>>arr[i];
while(m--){
int x; cin>>x;
cout<<BinarySearch(arr,n,x)<<'\n';
}
return 0;
}
```
----
Example 2:
[CSDC236 (緊急)遙遠的國度需水資源!!!](https://csdc.tw/problem/236/)
----
題解:
1. 若$m$個容積為$v$的罐子可以容納所有雨水,則$m$個容積大於$v$的罐子必定也可以容納所有雨水(罐子容積大小具有單調性),因此可以對罐子容積大小進行二分搜
2. 二分搜的範圍:
- 因為一杯水一定要完整倒入一個罐子,所以罐子容積大小至少要為最大的那杯水的體積
- 罐子容積大小至多為所有杯水的體積和,因為此時就可以容納所有雨水了,再多是浪費空間
----
3. 定義test()函數,檢測罐子容積為$v$時需要的罐子數量。若test(v)$\le m$,代表罐子容積為$v$時必定可以容納所有雨水,因此可將$r=mid-1$,往更小的容積檢查;若test(v)$>m$,代表罐子容積為$v$時無法容納所有雨水,因此可將$l=mid+1$,往更大的容積檢查
4. 模擬一下可以知道最終的答案會落在$l$
----
AC code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int t,m,n,arr[5005];
int test(int v){
int cnt=1,t=v;
for(int i=0;i<n;i++){
if(arr[i]<=t){
t-=arr[i];
}else{
cnt++;
t=v-arr[i];
}
}
return cnt;
}
int BinarySearch(int l,int r){
while(l<=r){
int mid=l+(r-l)/2;
if(test(mid)<=m) r=mid-1;
else l=mid+1;
}
return l;
}
signed main(){
horaceorz;
cin>>t;
while(t--){
cin>>m>>n; int maxi=-1,sum=0;
for(int i=0;i<n;i++){
cin>>arr[i];
maxi=max(maxi,arr[i]);
sum+=arr[i];
}
cout<<BinarySearch(maxi,sum)<<'\n';
}
return 0;
}
```
----
Example 3:
[APCS 2017/03 P4 基地台](https://zerojudge.tw/ShowProblem?problemid=c575)
----
題解:
1. 若直徑為$R$時可以覆蓋每個點,則直徑大於$R$時必定也可以覆蓋每個點(基地台直徑大小具有單調性),因此可以對直徑大小進行二分搜
2. 二分搜的範圍:
- 題目保證最小直徑必然是不小於1的整數,所以直徑至少要為1
- 直徑至多為最遠的兩個點的距離
----
3. 定義test()函數,檢測直徑為$R$時需要的基地台數量。若test($R$)$\le k$,代表直徑為$R$時必定可以覆蓋每個點,因此可將$r=mid-1$,往更小的直徑檢查;若test($R$)$>k$,代表直徑為$R$時無法覆蓋每個點,因此可將$l=mid+1$,往更大的直徑檢查
4. 模擬一下可以知道最終的答案會落在$l$
----
AC code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int n,k,arr[50005];
int test(int R){
int cnt=1,t=arr[0];
for(int i=1;i<n;i++){
if(arr[i]>t+R){
cnt++;
t=arr[i];
}
}
return cnt;
}
int BinarySearch(int l,int r){
while(l<=r){
int mid=l+(r-l)/2;
if(test(mid)<=k) r=mid-1;
else l=mid+1;
}
return l;
}
signed main(){
cin>>n>>k;
for(int i=0;i<n;i++) cin>>arr[i];
sort(arr,arr+n);
int l=1,r=arr[n-1]-arr[0];
cout<<BinarySearch(l,r);
return 0;
}
```
----
Example 4:
[APCS 2022/01 P4 牆上海報](https://zerojudge.tw/ShowProblem?problemid=h084)
----
題解:
1. 若高度為$H$時可以張貼所有海報,則高度小於$H$時必定也可以張貼所有海報(高度具有單調性),因此可以對高度進行二分搜
2. 二分搜的範圍:
- 最小高度為1
- 最大高度為最高的木板的高度
----
3. 定義test()函數,檢測高度為$H$時可以張貼的海報數量。若test($H$)$<k$,代表高度為$H$時無法張貼全部海報,因此可將$r=mid-1$,往更小的高度檢查;若test($H$)$=k$,代表高度為$H$時可以張貼全部海報,因此可將$l=mid+1$,往更大的高度檢查
4. 模擬一下可以知道最終的答案會落在$r$
----
AC code
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int n,k,h[200005],w[5005];
int test(int H){
int cnt=0,num=0,t=0;
for(int i=0;i<n;i++){
if(h[i]>=H){
t++;
}else{
while(t>=w[num]){
t-=w[num];
num++;
cnt++;
if(num==k) return cnt;
}
t=0;
}
}
while(t>=w[num]){
t-=w[num];
num++;
cnt++;
if(num==k) return cnt;
}
return cnt;
}
int BinarySearch(int l,int r){
while(l<=r){
int mid=l+(r-l)/2;
if(test(mid)<k) r=mid-1;
else l=mid+1;
}
return r;
}
signed main(){
cin>>n>>k;
int maxi=-1;
for(int i=0;i<n;i++){
cin>>h[i];
maxi=max(maxi,h[i]);
}
for(int i=0;i<k;i++) cin>>w[i];
int l=1,r=maxi;
cout<<BinarySearch(l,r);
return 0;
}
```
----
Example 5:
[CSDC346 2077](https://csdc.tw/problem/346/)
aka 第39屆進階組資格考p5
----
題解:
我懶得打了(X
很難的二分搜題目
去問副社長或是看[**上次資格考的題解**](https://hackmd.io/EOZUB47lSeSiJB6w4eoo6w)
----
AC code
```cpp=
#include <iostream>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
int Q(int p){
if(p<=100*2) return p/2;
else if(p<=100*2+9900*3) return 100+(p-(100*2))/3;
else if(p<=100*2+9900*3+990000*5) return 10000+(p-(100*2)-(9900*3))/5;
else return 1000000+(p-(100*2)-(9900*3)-(990000*5))/7;
}
int P(int q){
if(q<=100) return q*2;
else if(q<=10000) return 100*2+(q-100)*3;
else if(q<=1000000) return 100*2+9900*3+(q-10000)*5;
else return 100*2+9900*3+990000*5+(q-1000000)*7;
}
signed main(){
int A,B;
while(cin>>A>>B&&A&&B){
int qx,qy,qsum=Q(A);
//f=P(qsum-qx)-P(qx)=B
//if(f>B) qx要增加
//if(f<B) qx要減少
int l=0,r=qsum;
while(1){
int mid=l+(r-l)/2;
if(P(qsum-mid)-P(mid)==B){
qx=mid;
break;
}else if(P(qsum-mid)-P(mid)>B){
l=mid+1;
}else if(P(qsum-mid)-P(mid)<B){
r=mid-1;
}
}
cout<<P(qx)<<'\n';
}
return 0;
}
```
---
補充:
STL函式
#include\<algorithm>
lower_bound() 尋找有序資料大於等於某數的最小值
upper_bound() 尋找有序資料大於某數的最小值
----
傳統陣列
```cpp=
int arr[5]={1,3,5,7,9};
auto it1=lower_bound(arr,arr+5,3); //it1會是iterator(記憶體位址)
cout<<*it1<<'\n'; //值=3
cout<<it1-arr<<'\n'; //位置=1
auto it2=upper_bound(arr,arr+5,3); //it2會是iterator(記憶體位址)
cout<<*it2<<'\n'; //值=5
cout<<it2-arr<<'\n'; //位置=2
```
----
vector
```cpp=
vector<int> v={1,3,5,7,9};
auto it1=lower_bound(v.begin(),v.end(),3); //it1會是iterator(記憶體位址)
cout<<*it1<<'\n'; //值=3
cout<<it1-v.begin()<<'\n'; //位置=1
auto it2=upper_bound(v.begin(),v.end(),3); //it2會是iterator(記憶體位址)
cout<<*it2<<'\n'; //值=5
cout<<it2-v.begin()<<'\n'; //位置=2
```
----
用lower_bound()解 Example 1: [CSDC 65](https://csdc.tw/problem/65/)
```cpp=
#include <bits/stdc++.h>
#define int long long
#define horaceorz ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
using namespace std;
signed main(){
int n,m; cin>>n>>m; int arr[n];
for(int i=0;i<n;i++) cin>>arr[i];
while(m--){
int x; cin>>x;
cout<<lower_bound(arr,arr+n,x)-arr+1<<'\n';
}
return 0;
}
```
----
不要太常用lower_bound()和upper_bound(),因為很多題目還是需要自己定義二分搜的判別條件,像是前面的 Example 2~5 ,而且APCS觀念題幾乎每次都會考一兩題二分搜,所以還是好好練習手刻吧XD
----
練習題(們):
[binary search](https://csdc.tw/problems/?tag=binary%20search)
---
## Thank you for listening!
{"description":"當你在資料集合中尋找特定項⽬時,搜尋法是⼀種幫助你有效率地執⾏這項任務的技術","title":"Search Algorithms","contributors":"[{\"id\":\"fd160699-cd06-45fd-abd0-c09edbc85980\",\"add\":13433,\"del\":1560}]"}