# **Algorithm 演算法搜尋筆記** **[此筆記的原始碼](https://hackmd.io/qi8shArlSsS09WCNOVLyow?both)** --- ## 搜尋演算法 * 線性搜尋(Linear search) * 二元搜尋(Binary search) * 指數搜尋(Exponential search) * 插補搜尋(Interpolation search) * 費氏搜尋(Fibonacci search) ### **線性搜尋(Linear search)** #### 從第一個元素開始,依照順序,接續判斷是否為要搜索的元素,非常簡單。 ![image alt](https://www.tutorialspoint.com/data_structures_algorithms/images/linear_search.gif) 此種方法的優點是原陣列內部的元素不需要是排序過後的,因此若有新的元素加入陣列也可以直接搜尋,但是缺點是倘若被搜尋的元素是在陣列的末端,那麼將會耗費相當巨量的時間。 #### 時間複雜度: Note:假設有陣列中有$n$個元素 * Best Case:$O(1)$,剛好第一個index就是被搜索的元素 * Worst Case:$O(n)$,被搜索的元素在陣列末端,因此需要執行$n$次比對的步驟。 #### C code: ```c= #include <stdio.h> #define SIZE 10 int linear_search(int *, int); int main(){ int array[SIZE]={1,6,5,7,9,5,4,2,8,10}; int index; index=linear_search(array,5); printf("%d\n",index); return 0; } int linear_search(int *array, int number){ int index=0; for(index=0;index<SIZE;index++){ if(array[index]==number){ return index; } } //if the element doesn't appear in the array return -1; } ``` code:https://github.com/coherent17/search-algorithm/blob/main/search/linear_search.c ### **二元搜尋(Binary search)** #### 或稱為二分搜尋法,可以在"已排序"的序列中進行高效率的搜尋。 ![image alt](https://d18l82el6cdm1i.cloudfront.net/uploads/bePceUMnSG-binary_search_gif.gif) 取已排序資料中間index的值來跟被搜尋的數來比較,若非此數,則將資料以中間索引分半。若較小,則選擇較小的那半資料,再取較小那半資料中間的數字來比大小,反之亦然,持續重複步驟,便能找到該數。 #### 時間複雜度: Note:假設有陣列中有$n$個元素 * Best Case:$O(1)$,剛好中間的數值就是被搜索的數值 * Worst Case:$O(log(n))$,最慘需要折半對比$log_2(n)$次。 #### C code: 有兩種版本,一種是用迴圈的,另一種則是用遞迴。 在這邊有一個特別需要注意到的事情為中點的計算方式: ```c= int mid=(left+right)/2; ``` 若是使用這種計算方法,當$left$及$right$的總和超過$int$的範圍,那麼這邊將會出現不可預期的錯誤。 當($left+right>2^{31}-1$)將會溢位。那要如何改善呢? ```c= int mid=left+(right-left)/2; ``` 透過上面中點位置取法的改變,因為$right \geq left$,因此$right-left$就不會出現overflow的情形了! ##### 迴圈版(iterative binary search): ```c= #include <stdio.h> #define SIZE 10 int iterative_binary_search(int *, int, int, int); int main(){ int array[SIZE]={1,2,3,4,5,6,7,8,9,10}; int index; printf("iterative_binary_search:"); index=iterative_binary_search(array,0,SIZE-1,8); printf("%d\n",index); return 0; } int iterative_binary_search(int *array, int left, int right, int number){ while(left<=right){ //mid point index: int mid=left+(right-left)/2; //check if the number is at mid if(array[mid]==number) return mid; //if number is greater than mid, search right array else if(number>array[mid]) left=mid+1; //if number is smaller than mid, search left side else right=mid-1; } //the element isn't in the array return -1; } ``` code:https://github.com/coherent17/search-algorithm/blob/main/search/iterative_binary_search.c ##### 遞迴版(recursive binary search): ```c= #include <stdio.h> #define SIZE 10 int recursive_binary_search(int *, int, int, int); int main(){ int array[SIZE]={1,2,3,4,5,6,7,8,9,10}; int index; printf("recursive_binary_search:"); index=recursive_binary_search(array,0,SIZE-1,3); printf("%d\n",index); return 0; } int recursive_binary_search(int *array, int left, int right, int number){ if(left<=right){ //mid point index: int mid=left+(right-left)/2; //check if the number is at mid if(array[mid]==number) return mid; //if number is greater than mid, search right array else if(number>array[mid]) return recursive_binary_search(array, mid+1, right, number); //if number is smaller than mid, search left side else return recursive_binary_search(array, left, mid-1, number); } //the element isn't in the array return -1; } ``` code:https://github.com/coherent17/search-algorithm/blob/main/search/recursive_binary_search.c 用binary search的優點是非常有效率,可以看到worst case中,他的時間複雜度為$O(log(n))$,拿實際一點的數字來舉例,當有64筆資料,則最多需要試$log_2(64)=6$次,而當有128筆資料,則最多需要試$log_2(128)=7$次,可以看出隨著$n$增加,所需要試的次數增長是趨緩的。雖然使用binary search效率高,但是在使用binary search上有一個大前提是該數列必須要是"已排序"的數列。 ### **指數搜尋(Exponential search)** #### 為二分搜尋法的變形,可以在已排序後的陣列較快速的找到被搜尋的數值,不像Binary search是有邊界限制的。且被搜尋的數排在序列的越前面,效率越高。能夠縮小binary search進行的範圍,使其效率比binary search高。 ![image alt](https://img.magiclen.org/albums/exponential-search/animation-541x141.gif) 逐次比較被搜尋數與序列中index為2的冪次的數,直到被搜尋的數字大於序列中index為$2^k$的數,則針對$array[2^{k-1}:2^{k}-1]$進行binary search,即可找出該數。 Note that: 若$2^{k}-1$已經超出陣列的長度了,那麼我們將取陣列最後一個元素為binary search的右端點,因此真正再進行binary search的陣列為$array[2^{k-1}:min(2^{k}-1,size\ of\ array)]$ 舉例說明: 假設要找的數字為$47$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | | --- |:---:| --- | --- | --- | --- | --- | --- |:---:|:---:| | 2 | 9 | 30 | 32 | 38 | 47 | 61 | 69 | 79 | 81 | * Step 1:先比較$array[2^0]$是否大於被搜尋的數字47? 9<47 * Step 2:再依序比較$array[2^1],array[2^2],array[2^3]$,發現到$array[2^3]$時,其數值已經大於被搜索的數字了。 * Step 3:此時已經將binary search的範圍縮小到$array[2^{2}:2^{3}-1]$,再進行binary search即可找到被搜索數。 #### 時間複雜度: Note:假設有陣列中有$n$個元素 * Best Case:$O(1)$,剛好一開始的數值就是被搜索的數值 * Worst Case:$O(log(n))$,當被搜索數接近陣列的開頭,效率會高於binary search #### C code: ```c= #include <stdio.h> #define SIZE 10 int find_min(int, int); int binary_search(int *, int , int , int); int exponential_search(int *,int); int main(){ int array[SIZE]={1,2,3,4,5,6,7,8,9,10}; int index=exponential_search(array,8); printf("%d\n",index); return 0; } int find_min(int a, int b){ if(a<=b) return a; else return b; } int exponential_search(int *array,int number){ if(array[0]==number) return 0; //find the index to do the binary search int index=1; while(index<SIZE && array[index]<=number){ index*=2; } //do the binary search at array[index/2+1]~array[min(index-1,size of array)] return binary_search(array, (index/2),find_min(index-1,SIZE-1),number); } //use recursive binary search int binary_search(int *array, int left, int right, int number){ if(left<=right){ //mid point index: int mid=left+(right-left)/2; //check if the number is at mid if(array[mid]==number) return mid; //if number is greater than mid, search right array else if(number>array[mid]) return binary_search(array, mid+1, right, number); //if number is smaller than mid, search left side else return binary_search(array, left, mid-1, number); } //the element isn't in the array return -1; } ``` code:https://github.com/coherent17/search-algorithm/blob/main/search/exponential_search.c ### **插補搜尋(Interpolation search)** #### 一樣是二分搜尋法的變形,不過尋找中間點的index改為使用數學中的內插法來進行。 此被搜尋的數列必須為"已排序的數列",取最小及最大的兩點算出斜率,而後透過數學上的內插法去計算該被搜尋數的可能位置在哪裡,而後以那點為中點,最小及最大為範圍,進行比較,若該數大於剛剛算出的中點,則將剛剛的中點改為下界,再次使用內插法算出該數可能的位置。反之亦然。 #### 內插法: 設$(left,data[left])$,$(right,data[right])$為上限及下限的索引及所對應的數值。 兩點間的斜率為: * $\frac{data[right]-data[left]}{right-left}$ 使用點斜式造出通過此二點的線: * $y-data[left]=\frac{data[right]-data[left]}{right-left}(x-left)$ 設被搜尋數為$number$,而透過內插法求出他可能的索引值為$index$ 將$(index,number)$代入上式整理後可以得到我們預期被搜索數出現的$index$為: * $index=left+\frac{number-data[left]}{data[right]-data[left]}(right-left)$ 而後我們去比較$data[index]$與$number$的關係: * **Three cases**: * Case 1:$number = data[index]$ * Case 2:$number > data[index]$ * Case 3:$number < data[index]$ 若為Case 1,那就成功找到該數了,若為Case 2,則將剛剛的$left$改為$index+1$,而後繼續使用內插法算出新的$index$直到找到數字為止。若為Case 3,則將剛剛的$right$改為$index-1$,而後繼續使用內插法算出新的$index$直到找到數字為止。 #### 舉例說明: 假設要找的數字為$84$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- |:---:| --- | --- | --- | --- | --- | --- | | 13 | 21 | 34 | 55 | 69 | 73 | 84 | 101 | 透過內插法,可以得知$index:$ $index=0+\frac{84-13}{101-13}(7-0)=5.6 \sim 5$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- |:---:| --- | --- | --- | ----------------- | --- |:---:| | 13 | 21 | 34 | 55 | 69 | $\color{red}{73}$ | 84 | 101 | 由於$73$小於被搜索的數,因此將對後面的資料進行同樣的搜索,即將$left$改為剛剛算出來的$5+1$。 因此新的$index:$ $index=6+\frac{84-84}{101-73}(7-5)=6$ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- |:---:| --- | --- | --- | --- | ----------------- |:---:| | 13 | 21 | 34 | 55 | 69 | 73 | $\color{red}{84}$ | 101 | 成功找到該被搜尋數了! #### 時間複雜度: Note:假設有陣列中有$n$個元素 * Best Case:$O(1)$,透過內插法直接找到正確的索引值(資料為線性分布) * Worst Case:$O(n)$,近似線的公式與實際上誤差很大,每次都剛好只有排除一個元素 #### C code: ```c= #include <stdio.h> #define SIZE 8 int interpolation_search(int *, int); int main(){ int array[SIZE]={13,21,34,55,69,73,84,101}; int index=interpolation_search(array,84); printf("%d\n",index); return 0; } int interpolation_search(int *array, int number){ int left=0; int right=SIZE-1; while(number >= array[left] && number <= array[right] && left <= right){ int index; //interpolation to find the index index=left+(number-array[left])/(array[right]-array[left])*(right-left); //when left meet right if(left==right){ if(array[left]==number) return left; else return -1; } //if the number appear at the index if(number==array[index]) return index; //if the number is greater than array[index] else if(number > array[index]) left=index+1; //if the number is smaller than array[index] else right=index-1; } //the element isn't in the array return -1; } ``` code:https://github.com/coherent17/search-algorithm/blob/main/search/interpolation_search.c