本次所統整的排序演算法只有內部排序法,沒有外部排序法。 ###### 一個內建sort砸下去這小節的用途就小好多。 :::info :::spoiler 資料在經過排序後有什麼好處? 1.容易閱讀 2.利於統計及整理 3.減少搜尋的時間 ::: ### 排序方式 > **直接排序:** <font color="#D83507">直接交換</font>儲存資料的位置。(耗費較多時間進行資料的更動) > **邏輯移動:** <font color="#D83507">改變</font>指向資料的<font color="#D83507">輔助指標的值</font>。 ### 使用的記憶體種類 >**內部排序:** 資料量小,<font color="#D83507">可在記憶體內排序。</font> >**外部排序:** 資料量大到無法在記憶體內排序,<font color="#D83507">用到輔助記憶體</font>。(ex.硬碟) ### 穩定性 > **穩定的排序:** 在排序後,兩相同鍵值保持原本的次序。 > **不穩定的排序** 在排序後,兩相同鍵值<font color="#D83507">不一定</font>保持原本的次序。 :::success ::: spoiler 舉例 排序前: 3~左~ 6 2 8 3~右~ 穩定的排序: 2 3~左~ 3~右~ 6 8 不穩定的排序: 2 3~右~ 3~左~ 6 8 ::: ### 時間複雜度 進行排序的執行時間我們叫他**時間複雜度**,其中又分為最好情況、最壞情況和平均情況。 (正文所出現的皆為平均情況。) > **最好情況:** 資料已排序完成。 > **最懷情況:** 每一鍵值皆須重新排列。 > **平均情況:** 輸入的資料以等概率出現。(就是取平均) :::success ::: spoiler 舉例 最好情況: > 排序前: 2 4 6 8 9 > 排序後: 2 4 6 8 9 最壞情況: > 排序前: 9 8 6 4 2 > 排序後: 2 4 6 8 9 ::: ### 空間複雜度 進行排序所需的空間我們叫他**空間複雜度**,<font color="#D83507">任何排序法都有資料對調的動作,所以必定會用到一個額外空間</font>,若該排序法必須藉由遞迴的方式進行,其所使用到的堆疊則是該排序法須付出的額外空間。 --- # 泡沫排序法(bubble sort) 由第一個元素開始,比較相鄰元素的大小,若大小有誤則交換,以此類推直至排序完成。 ### <font color="#2FB428">分析</font> $1.$ 穩定的排序法。 $2.$ 時間複雜度為 $O(n^2)$。 $3.$ 空間複雜度為 $O(n)$。 $4.$ 適用資料量小或有部分排序的資料。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) **第一輪之第一次交換(前者大於後者,交換)** ![image](https://hackmd.io/_uploads/HkQ_zfgwA.png) ![image](https://hackmd.io/_uploads/HJpqffxD0.png) **第一輪之第二次交換(後者大於前者,不交換)** ![image](https://hackmd.io/_uploads/BkplmGevR.png) ![image](https://hackmd.io/_uploads/SJ7M7fxPA.png) **第一輪之第三次交換(前者大於後者,交換)** ![image](https://hackmd.io/_uploads/Sy78mflDR.png) ![image](https://hackmd.io/_uploads/S1TvQMxwR.png) **第一輪之第四次交換(後者大於前者,不交換)** ![image](https://hackmd.io/_uploads/SJKYXMxwA.png) ![image](https://hackmd.io/_uploads/B175XMlPA.png) **以此類推重複至迴圈結束** ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std ; int main () { int a ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i ++) { //輸入資料 cin >> arr[i] ; } for (int i = 0 ; i < a - 1 ; i ++) { //泡沫排序法 for (int j = 0 ; j < a - i - 1 ; j ++) { if(arr[j] > arr[j+1]) { int tmp ; tmp = arr[j] ; arr[j] = arr[j+1] ; arr[j+1] = tmp ; } } } cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i ++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 插入排序法(insert sort) 將陣列中的元素與與排序好的資料做比較,將元素插入適當的位置(已排序好的資料中),以此類推直至排序完成。 ### <font color="#2FB428">分析</font> $1.$ 穩定的排序法。 $2.$ 時間複雜度為 $O(n^2)$。 $3.$ 空間複雜度為 $O(n)$。 $4.$ 適用資料量小或有部分排序的資料。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) **原始資料:** ![image](https://hackmd.io/_uploads/rJLeGGeDA.png) **第一次交換(將第一個元素放入陣列中的第一個位置)** ![image](https://hackmd.io/_uploads/rkwQzHxP0.png) ![image](https://hackmd.io/_uploads/BkhYMHxvC.png) **第二次交換(將第二個元素與陣列中的元素比較後插入)** ![image](https://hackmd.io/_uploads/B1sVmBxD0.png) ![image](https://hackmd.io/_uploads/SJwBmrePR.png) **第三次交換(將第三個元素與陣列中的元素比較後插入)** ![image](https://hackmd.io/_uploads/SypU4HeD0.png) ![image](https://hackmd.io/_uploads/Hk0wErxDC.png) **第四次交換(將第四個元素與陣列中的元素比較後插入)** ![image](https://hackmd.io/_uploads/ryq-HBlDA.png) ![image](https://hackmd.io/_uploads/r1lXSHgDA.png) **第五次交換(將第五個元素與陣列中的元素比較後插入)** ![image](https://hackmd.io/_uploads/Sk_d8rlv0.png) ![image](https://hackmd.io/_uploads/BJItUHeDA.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std ; int main () { int a , j ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i ++) { //輸入資料 cin >> arr[i] ; } for (int i = 1 ; i < a ; i ++) { //插入排序法 int tmp ; tmp = arr[i] ; j = i - 1 ; while (j >= 0 && arr[j] > tmp) { arr[j+1] = arr[j] ; j-- ; } arr[j+1] = tmp ; } cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i ++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 選擇排序法(selection sort) 有兩種方式,**由大至小**和**由小到大**。以由大至小為例,最小值放至第一個位置,第二小值放至第二個位子,以此類推直至排序完成。 ### <font color="#2FB428">分析</font> $1.$ 不穩定的排序法。 $2.$ 時間複雜度為 $O(n^2)$。 $3.$ 空間複雜度為 $O(n)$。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) ![image](https://hackmd.io/_uploads/rJLeGGeDA.png) **第一次交換(找到最小值與第一個元素交換)** ![image](https://hackmd.io/_uploads/ryopTMxvA.png) ![image](https://hackmd.io/_uploads/B1pZRflw0.png) **第二次交換(找到第二小值與第二個元素交換)** ![image](https://hackmd.io/_uploads/r1GcCzev0.png) ![image](https://hackmd.io/_uploads/Sky6Rzgv0.png) **第三次交換(找到第三小值與第三個元素交換)** ![image](https://hackmd.io/_uploads/ByJqkXgPC.png) ![image](https://hackmd.io/_uploads/HyOjJmlDA.png) **第四次交換(找到第三小值與第三個元素交換(本次大小相同,不交換))** ![image](https://hackmd.io/_uploads/ryQ7lmgwC.png) ![image](https://hackmd.io/_uploads/Skt-gmeP0.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std ; int main () { int a ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i ++) { //輸入資料 cin >> arr[i] ; } for (int i = 0 ; i < a ; i ++) { //選擇排序法 for (int j = i+1 ; j < a ; j ++) { if(arr[i] > arr[j]) { int tmp ; tmp = arr[i] ; arr[i] = arr[j] ; arr[j] = tmp ; } } } cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for(int i = 1 ; i < a ; i ++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 謝耳排序法(shell sort) 分成特定的小區塊,以插入排序法排完區塊內的資料漸漸減少間隔的距離。 ### <font color="#2FB428">分析</font> $1.$ 穩定的排序法。 $2.$ 時間複雜度為 $O(n^{3/2})$。(以這題為例,最好的情況為 $O(n \;log^2 \; n) 。$ $3.$ 空間複雜度為 $O(n)$。 $4.$ 適用資料量小或有部分排序的資料。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) ![image](https://hackmd.io/_uploads/H1_NcG-vA.png) **第一次交換(將該資料的長度($6$)除以$2$(不必定為$2$),$6/2$得$3$,便是將他隔成3區。相同區塊做比較後交換,即顏色所示,其中綠的後者大於前者,不交換)** ![image](https://hackmd.io/_uploads/Hy9F9f-PR.png) ![image](https://hackmd.io/_uploads/B1AcozWDC.png) **第二次交換(將區塊($3$)除以$2$(不必定為$2$),$3/2$得$1$,便是將他隔成1區(在這等於直接做插入排序法),相同區塊做比較後交換,即顏色所示)** ![image](https://hackmd.io/_uploads/B1anuXZwC.png) ![image](https://hackmd.io/_uploads/rJFkKQbvC.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std ; int main () { int a , j , shell ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i ++) { //輸入資料 cin >> arr[i] ; } shell = a / 2 ; //謝耳排序法 while (shell != 0) { for (int i = shell ; i < a ; i ++) { //裡頭的插入排序法 int tmp ; tmp = arr[i] ; j = i - shell ; while (j >= 0 && arr[j] > tmp) { arr[j+shell] = arr[j] ; j = j - shell ; } arr[j+shell] = tmp ; } shell = shell / 2 ; } cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i ++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 快速排序法(quick sort) 目前公認最佳的排序法,使用切割征服法,自行設定一個中間值,以此中間值將所有資料分成兩部分,小於中間值置於左側,大於則相反,在以相同方式處理兩邊資料,以此類推直至排序完成。(於我看來,和二分搜的概念類似) ### <font color="#2FB428">分析</font> $1.$ 不穩定的排序法。 $2.$ 時間複雜度為 $O(n~log_2~n)$。 $3.$ 空間複雜度最差為 $O(n)$ ,最佳為 $(log_2~n)$。 $4.$ 平均執行時間最快。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) **原始資料:** ![image](https://hackmd.io/_uploads/rJLeGGeDA.png) **設一鍵值(通常取第一位),小丟左大丟右:** ![image](https://hackmd.io/_uploads/ryOvwLbwA.png) ![image](https://hackmd.io/_uploads/B1J4uLZvR.png) **再來在五的左側設一鍵值(通常取第一位),小丟左大丟右,左半排序完成(若在這取的鍵值不為0或1還要繼續):** ![image](https://hackmd.io/_uploads/SkPOuIZvA.png) ![image](https://hackmd.io/_uploads/BJs9dI-w0.png) **再來在五的右側設一鍵值(通常取第一位),小丟左大丟右,右半排序完成(若在這取的鍵值不為0或1還要繼續):** ![image](https://hackmd.io/_uploads/SyGGKIbPR.png) ![image](https://hackmd.io/_uploads/BJ7Ut8ZwC.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std ; int partition(int arr[], int low, int high); void quickSort(int arr[], int low, int high) { //快速排序法 if (low < high) { int pivotIndex = partition(arr, low, high); quickSort(arr, low, pivotIndex - 1); quickSort(arr, pivotIndex + 1, high); } } int partition(int arr[], int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j <= high - 1; j++) { if (arr[j] <= pivot) { i++; swap(arr[i], arr[j]); } } int tmp ; tmp = arr[i+1] ; arr[i+1] = arr[high] ; arr[high] = tmp ; return i + 1; } int main() { int a; cin >> a; int arr[a]; for (int i = 0; i < a; i++) { //輸入資料 cin >> arr[i]; } quickSort(arr, 0, a - 1); cout << arr[0]; //輸出資料(配合例題故以此方式輸出) for (int i = 1; i < a; i++) { cout << ' ' << arr[i]; } cout << '\n'; return 0; } ``` ::: --- # 合併排序法(merge sort) 針對已排序好的兩個或兩個以上的檔案由合併的方式,組成一個排序好的檔案。 #### <font color="#2FB428">分析</font> $1.$ 穩定的排序法。 $2.$ 時間複雜度為 $O(n~log~n)$。 $3.$ 空間複雜度為 $O(n)$。 $4.$ 適用資料量大的外部檔案。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) **原始資料:** ![image](https://hackmd.io/_uploads/rJLeGGeDA.png) **先拆分:** ![image](https://hackmd.io/_uploads/SkyjKD5D0.png) ![image](https://hackmd.io/_uploads/B1Y0YDqvR.png) ![image](https://hackmd.io/_uploads/SJ_O9w9PA.png) **會得到:** 再來進行合併的動作。 ![image](https://hackmd.io/_uploads/SkXf64WvC.png) **後合併(合併的資料做排序):** ![image](https://hackmd.io/_uploads/S1HeovcPC.png) ![image](https://hackmd.io/_uploads/r1VBiPcw0.png) ![image](https://hackmd.io/_uploads/ryaOjDcDC.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include<iostream> using namespace std; void merge(int arr[], int left, int middle, int right) { int n1 = middle - left + 1; int n2 = right - middle; int leftArr[n1], rightArr[n2]; for (int i = 0; i < n1 ; i++) { leftArr[i] = arr[left + i]; } for (int j = 0; j < n2; j++) { rightArr[j] = arr[middle + 1 + j]; } int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (leftArr[i] <= rightArr[j]) { arr[k] = leftArr[i]; i++; } else { arr[k] = rightArr[j]; j++; } k++; } while (i < n1) { arr[k] = leftArr[i]; i++; k++; } while (j < n2) { arr[k] = rightArr[j]; j++; k++; } } void mergeSort(int arr[], int left, int right) { //合併排序法 if (left < right) { int middle = left + (right - left) / 2; mergeSort(arr, left, middle); mergeSort(arr, middle + 1, right); merge(arr, left, middle, right); } } int main() { int a; cin >> a; int arr[a]; for (int i = 0; i < a; i++) { //輸入資料 cin >> arr[i]; } mergeSort(arr, 0, a - 1); cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 堆積排序法(heap sort) 選擇排序法的改版,減少其的比較次數,利用到二元樹(後續的課程)的堆積樹。 ### <font color="#2FB428">分析</font> $1.$ 不穩定的排序法。 $2.$ 時間複雜度為 $O(n~log_2~n)$。 $3.$ 空間複雜度為 $O(n)$。 ### <font color="#2FB428">圖例</font> 在進行堆積排序法的圖例前要先介紹什麼是堆積樹,不然不好解釋堆積排序法是怎麼進行的。 ::: info ::: spoiler 堆積樹 (本題以最大堆積樹舉例) **同樣以5 2 7 4 9 來解釋** ![image](https://hackmd.io/_uploads/SJcY2CQwR.png) **arr [0] 跟arr [1] 比較,前者大於後者,不交換** ![image](https://hackmd.io/_uploads/SJcY2CQwR.png) **arr [0] 跟arr [2] 比較,後者大於前者,交換** ![image](https://hackmd.io/_uploads/S1xc60mvR.png) **arr [1] 跟arr [3] 比較,後者大於前者,交換。 此時的arr [1] 再跟 arr[0] 做比較,後者大於前者,不交換** ![image](https://hackmd.io/_uploads/H1Qx00mvR.png) **arr [1] 跟arr [4] 比較,後者大於前者,交換。 此時的arr [1] 再跟 arr[0] 做比較,前者大於後者,交換** (第一步) ![image](https://hackmd.io/_uploads/HycQCC7vC.png) (第二步) ![image](https://hackmd.io/_uploads/HkPmyJ4DC.png) **便可得到該陣列中最大的元素,提出,刪掉樹根,重複直至排序結束。** ::: :::warning :::spoiler 點我打開 (太長了,建議看完收起) **原始資料:** ![image](https://hackmd.io/_uploads/rJLeGGeDA.png) **第一次建立堆積樹** ![image](https://hackmd.io/_uploads/H1sFg1EDR.png) **第二次建立堆積樹** ![image](https://hackmd.io/_uploads/BkUsl1NwR.png) **第三次建立堆積樹** ![image](https://hackmd.io/_uploads/ryK6l1EDR.png) **第四次建立堆積樹** ![image](https://hackmd.io/_uploads/B1pr-yVPA.png) **第五次建立堆積樹** ![image](https://hackmd.io/_uploads/HyYvZJVvR.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= #include <iostream> using namespace std; void heap(int arr[], int n, int i) { int largest = i; int left = 2 * i + 1; int right = 2 * i + 2; if (left < n && arr[left] > arr[largest]) { largest = left; } if (right < n && arr[right] > arr[largest]) { largest = right; } if (largest != i) { swap(arr[i], arr[largest]); heap(arr, n, largest); } } void heapSort(int arr[], int n) { //堆積排序法 for (int i = n / 2 - 1; i >= 0; i--) { heap(arr, n, i); } for (int i = n - 1; i > 0; i--) { swap(arr[0], arr[i]); heap(arr, i, 0); } } int main() { int a ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i++) { //輸入資料 cin >> arr[i] ; } heapSort(arr, a-1); cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: --- # 基數排序法(radix sort) 以分配模式排序,有 最高有效鍵值開始排序(MSD) 和 從最低有效鍵值開始排序(LSD)。 換句話說基數排序法不同於其他類型的排序法,他排序的方法利用的了數學的概念,**數字的大小和其位數有關**,利用這一點來進行排序。 ### <font color="#2FB428">分析</font> $1.$ 穩定的排序法。 $2.$ 時間複雜度為 $O(n \; log_b \; k)$,$b$ 為底數。 $3.$ 空間複雜度為 $O(n+k)$。 ### <font color="#2FB428">圖例</font> :::warning :::spoiler 點我打開 (太長了,建議看完收起) **原始資料:(為更詳細介紹,故將資料增加不同位數)** ![image](https://hackmd.io/_uploads/BJql5ZNP0.png) **第一次排序:(排個位數)** ![image](https://hackmd.io/_uploads/rkFh5-VwC.png) **第二次排序:(排十位數)** ![image](https://hackmd.io/_uploads/r18mjbVv0.png) **第三次排序:(排百位數,若無則當0)** ![image](https://hackmd.io/_uploads/ry_qsZEvA.png) **第四次排序:(排千位數,若無則當0)** ![image](https://hackmd.io/_uploads/ry_qsZEvA.png) ::: ### <font color="#2FB428">程式碼(配合例題)</font> :::danger :::spoiler 點我打開 (太長了,建議看完收起) ```cpp= int getMax(int arr[], int n) { int max = arr[0]; for (int i = 1; i < n;i++) { if (arr[i] > max) { max = arr[i]; } } return max; } void countSort(int arr[], int n, int exp) { int output[n]; int count[10] = {0}; for (int i = 0; i < n; i++) { count[(arr[i] / exp) % 10]++; } for (int i = 1; i < 10; i++) { count[i] += count[i - 1]; } for (int i = n - 1; i >= 0; i--) { output[count[(arr[i] / exp) % 10] - 1] = arr[i]; count[(arr[i] / exp) % 10]--; } for (int i = 0; i < n; i++) { arr[i] = output[i]; } } void radixSort(int arr[], int n) { //基數排序法 int max = getMax(arr, n); for (int exp = 1; max / exp > 0; exp *= 10) { countSort(arr, n, exp); } } int main() { int a ; cin >> a ; int arr[a] ; for (int i = 0 ; i < a ; i++) { //輸入資料 cin >> arr[i] ; } radixSort(arr, a-1); cout << arr[0] ; //輸出資料(配合例題故以此方式輸出) for (int i = 1 ; i < a ; i++) { cout << ' ' << arr[i] ; } cout << '\n' ; } ``` ::: # 例題 ###### 有什麼題目沒提到或有新增的煩請留言告知。 [a066: Sort! Sort! Sort!](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a066) [a083: Sort yourself 1](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a083) [a084: Sort yourself 2](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a084) [a208: 真 ● 泡沫排序法](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a208) [a248: pI 你那第幾中隊的R 會不會排隊R 亂七八糟 一點紀律都沒有](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a248) [a934: 泡沫排...?](https://dandanjudge.fdhs.tyc.edu.tw/ShowProblem?problemid=a934) ###### 都看到這了難道不按個愛心支持一下嗎?