堆排序是一種樹形選擇排序,是對直接選擇排序的有效改進。html
具備n個元素的序列(h1,h2,...,hn),當且僅當知足(hi>=h2i,hi>=2i+1)或(hi<=h2i,hi<=2i+1) (i=1,2,...,n/2)時稱之爲堆。在這裏只討論知足前者條件的堆。由堆的定義能夠看出,堆頂元素(即第一個元素)必爲最大項(大頂堆)。徹底二 叉樹能夠很直觀地表示堆的結構。堆頂爲根,其它爲左子樹、右子樹。java
初始時把要排序的數的序列看做是一棵順序存儲的二叉樹,調整它們的存儲序,使之成爲一個堆,這時堆的根節點的數最大。而後將根節點與堆的最後一個節點交換。而後對前面(n-1)個數從新調整使之成爲堆。依此類推,直到只有兩個節點的堆,並對它們做交換,最後獲得有n個節點的有序序列。從算法描述來看,堆排序須要兩個過程,一是創建堆,二是堆頂與堆的最後一個元素交換位置。因此堆排序有兩個函數組成。一是建堆的滲透函數,二是反覆調用滲透函數實現排序的函數。算法
/** * 創建堆 * @param a 待建的數組 * @param lastIndex 須要創建的數組的最後一個元素(控制須要創建堆的長度) */ private void buildMaxHeap(int[] a, int lastIndex){ // 從lastIndex處節點(最後一個節點)的父節點開始 for(int i = (lastIndex-1)/2; i >=0 ; i--){ // k保存正在判斷的節點 int k=i; // 若是當前的節點的子節點存在 while (k*2+1<=lastIndex){ // biggerIndex 爲最大值的索引,先將其賦值爲左子節點 int biggerIndex = k*2+1; // 若是存在右子節點,則須要比較其大小 if(biggerIndex < lastIndex){ // biggerIndex始終爲最大的子節點。 if(a[biggerIndex + 1] > a[biggerIndex]){ biggerIndex++; } } // 若是k節點的值小於其較大的子節點的值,則須要交換他們 if(a[biggerIndex] > a[k]){ swap(a, biggerIndex, k); // 交換後的左子節點,、 // 有可能小於他本身的子節點, // 因此須要從新進行比較排序, // 保證最小值在下面的節點 k = biggerIndex; } else { break; } } } } /** * 交換 */ private void swap(int[] a, int i, int j){ int temp = a[i]; a[i] = a[j]; a[j] = temp; } public void heapSort(int[] a){ for(int i=0; i<a.length-1; i++){ buildMaxHeap(a, a.length-1-i); swap(a, 0, a.length-1-i); } } @Test public void heapTest(){ int[] a={7,5,3,2,9,10,8,4,6,1}; heapSort(a); System.out.println(Arrays.toString(a)); } 複製代碼
歸併排序(MERGE-SORT)是利用歸併的思想實現的排序方法,該算法採用經典的分治(divide-and-conquer)策略(分治法將問題分(divide)成一些小的問題而後遞歸求解,而治(conquer)的階段則將分的階段獲得的各答案"修補"在一塊兒,即分而治之)。shell
/** * 歸併排序 * 簡介:將兩個(或兩個以上)有序表合併成一個新的有序表 即把待排序序列分爲若干個子序列,每一個子序列是有序的。而後再把有序子序列合併爲總體有序序列 * 時間複雜度爲O(nlogn) * 穩定排序方式 * @param a 待排序數組 */ private void mergeSort(int[] a, int low, int high){ if(low < high){ int mid = (low + high) >>> 1; mergeSort(a, low, mid); mergeSort(a,mid+1, high); merge(a, low, mid, high); } } /** * 將數組中low到high位置的數進行排序 * @param a 待排序數組 * @param low 待排的開始位置 * @param mid 待排中間位置 * @param high 待排結束位置 */ private void merge(int[] a, int low, int mid, int high){ int[] temp = new int[high -low + 1]; int i = low; // 左指針 int j = mid + 1; // 右指針 int k = 0; // 將較小的數移到新數組中 while (i <= mid && j <= high){ if(a[i] < a[j]){ temp[k++] = a[i++]; } else { temp[k++] = a[j++]; } } // 把左邊剩餘的移到數組中 while (i <= mid){ temp[k++] = a[i++]; } // 把右邊剩餘的移到數組中 while (j <= high){ temp[k++] = a[j++]; } // 把新數組中的數覆蓋原數組 for(int k2 = 0; k2 <temp.length; k2++){ a[k2+low] = temp[k2]; } } @Test public void mergeTest(){ int[] a={7,5,3,2,9,10,8,4,6,1}; mergeSort(a, 0, a.length-1); System.out.println(Arrays.toString(a)); } 複製代碼
把記錄按步長進行分組,對每組記錄採用直接插入的方法進行排序。隨着步長的縮小,所分紅的組包含的記錄就愈來愈多,當步長的值減少到1時,整個數據合成一組,構成一組有序的記錄,則完成排序。數組
private void shellSort(int[] a){ for(int increment = a.length/2; increment > 0; increment /= 2){ // 分組 for(int i=increment; i < a.length; i++){ // 組內排序 for(int j=i; j >= increment; j--){ if(a[j] < a[j-increment]){ int temp = a[j]; a[j] = a[j-increment]; a[j-increment] = temp; } else { // 由於當一次循環以上時,由於前面已經排好序, // 因此直接與最近的一個increment增量比較便可。 // 符合繼續比較,不符合直接跳過 break; } } } } } 複製代碼
減小交換次數,提升效率bash
/** * 哨兵希爾排序,就是將位置爲j的元素取出來放到一個變量, * 最後將這個值放到合適的位置 * @param a 待排序數組a */ private void shellSort1(int[] a){ int j=0; int temp = 0; for(int increment = a.length >>> 1; increment >0; increment = increment >>> 1){ for (int i=increment; i < a.length; i++){ temp = a[i]; for(j=i; j>=increment; j-=increment){ // temp 是a[j-increment] 交換後的值, // if裏面只是將a[j]的值變換了, // 而a[j-increment]中沒有變, // 所以來減小變換次數 if(temp < a[j-increment]){ a[j] = a[j-increment]; } else { // 由於當一次循環以上時,由於前面已經排好序, // 因此直接與最近的一個increment增量比較便可。 // 符合繼續比較,不符合直接跳過 break; } } a[j] = temp; } } } 複製代碼
參考:
必須知道的八大種排序算法【java實現】(二) 選擇排序,插入排序,希爾算法【詳解】
內部排序(二)希爾排序的兩種實現
數據結構常見的八大排序算法(詳細整理) 圖解排序算法(四)之歸併排序markdown
有些註解是本身的理解,註解及代碼若有不恰之處,歡迎各位留言指正。數據結構