# 基礎演算法 線性搜尋, 二元搜尋, 選擇排序, 泡沫排序, 合併排序
###### tags: `Algorithm`
## 線性搜尋法(Linear Search)
假設我們有一個含有n個元素的陣列(沒有經過排序),我們並不知道每一個元素具體的數值為和,假設我們想要在這個陣列當中找到特定的數值,最簡單的方法便是由左到右找一次,直到找到我們想要找到的數值為止(因為我們的陣列未經排序)。
而這樣的行為,使用虛擬碼(pseudocode)來表示,可以寫成
```c=
For i from 0 to n–1
If number behind i'th door
Return true
Return false
```
> 1.我們對每一個元素(視為房間),將每一間房間都標記上房號,從0到n - 1號,然後依序確認
> 2.我們在for迴圈中依序把每一間房間都檢視過一遍,如果有找到,則回傳ture,沒有則跳出for,回傳false
> 3.而這個演算法的時間複雜度為O(N)(worst case),而他的下界會是Ω(1)(best case)
> 你可能要找的數字在最後一號(worst case),也有可能在第一個位置(best case)
如果我們已經知道這一陣列已經經過排序,則我們可以從陣列的最中間開始找起,可以獲得更高的效率。
程式碼實作
```c=
int main(void) {
int array[10] = {0,1,2,3,4,5,6,7,8,9};
int size = sizeof(array) / sizeof(int);
int target = 0;
scanf("%d", &target);
printf("target = %d", Linear_search(array, size, target));
}
int Linear_search(int *array, int size, int target)
{
for(int i = 0; i < size; i++)
{
if(array[i] == target)
{
return array[i];
}
}
return -1;
}
```
## 二元搜尋法(Binary Search)
如果給定的陣列是已經經過排序(由小到大),則我們可以使用比Linear Search還要更高效率的方法,Binary Search,找到陣列中的中間元素,如果我們要找的元素比中間元素小,則向左邊找。如果我們要找的元素比中間元素大,則向右邊找。如果一開始就沒有任一扇門存在,則回傳false。
而這樣的行為,使用虛擬碼(pseudocode)來表示,可以寫成
```c=
If no doors
Return false
If number behind middle door
Return true
Else if number < middle door
Search left half
Else if number > middle door
Search right half
```
> 1.時間複雜度O(log(n))(worse case),Ω(1)(best case)
> 2.假設現在有64個元素,我們可以注意到,使用Binary Search所需的步驟數遠少於Linary Search
> Binary Search : 64 -> 32 -> 16 -> 8 -> 4 -> 2 -> 1
> Linary Search : 64 -> 63 -> 62 -> 61 ....
程式碼實作
```c=
int main(void) {
int array[10] = {0,1,2,3,4,5,6,7,8,9};
int size = sizeof(array) / sizeof(int);
int target = 0;
scanf("%d", &target);
printf("size = %d\n", size);
printf("target = %d", Binary_search(array, size, target));
}
int Binary_search(int *array, int size, int target)
{
int max = size - 1;
int min = 0;
while(max >= min)
{
int mid = (min + max) / 2;
if(array[mid] == target)
{
return array[mid];
}
else if(array[mid] < target)
{
min = mid + 1;
}
else
{
max = mid - 1;
}
}
return -1;
}
```
## 選擇排序法(Selection Sort)
在上面的例子中,如果我們要加快搜尋陣列中的某一個元素,我們會使用Binary Search來取代Linear Search,但是,使用Binary Search的條件為陣列中的元素必須是經過排序的,所以,如果我們想要加快搜尋陣列中某一個元素的速度,我們必須要先將陣列進行排序後,以便我們搜尋陣列中的元素,這裡將介紹選擇排序法(Selection Sort)。
如果我們現在有一組陣列[4,2,7,1,6,3,5],對其進行選擇排序法(Selection Sort)如下
> 首先,我們先選出陣列中最小的元素,從0號看到n - 1號,我們可以找到1,我們將1從陣列中取出
> [4,2,7,X,6,3,5] 手上:1
> 由於1是目前的最小值,我們必須將它放在0號的位置,以符合由小到大的排序方法,因此,我們將在0號的元素4,給取出
> [X,2,7,X,6,3,5] 手上:4,1
> 將1放到原先4所在的位置
> [1,2,7,X,6,3,5] 手上:4
> 將4放回原本1所在的位置
> [1,2,7,4,6,3,5]
>
> 再次進行排序,第一個數字已經排序過,故我們從1號看到n - 1號,找出陣列中最小的元素,元素為2,並取出
> [1,X,7,4,6,3,5] 手上:2
> 將2放到目前最左邊的位置以符合由小排到大的要求
> [1,2,7,4,6,3,5]
>
> 再次進行排序,第一個數字和第二個數字已經經過排序,故我們從2號看到n - 1號,找出陣列中最小的元素,找到元素為3,並取出
> [1,2,7,4,6,X,5] 手上: 3
> 由於要求為由小到大進行排序,我們將7號元素取出
> [1,2,X,4,6,X,5] 手上: 3,7
> 將3放入原先7的位置
> [1,2,3,4,6,X,5] 手上: 7
> 將7放回原本3所在的位置
> [1,2,3,4,6,7,5]
>
> 以此類推已完成排序
[視覺化選擇排序法](https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html)
而這樣的行為,使用虛擬碼(pseudocode)來表示,可以寫成
```c=
For i from 0 to n–1
Find smallest item between i'th item and last item
Swap smallest item with i'th item
```
> 1.使用for迴圈找到0號到n - 1號陣列中,最小的元素
> 2.找到最小元素後,和第i號元素進行交換,如上方演示所示
> 3.在這個演算法中,我們在n個元素中找出最小元素,並進行了n次,時間複雜度大約為O(n^2^),而Ω(n^2^),可見在最理想的情況下,我們仍然需要N^2^
> 證明:
> 我們查詢所有元素n,然後n - 1,然後n - 2....
> n + (n - 1) + (n - 2) ...
> -> n(n + 1) / 2
> -> (n^2 + n) / 2
> 在n趨近於無限大的情況下,該數值逼近於n^2
> 故時間複雜度為O(N^2)
程式碼實作
```c=
int main(void) {
int array[7] = {4,2,7,1,6,3,5};
int size = sizeof(array) / sizeof(int);
Selection_sort(array, size);
show(array, size);
return 0;
}
void Selection_sort(int *array, int size)
{
int min_id = 0;
for(int i = 0; i < size - 1; i++)
{
min_id = i;
for(int j = i + 1; j < size; j++)
{
if(array[j] < array[min_id])
{
min_id = j;
}
}
swap(&array[min_id], &array[i]);
}
}
void show(int *array, int size)
{
for(int i = 0; i < size; i++)
{
printf("%d ", array[i]);
}
}
void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
```
## 泡沫排序法(Bubble sort)
我們將介紹另外一種排序法,被稱為泡沫排序法(Bubble Sort)
如果我們現在有一組陣列[4,2,7,1,6,3,5],對其進行泡沫排序法(Bubble Sort)如下
> 挑選第0號元素4,4比2大,故兩者交換 [2,4,7,1,6,3,5]
> 挑選第1號元素4,4比7小,故不交換 [2,4,7,1,6,3,5]
> 挑選第2號元素7,7比1大,故交換 [2,4,1,7,6,3,5]
> 挑選第3號元素7,7比6大,故交換 [2,4,1,6,7,3,5]
> 挑選第4號元素7,7比3大,故交換 [2,4,1,6,3,7,5]
> 挑選第5號元素7,7比5大,故交換 [2,4,1,6,3,5,7]
> 經過第一次排序後,我們已經將最大的元素放到最末端
> 剩下以此類推後,即可完成排序
[視覺化排序法](https://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html)
而這樣的行為,使用虛擬碼(pseudocode)來表示,可以寫成
```
Repeat until sorted
For i from 0 to n–2
If i'th and i+1'th elements out of order
Swap them
```
> 1.由於我們比較的為第i個元素和第i + 1個元素,因此我們只需要找0到n - 2號
> 2.在每一次排序,最大的數字都會被移動到最右方。
> 3.時間複雜度O(N^2^),Ω(N),在Best case,看過整個陣列是否需要swaps即可。
程式碼實作
```c=
#define swap(x, y) {int temp; temp = x; x = y; y = temp;}
void Bubble_sort(int *, int);
void show(int *, int );
int main(void) {
int array[7] = {4,2,7,1,6,3,5};
int size = sizeof(array) / sizeof(int);
Bubble_sort(array, size);
show(array, size);
return 0;
}
void Bubble_sort(int *array, int size)
{
for(int i = 0; i < size - 1; i++)
{
for(int j = 0; j < size - i - 1; j++)
{
if(array[j] > array[j + 1])
{
swap(array[j], array[j + 1]);
}
}
}
}
void show(int *array, int size)
{
for(int i = 0; i < size; i++)
{
printf("%d ", array[i]);
}
}
```
## 合併排序法(Merge sort)
如果有兩筆或以上的資料,而這些資料位於不同的陣列或是檔案中,我們要如何為它們進行排序?
這時我們可以使用Merge sort,其原理為將兩個**已經排好序**的陣列,將兩者合併後進行排序。
但如果兩個陣列本身沒有經過排序,是否還能使用合併排序法呢? 答案是肯定的,我們知道對長度越短的陣列進行排序,所花費的時間越少,我們可以將想要排序的陣列,不斷的拆分為兩等分,直到剩下一個元素,再來進行合併排序。

而這樣的策略,又被稱為分治法(Divide-and-Conquer)。
Divide:將原問題猜分若干個子問題
Conquer:使用遞迴的方式解決子問題,當子問題足夠簡單時則能夠直接解
Combine:將子問題的解不斷合併,得到原問題的解
而Merge Sort即是Divide-and-Conquer的應用之一,而進行的步驟如下
1.先將目前的array拆分成兩個array
2.將兩個sub array進行Merge sort
3.直到sub array只有一個元素,即將兩個sub array合併成新的sub array
而這樣的行為,使用虛擬碼(pseudocode)來表示,可以寫成
```c=
If only one number
Return
Else
Sort left half of number
Sort right half of number
Merge sorted halves
```
假設我們要使用Merge sort排序array:[6 3 8 5 2 7 4 1]
1.拆分array成sub array
```
6 3 8 5 | 2 7 4 1
```
2.將sub array拆分為sub array
```
6 3 | 8 5
```
3.將sub array拆分為sub array
```
6 | 3
```
4.現在兩個sub array都只剩下一個元素,表示他們已經完成排序,這時候我們將他們進行merge,並生成新的sub array
```
_ _ 8 5 2 7 4 1
3 6
```
5.將2.的sub array拆分為sub array
```
8 | 5
```
6.現在兩個sub array都只剩下一個元素,表示他們已經完成排序,這時候我們將他們進行merge,並生成新的sub array
```
_ _ _ _ 2 7 4 1
3 6 | 5 8
```
7.sub array[3, 6]和sub array[5, 8]皆已完成排序,這時候我們將他們進行merge,並升成新的sub array
```
_ _ _ _ _ _ _ _
_ _ _ _ 2 7 1 4
3 5 6 8
```
8.對1.產生右邊的sub array也徒同上面的步驟進行排序,我們會得到兩個已經經過排序的sub array
```
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
3 5 6 8 | 1 2 4 7
```
9.將兩個sub array進行merge,完成整個array的sorted
```
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
_ _ _ _ _ _ _ _
1 2 3 4 5 6 7 8
```
圖解如下:

程式碼實作
```c=
#include <stdio.h>
#include <stdlib.h>
void merge(int arr[], int head, int mid, int tail);
void merge_sort(int arr[], int head, int tail);
void input_list(int arr[], int);
void show_list(int arr[], int count);
// Merge two subarrays of A[].
// First subarray is arr[head..mid]
// Second subarray is arr[mid+1..tail]
int main()
{
int count;
scanf("%d", &count); //讀入陣列的大小
int *list = (int *)malloc(sizeof(int) * count);
printf("Numbers to be sorted: ");
input_list(list, count);
show_list(list, count);
printf("\n");
merge_sort(list, 0, count - 1);
printf("Numbers Sorted: ");
show_list(list, count);
return 0;
}
void merge(int arr[], int head, int mid, int tail)
{
int lenA = mid - head + 1;
int lenB = tail - (mid + 1) + 1;
int *A = (int*)malloc(sizeof(int) * lenA); //First sub array
int *B = (int*)malloc(sizeof(int) * lenB); //Second sub array
//Copy data to temp arrays A[] and B[]
int i, j, k;
for (i = 0; i < lenA; i++)
{
A[i] = arr[head + i]; //複製資料到左鎮列
}
for (j = 0; j < lenB; j++)
{
B[j] = arr[mid + 1 + j]; //複製資料到右陣列
}
// Merge two temp arrays back into arr[head..tail]
i = 0;
j = 0;
k = head;
//while array A and B haven't finished scanning
while (i < lenA && j < lenB)
{
if (A[i] < B[j])
{
arr[k] = A[i];
i++;
}
else
{
arr[k] = B[j];
j++;
}
k++;
}
//Copy the remaing elements into arr[], if A[] haven't finished scanning
while (i < lenA)
{
arr[k] = A[i];
i++;
k++;
}
//Copy the remaing elements into arr[], if B[] haven't finished scanning
while (j < lenB)
{
arr[k] = B[j];
j++;
k++;
}
}
void merge_sort(int arr[], int head, int tail)
{
if (head < tail)
{
int mid = (head + tail) / 2;
merge_sort(arr, head, mid);
merge_sort(arr, mid + 1, tail);
merge(arr, head, mid, tail);
}
}
void show_list(int arr[], int count)
{
for (int i = 0; i < count; i++)
{
printf("%d ", arr[i]);
}
}
void input_list(int arr[], int count)
{
for (int i = 0; i < count; i++)
{
scanf("%d", &arr[i]);
}
}
```
程式碼行為:
假設Input 為[6 3 2]
> merge_sort(arr, 0, 2)
> if(head(0) < tail(2))成立
> mid = (2 + 0) / 2 = 1
> 呼叫merge_sort(arr, 0, 1)
>> if(head(0) < tail(1))成立
>> mid = (0 + 1) / 2 = 0
>> 呼叫merge_sort(arr, 0, 0)
>>> if(head(0) < tail(0))不成立
>>>
>> 呼叫merge_sort(arr, 1, 1)
>>
>>> if(head(1) < tail(1))不成立
>>>
>> 呼叫merge(arr, 0, 0, 1)
>>
>>> lenA = mid(0) + head(0) + 1 = 1, lenB = tail(1) - (mid(0) + 1) + 1 = 1
>>> 分別將資料複製到左陣列A和右陣列B
>>> A[] = {6}, B[] = {3}
>>> while(i(0) < lenA(1) && j(0) < lenB(1))
>>>> if(A[0] (6) < B[0] (3))不成立
>>>> else arr[0] = B[0], arr[] = {3,3,2}
>>>>
>>> k++ = 1,表示arr已寫入元素,j++表示B陣列元素已寫入arr
>>> while(i(0) < lenA(1) && j(1) < lenB(1))不成立
>>> 將還沒寫入arr的元素寫入到arr中
>>> while(i(0) < lenA(1))成立
>>>> arr[1] = A[0], arr[] = {3, 6, 2}
>>>>
>>> while(j(1) < lenB(1))不成立
>>>
>> merge_sort(arr, 2, 2)
>>> if(head(2) < tail(2))不成立
>>>
>> merge(arr, 0, 1, 2)
>>> lenA = mid(1) + head(0) + 1 = 2, lenB = tail(2) - (mid(1) + 1) + 1 = 1
>>> 別將資料複製到左陣列A[]和和右陣列B[]
>>> A[] = {3,6}, B[] = {2}
>>> while(i(0) < lenA(2) && j(0) < lenB(1))
>>>> if(A[0] (3) < B[0] (2))不成立
>>>> else arr[0] = B[0], arr[] = {2,3,6}, k++ = 1, j++ = 1
>>>>
>>> while(i(0) < lenA(2) && j(1) < lenB(1))不成立
>>> 將還沒寫入arr的元素寫入arr中
>>> while(i(0) < lenA(2))
>>>> arr[1] = A[0], arr[] = {2,3,6}
>>>>
>>> while(i(1) < lenA(2))
>>>> arr[2] = A[1], arr[] = {2,3,6}
>>>>
>>> while(i(2) < lenA(2))不成立
>>> while(j(1) < lenB(1))不成立
>>>
>> 到這裡arr已經完成排序
參考資料: 大話數據結構,C语言程序设计现代方法第2版,IT Home,圖片源於網路