Sort Algorithm(ASC)
[TOC] //怎么生成目錄,糾結ing
插入排序
每一趟排序都將待排元素插入到已有序的序列中,但不能保證該元素的插入位置是全局有序的。
Insertion Sort
直接插入排序
- 最好情況:$O(n)$
- 最壞情況:$O(n^2)$
- 平均情況:$O(n^2)$
- 輔助空間:O(1)
- 穩(wěn)定性:穩(wěn)定
- 每一趟排序只能保證當前有序,并不能保證全局有序
核心思想
數組中的每一個待排元素與已部分有序的元素依次比較,如果待排元素小于當前已有序的元素,則交換兩者位置,并繼續(xù)向前比較。-
程序示例
public static void insertionSort(int[] arr){ for (int i=1;i<arr.length;i++){ for (int j=i-1;j>=0;j--){ if (arr[j]>arr[j+1]){ swap(arr,j,j+1); } } } } public static void swap(int[] arr,int i,int j){ int tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; }
-
程序分析
直接插入排序采用兩層循環(huán):- 外層循環(huán)用來跟蹤每一個待排元素,從1到 n-1,剛開始arr[0]是默認有序的。
- 內層循環(huán)負責當前待排元素與已部分有序的元素進行逐一比較,確定當前待排元素的最終插入位置。
優(yōu)化分析*
在內層循環(huán)中,每當逆序(已有序元素大于待排元素時)的情況出現時,就執(zhí)行一次 swap 操作,交換兩元素的位置,這樣會導致交換次數較多。可以考慮通過比較確定最后的插入位置,然后將交換操作推遲到最后進行。這樣只需要進行一次交換操作,但已有序元素的移動次數是不會改變的。
Shell Sort
希爾排序(縮小增量排序)
- 步長+直接插入排序
- 最好情況:$O(n)$
- 最壞情況:$O(n^2)$
- 平均情況:$O(n^{1.3})$
- 輔助空間:O(1)
- 穩(wěn)定性:不穩(wěn)定
- 每一趟排序只能保證當前有序,并不能保證全局有序
核心思想
希爾排序,又稱縮小增量排序,使用步長來確定每一次的待排子序列,然后使用直接插入排序對當前待排子序列進行排序。在完成步長為 h 的排序后,通過證明可以保證,對于每一個 i 與 i+h 元素都是有序的。然后縮小步長,再次使用直接插入排序。直到最后步長為1,最后一次序列已經基本有序,最后采用一次直接插入排序后就可以得到最終的有序數組。-
程序示例
public static void shellSort(int[] arr){ //第一層循環(huán)控制步長 for (int gap=arr.length/2;gap>0;gap=gap/2){ //第二層循環(huán)控制滿足步長的所有子序列 for (int i=gap;i<arr.length;i++){ //第三層循環(huán)控制當前滿足步長的子序列的排序,采用直接插入排序的方式進行排序 for (int j=i;j<arr.length;j+=gap){ if (arr[j]<arr[j-gap]){ swap(arr,j,j-gap); } } } } } public static void swap(int[] arr,int i,int j){ int tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; }
-
程序分析
希爾排序采用三層循環(huán):- 第一層循環(huán)負責確定每一趟的步長,并按一定的規(guī)則(這里采用/2折半的規(guī)則),循環(huán)減小步長到1。
- 第二層循環(huán)針對當前步長,依次確定滿足該步長的待排子序列。
- 第三層循環(huán)針對當前確定的待排子序列,采用直接插入排序的方法進行排序。
選擇排序
基本思想:比較+交換
Selection Sort
簡單選擇排序
- 最好情況:$O(n^2)$
- 最壞情況:$O(n^2)$
- 平均情況:$O(n^2)$
- 輔助空間:O(1)
- 穩(wěn)定性:不穩(wěn)定
- 每一趟排序保證全局有序
核心思想
簡單選擇排序,是選擇排序中的一種,遵循其基本思想:比較+交換。即通過依次比較確定當前趟最小元素,并根據情況與當前待排元素交換。具體做法是在每趟排序中,通過將當前待排元素與與剩余所有元素比較,找到最小的元素并與當前元素交換(如果當前待排元素就是最小,則不用交換)。-
程序示例
public static void selectionSort(int[] arr){ for (int i=0;i<arr.length;i++){ //這里采用 min 來標記最小元素的下標,方便 swap 的時候通過下標進行交換 int min=i; //第二層循環(huán)負責將當前元素與剩余所有元素比較,找到這些元素中的最小值,如果該最小值不是當前元素,則在退出循環(huán)后將當前元素與該最小元素交換 //因為每一次尋找當前趟最小元素都是與所有待排元素比較,因此,簡單選擇排序是全局有序的 for (int j=i+1;j<arr.length;j++){ if (arr[j]<arr[min]){ min=j; } } if (min!=i){ swap(arr,i,min); } } } public static void swap(int[] arr,int i,int j){ int tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; }
-
程序分析
簡單選擇排序,采用兩層循環(huán):- 外層循環(huán)用于控制當前待排元素及其待排位置。
- 內存循環(huán)用于基于當前待排位置和待排元素,循環(huán)依次與序列中剩余其它元素比較,如果存在元素小于當前待排元素,則采用一個臨時變量始終標記符合該條件的元素。在退出內層循環(huán)后,判斷經過一趟完整比較后,最小元素是否為當前待排元素,如果是,說明當前待排元素是當前趟的最小值,當前位置即全局有序位置,不交換;如果不是,則采用 swap 交換。
Heap Sort
堆排序
- 最好情況:$O(nlog_2n)$
- 最壞情況:$O(nlog_2n)$
- 平均情況:$O(nlog_2n)$
- 輔助空間:O(1)
- 穩(wěn)定性:不穩(wěn)定
- 每一趟排序保證全局有序
核心思想
堆排序,先根據父節(jié)點與子節(jié)點下標關系(i,2i+1,2i+2)將待排序列構造為大頂堆(實際上還是在數組中存儲)。然后執(zhí)行循環(huán)操作:每次將頂點元素與最后一個元素swap,交換后,當前最大元素已經位于數組的末尾,因為原來的末尾元素到了頂點位置,有可能破壞堆的性質,則通過堆調整來修正堆性質。循環(huán)執(zhí)行這一組操作,直到堆中只有一個元素的時候,該待排序列已然有序。-
程序示例
public static void heapSort(int[] arr){ buildHeap(arr); int length=arr.length; while (length>0){ swap(arr,0,length-1); length--; adjustToHeap(arr,length,0); } } public static void buildHeap(int[] arr){ for (int i=0;i<arr.length/2;i++){ adjustToHeap(arr,arr.length,i); } } public static void adjustToHeap(int[] arr,int length,int index){ int indexOfMax=index; while (true){ int leftIndex=getLeftChild(index); int rightIndex=getRightChild(index); if (leftIndex<length&&arr[leftIndex]>arr[indexOfMax]){ indexOfMax=leftIndex; } if (rightIndex<length&&arr[rightIndex]>arr[indexOfMax]){ indexOfMax=rightIndex; } if (indexOfMax!=index){ swap(arr,index,indexOfMax); index=indexOfMax; continue; } else { break; } } } public static int getLeftChild(int i){ return 2*i+1; } public static int getRightChild(int i){ return 2*i+2; } public static void swap(int[] arr,int i,int j){ int tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; }
-
程序分析
在采用堆排序前,需要對待排序列構造大頂堆。具體構造方式,稍后詳述。- 在生成大頂堆后,滿足大頂堆的性質,第一個元素 arr[0]是當前0~arr.length-1中的最大值。接下來對該大頂堆進行 N-1 次的交換和調整。
- 具體做法是,在每次交換和調整中,將堆頂的最大值與堆尾元素互換,這樣,該最大值就位于正確順序位置上。因為互換后有可能破壞大頂堆順序,因此必須在互換后調用 adjustToHeap 進行堆性質修正。
交換排序
Bubble Sort
冒泡排序
- 最好情況:$O(n^2)$
- 最壞情況:$O(n^2)$
- 平均情況:$O(n^2)$
- 輔助空間:O(1)
- 穩(wěn)定性:穩(wěn)定
- 每一趟排序保證全局有序
核心思想
冒泡排序,在每一趟排序中,都依次比較左右兩個元素,如果不符合大小關系,則交換位置,并繼續(xù)比較下一個坐標位置。在一趟比較結束后,當前趟最大的元素會沉底。-
程序示例
public static void bubbleSort(int[] arr){ //對于 N 個元素的序列,需要 N-1 趟排序 for (int i=1;i<arr.length;i++){ //在每一趟排序后,下標為 arr.length-i的元素已經位于全局有序位置,因此下一趟比較的最后待排位置為 arr.length-i-1 //每一趟比較中,當前下標的左右兩個元素如果順序不當,則互相交換,較大的向后調整,如果滿足大小要求,則不交換。這樣依次對剩余元素進行比較,直到 arr.length-i for (int j=0;j<arr.length-i;j++){ if (arr[j]>arr[j+1]){ swap(arr,j,j+1); } } } } public static void swap(int[] arr,int i,int j){ int tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; }
-
程序分析
冒泡排序采用兩層循環(huán),對于 N 個元素的冒泡排序,需要進行 N-1 趟排序,第 i 趟排序需要比較 N-i 次。- 第一層循環(huán)控制冒泡排序的趟數,從1到 N-1趟冒泡排序。
- 第二層循環(huán)控制每一趟的冒泡排序,因為經過第 i-1 趟的排序,第 N-i+1 個位置已經有序,所以在第 i 趟的冒泡排序中,只需要從第一個元素一直比較到第 N-i 個元素。
優(yōu)化分析
針對最好情況為$O(n)$的情形,可以在外層循環(huán)中設置一個變量用來檢查上一趟是否存在交換操作,在內層循環(huán)中,發(fā)生 swap 操作則置該變量為 true,否則默認為 false。在退出內層循環(huán)后,外層邏輯會判斷如果變量為 true 說明上一趟待排序列不滿足順序條件,繼續(xù)本趟排序;如果為 false 說明在上一趟中待排序列所有元素已經有序。因此在本趟排序可以直接 break,已經提前完成了序列排序。
Quick Sort
快速排序
- 最好情況:$O(nlog_2n)$
- 最壞情況:$O(n^2)$
- 平均情況:$O(nlog_2n)$
- 輔助空間:$O(nlog_2n)$
- 穩(wěn)定性:不穩(wěn)定
- 每一趟排序保證軸 key 全局有序
核心思想
循環(huán)比較交換+分而治之的思想。在每一趟的排序操作中,當 i不等于j 的時候,將大于 key 的元素都交換在 key 的右邊,小于 key 的元素都交換在 key 的左邊。當 i等于j 的時候,就是 key 的最終有序位置。但是經過一趟排序操作,僅保證了當前 key 位于正確有序的位置 i,但對于start~i-1和 i+1~end 是無序的,因此需要在這兩段上遞歸調用剛剛的排序操作。-
程序示例
public static void quickSort(int[] arr,int start,int end){ if (start<end){ int i=start; int j=end; int key=arr[start]; while (i!=j){ //從后向前循環(huán)找到第一個小于 key 的元素 while (i<j&&arr[j]>=key){ j--; } //將該元素替換到 key 的左邊 if (i<j){ arr[i]=arr[j]; i++; } //從前向后循環(huán)找第一個大于 key 的元素 while (i<j&&arr[i]<key){ i++; } //將該元素替換到 key 的右邊 if (i<j){ arr[j]=arr[i]; j--; } } //當 i=j 的時候,說明在這一趟交換過后,所有大于 key 的元素都位于 key 的右邊,所有小于 key 的元素都位于 key 的左邊 //而此時 i==j 即表示 key 應該插入的位置,即原先的 arr[start]元素的最終位置 arr[i]=key; quickSort(arr,start,i-1); quickSort(arr,i+1,end); } }
-
程序分析
快速排序在每一趟排序中,先選定一個軸,通過交換來確定軸的最終位置,在一趟排序后,大于或小于該軸的元素會位于該軸的兩側。- 外層循環(huán)用來控制這趟排序是否結束,當 i 等于 j 的時候,說明待排序列的比較已經結束,該位置即為軸 key 的最終有序位置。
- 在第一個內層循環(huán)中,先從后向前循環(huán)查找第一個小于 key 的元素,如果存在則退出循環(huán)并將該元素交換至 key 的 i 位置,然后再進入第二個內層循環(huán)。如果沒有存在一個小于 key 的元素,說明在從后向前的查找中,key 后面的元素都大于 key,因此,說明 key 就是在正確的有序位置。
- 在第二個內層循環(huán)中,從前向后循環(huán)查找第一個大于 key 的元素,如果存在則退出循環(huán)并交換至當前 j 位置,然后退出該循環(huán),判斷 i 是否等于 j,不等于則說明該趟還沒比較結束,繼續(xù)從第一個內層循環(huán)開始,繼續(xù)排序操作。直至 i 等于 j。
優(yōu)化分析
快速排序對于越無序的序列效果越好,但對于基本有序序列,在選定 key 后,有可能剩余的 N-1個元素均大于或小于 key,導致分而治之的效果不明顯,沒有明顯的均分待排序列,使得效率接近于插入排序$O(n^2)$的效果。改善這樣的情況,可以嘗試采用隨機 key 的方式,每一趟排序,均在當前 start~end 中隨機選取 key 作為軸,而不是始終設定為 arr[start]為軸,盡可能通過隨機化減少有序影響,實現或接近分治效果。