插入排序
將第i個元素插入前i-1個已排好序的子序列中,使得前i個元素都是有序的,直到全部元素都是有序的。java
void insertSort(int[] nums) { for(int i=2; i<nums.length; i++) { int cur = nums[i]; int j=i-1; for(; j>=0 && nums[j]>cur; j--) { nums[j+1] = nums[j]; } nums[j+1] = cur; } }
改進:二分插入排序 —— 在插入第 i 個元素時使用二分法在前 i-1 個元素中查找插入位置。算法
void binaryInsertSort(int[] nums) { for(int i=1; i<nums.length; i++) { int cur = nums[i]; int low = 0, high = i-1; while(low<=high) { int mid = low + (high-low)/2; if(cur>nums[mid]) high = mid - 1; else low = mid + 1; } for(int j=i-1; j>high; j--) { nums[j+1] = nums[j]; } nums[high+1] = cur; } }
希爾排序
縮小增量的插入排序:先將待排數組分割成若干子序列分別進行插入排序,待整個數組中的元素基本有序時再對整個數組進行一次直接插入排序。shell
void shellSort(int[] nums, int delt[]) { // delt 爲增量序列 for(int k=0; k<delt.length; k++) { for(int i=delt[k]+1; i<nums.length; i=i+delt[k]) { int j=i-delt[k]; for(; j>=0 && nums[i]<nums[j]; j=j-delt[k]) { nums[j+delt[k]] = nums[j]; } nums[j+delt[k]] = nums[i]; } } }
冒泡排序
第一趟冒泡排序:將數組的第1個和第2個元素比較,若爲逆序則交換,繼續比較第2個和第3個元素,直至第n-1和第n個元素比較爲止,使得最大元素在數組末尾位置;
第二趟冒泡排序:將數組的前n-1個元素進行一樣操做,使得次大元素在數組倒數第二的位置;
...
第i趟冒泡排序:將數組的前n-i+1個元素依次比較相鄰元素,並在逆序時交換,使得這n-i+1個元素中最大的元素被交換到第n-i+1的位置上;
...
直到進行 n-1 趟排序爲止。數組
void bubbleSort(int[] nums) { for(int i=0; i<nums.length-1; i++) { for(int j=0; j<nums.length-i-1; j++) { if(nums[j]>nums[j+1]) { int temp = nums[j+1]; nums[j+1] = nums[j]; nums[j] = temp; } } } }
快速排序
經過一趟排序將待排數組分割成獨立的兩部分,使得其中一部分元素比另外一部分元素小,則可分別對這兩部分元素繼續進行排序,以達到整個數組有序。
一趟快速排序的步驟:設置兩個指針low, high, 分別指向待排序列的起止位置,設pivot爲樞軸元素,暫存,首先從high所指位置起向前搜索找到第一個小於pivot的元素,而後從low所指位置向後搜索找到第一個大於pivot的元素,排序結束後將樞軸元素移至正確位置上。緩存
int partion(int[] nums, int low, int high) { int pivotKey = nums[low]; while(low<high) { while(low<high && nums[high]>=pivotKey) high--; nums[low]=nums[high]; while(low<high && nums[low]<=pivotKey) low++; nums[high]=nums[low]; } nums[low]=pivotKey; return low; } void quickSort(int[] nums, int low, int high) { if(low<high) { int index = partion(nums, low, high); quickSort(nums, low, index-1); quickSort(nums, index+1, high); } }
切分函數:先在數組中選擇一個數字,再把數組中數字分爲兩部分,比選擇的數字小的移到數組的左邊,大的移到數組的右邊。less
int partionEx(int[] nums, int low, int high) { int pivot = low; swap(nums, pivot, high); int small = low-1; for(int i=low; i<high; i++) { if(nums[i]<nums[high]) { small++; if(small!=index) { swap(nums, small, index); } } } small++; swap(nums, small, high); return small; }
改進:三向切分——對於含有大量重複元素的數組能夠將數組切分爲三部分,即大於、等於和小於樞軸元素。函數
void quickSortThreeWay(int[] nums, int low, int high) { if(low>=high) return; int pivotKey = nums[low], less=low, i = low+1, greater = high; while(i<=greater) { if(nums[i]<pivotKey) swap(nums, less++, i++); else if(nums[i]>pivotKey) swap(nums, i, greater--); else i++; } quickSortThreeWay(nums, low, less-1); quickSortThreeWay(nums, greater+1, high); }
選擇排序
第i趟排序中經過n-i次比較從子序列 [i+1,...n] 中選擇最小的元素並與第i個元素交換。ui
void selectSort(int[] nums) { for(int i=0; i<nums.length-1; i++) { int minPos = i; for(int j=i+1; j<nums.length; j++) { if(nums[minPos]>nums[j]) { minPos = j; } } if(minPos!=i) swap(nums, i, minPos); } }
堆排序
將數組視爲一個徹底二叉樹:大頂堆中非葉節點元素 ≥ 其左右孩子,堆頂是最大值;小頂堆中非葉節點元素 ≤ 其左右孩子,堆頂是最小值。
先將數組建成大頂堆,將堆頂元素與數組最後一個元素交換,而後對前n-1個元素進行篩選從新調整成一個大頂堆,如此反覆直至數組有序。指針
void heapAdjust(int[] nums, int low, int high) { int cur = nums[low]; for(int i=2*low; i<=high; i=i*2) { if(i+1<=high && nums[i]<nums[i+1]) i++; // 孩子中的最大元素 if(cur>=nums[i]) break; nums[low] = nums[i]; low = i; } nums[low] = cur; } void heapSort(int[] nums) { for(int i=nums.length/2; i>=0; i--) { heapAdjust(nums, i, nums.lengh-1); } for(int i=nums.length-1; i>0; i--) { swap(nums, 0, i); heapAdjust(nums, 0, i-1); } }
歸併排序
將多個有序序列合併成一個新的有序序列。code
void mergeSort(int[] nums, int low, int high) { if(low<high) { int mid = low+(high-low)/2; mergeSort(nums, low, mid); mergeSort(nums, mid+1, high); merge(nums, low, mid, high); } } void merge(int[] nums, int low, int mid, int high) { int[] temp = new int[high-low+1]; int lowHF = low, highHF = mid+1, i=0; while(lowHF<=mid && highHF<=high) { if(nums[lowHF]<=nums[highHF]) temp[i++]=nums[lowHF++]; else temp[i++]=nums[highHF++]; } while(lowHF<=mid) { temp[i++]=nums[lowHF++]; } while(highHF<=high) { temp[i++]=nums[highHF++]; } for(i=0; i<temp.length; i++) { nums[i+low] = temp[i]; } }
排序算法的比較
冒泡排序:穩定; 時間複雜度 n^2; 空間複雜度 1. 插入排序:穩定; 時間複雜度 n ~ n^2, 和初始順序有關; 空間複雜度 1. 希爾排序:不穩定; 時間複雜度 n 的若干倍乘以遞增序列的長度; 空間複雜度 1. 選擇排序:不穩定; 時間複雜度 n^2; 空間複雜度 1. 快速排序:不穩定; 時間複雜度 nlogn ~ n^2; 空間複雜度 logn. 三向切分快速排序:不穩定; 時間複雜度 n ~ nlogn; 空間複雜度 logn; 適用於有大量重複主鍵. 堆排序:不穩定; 時間複雜度 nlogn; 空間複雜度 1. 歸併排序:穩定; 時間複雜度 nlogn; 空間複雜度 N.
快速排序是最快的通用排序算法,它的內循環的指令不多,並且它還能利用緩存,由於它老是順序地訪問數據。它的運行時間近似爲 ~cNlogN,這裏的 c 比其餘線性對數級別的排序算法都要小。
使用三向切分快速排序,實際應用中可能出現的某些分佈的輸入可以達到線性級別,而其它排序算法仍然須要線性對數時間。
Java 的排序算法實現 Java 主要排序方法爲 java.util.Arrays.sort(),對於原始數據類型使用三向切分的快速排序,對於引用類型使用歸併排序。