/** * 一、冒泡排序 * <p> * 原理:<p>循環n-1次,每次循環找到當次循環最大/最小元素</p> * 實現:<p>外層控制循環次數(n-1),內層對相鄰元素進行比較交換</p> * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)} */ private static void bubbleSort(int[] arr) { //外層控制循環次數 for (int m = 0; m < arr.length - 1; m++) { //內層比較交換 for (int n = 0; n < arr.length - 1 - m; n++) { if (arr[n] > arr[n + 1]) { int tmp = arr[n]; arr[n] = arr[n + 1]; arr[n + 1] = tmp; } } } }
/** * 二、選擇排序 * <p> * 原理:<p>循環n次,內部循環每次找到當前循環元素範圍內最大/最小下標,如發生改變,則交換數值</p> * 實現:<p>外層控制循環次數(n-1),內層對相鄰元素進行比較、記錄下標、交換數值</p> * 優缺點:對比冒泡排序,少了每次對比後的交互,只在每次外層循環後交互,優於冒泡 * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)} */ public static void selectSort(int[] arr) { //外層控制次數 for (int i = 0; i < arr.length; i++) { int min_index = i; //內層控制元素範圍並實現對比、記錄、交換 for (int j = i + 1; j < arr.length; j++) { if (arr[j] < arr[min_index]) { min_index = j; } } //若是最大/小index發生改變,則交換 if (i != min_index) { int temp = arr[i]; arr[i] = arr[min_index]; arr[min_index] = temp; } } }
/** * 三、插入排序 * <p> * 原理:<p>依次將原數組每一個元素與該元素以前的元素對比,根據排序規則插入到合適位置</p> * 實現:<p>外層循環控制對哪一個下標的元素進行處理,內部循環將次元素與其以前的元素進行對比,找到正確的位置</p> * 優缺點:在數組元素隨機排列的狀況下,插入排序優於冒泡和選擇 * 複雜度:{[@code](https://my.oschina.net/codeo) O(n^2)} */ private static void insertSort(int[] arr) { for (int i = 1; i < arr.length; i++) { //將當前元素與其以前的元素進行對比、交換 for (int j = i; j > 0; j--) { if (arr[j] < arr[j - 1]) { //交換 int temp = arr[j]; arr[j] = arr[j - 1]; arr[j - 1] = temp; } else { break; } } } }
/** * 四、希爾排序 * <p> * 原理: * <p> * 希爾排序也是一種插入排序,它將數組以增量進行增量組,而後分別進行插入排序,不斷縮小增量,直至增量爲1,數組即排序完成,建議增量爲{n/2,(n/2)/2...1},稱爲希爾增量 * </p> * 實現:<p>最外層循環控制增量,中層負責對當前增量下的元素進行循環處理,內層使用插入排序進行排序</p> * 優缺點:在數組元素隨機排列的狀況下,插入排序優於冒泡和選擇 * 複雜度:在{n/2,(n/2)/2...1}增量序列下爲{[@code](https://my.oschina.net/codeo) O(n^2)|,通過優化後能夠達到{[@code](https://my.oschina.net/codeo) O(n^2/3)} */ public static void shellSort(int[] arr) { //外層控制增量 for (int add = arr.length / 2; add > 0; add /= 2) { //從增量大小處開始對每組進行排序操做,由於在增量處恰好每一組都出現了一個元素 for (int i = add; i < arr.length; i++) { //插入排序操做,與該組該元素以前元素進行對比、交換 for (int j = i; j > 0; j -= add) { //判斷的時候注意j-add>=0,而不是>0,這樣才能確保第0個元素也能被排序進來 if (j - add >= 0 && arr[j] < arr[j - add]) { //交換 int temp = arr[j]; arr[j] = arr[j - add]; arr[j - add] = temp; } else { break; } } } } }
/** * 五、歸併排序 * <p> * 原理:<p>將原數組一直二分下去,直到不能再分,對分離的每個部分,從新組合起來,組合的時候對相鄰部分按照順序依次排序(藉助臨時數組重組後拷貝)</p> * 實現:<p>使用遞歸實現,在對相鄰節點進行合併的時候,藉助臨時數組進行存儲和拷貝</p> * 優缺點:利用徹底二叉樹,最好,最壞,平均時間複雜度均爲O(nlogn) * 複雜度:{@code O(nlogn)} */ public static void mergeSort(int[] arr) { //臨時數組,避免遞歸中頻繁開闢空間 int[] temp = new int[arr.length]; doMergeSort(arr, 0, arr.length - 1, temp); } /** * @param arr 排序數組 * @param left 最左邊index * @param right 最右邊index * @param temp 臨時數組 */ private static void doMergeSort(int[] arr, int left, int right, int[] temp) { //當元素不能再分時中止 if (left < right) { int mid = (left + right) / 2; doMergeSort(arr, left, mid, temp); doMergeSort(arr, mid + 1, right, temp); int left_start_index = left; int rigth_start_index = mid + 1; int temp_index = 0; //先對比在兩邊數組都有數據的時候 while (left_start_index <= mid && rigth_start_index <= right) { if (arr[left_start_index] < arr[rigth_start_index]) { temp[temp_index++] = arr[left_start_index++]; } else { temp[temp_index++] = arr[rigth_start_index++]; } } //考慮左還遺留元素 while (left_start_index <= mid) { temp[temp_index++] = arr[left_start_index++]; } //考慮右還遺留元素 while (rigth_start_index <= right) { temp[temp_index++] = arr[rigth_start_index++]; } //將臨時數組元素拷貝進原數組 int copy_index = 0; int arr_index = left; while (copy_index < right - left + 1) { arr[arr_index++] = temp[copy_index++]; } } }
/** * 六、快速排序-三數取中法 * <p> * 原理:<p>首先對原數組的首中(樞紐)尾三個元素進行排序交換,而後將中值移動到緊鄰尾元素的位置,對中間剩餘的非首中尾元素分別從前(記下標爲i)找到大於樞紐,尾(記下標爲j)找到 * 小於樞紐的值,若是配對成功則交換,當i>=j時,中止匹配,再將樞紐值與i(只可i不可j)值交換,再將該數組以i(此時爲樞紐值)拆分爲兩個數組,遞歸進行此操做,直至排序完成 * </p> * 實現:<p>先取得前、中、尾三數,再排序並把中移到緊鄰尾的位置,將剩餘元素以樞紐值劃分配對交換,還原樞紐值,遞歸</p> * 複雜度:{@code O(nlogn)} */ private static void quickSort(int[] arr) { doQuickSort(arr, 0, arr.length - 1); } /** * @param arr 排序數組 * @param left 左元素下標 * @param right 右元素下標 */ private static void doQuickSort(int[] arr, int left, int right) { if (left < right) { //取得首、中、尾並調整順序 int mid = (left + right) / 2; //左兩數 if (arr[left] > arr[mid]) { int temp = arr[left]; arr[left] = arr[mid]; arr[mid] = temp; } //右兩數 if (arr[mid] > arr[right]) { int temp = arr[mid]; arr[mid] = arr[right]; arr[right] = temp; } //左兩數 if (arr[left] > arr[mid]) { int temp = arr[left]; arr[left] = arr[mid]; arr[mid] = temp; } //將中值放到緊鄰尾元素處 int temp = arr[right - 1]; arr[right - 1] = arr[mid]; arr[mid] = temp; //真正的排序操做,若是left與right之間元素個數大於2個時才須要作拆分排序,其餘狀況在上面構建樞紐的時候就已經排序好了 if (right - left > 2) { int flag = arr[right - 1]; int left_index = left + 1; int right_index = right - 2; while (left_index <= right_index) { //大於樞紐值 if (arr[left_index] > flag) { if (arr[right_index] < flag) { int temp1 = arr[left_index]; arr[left_index] = arr[right_index]; arr[right_index] = temp1; left_index++; right_index--; } else { right_index--; } } else { left_index++; } } //把樞紐值和left_index處值交換 int temp2 = arr[left_index]; arr[left_index] = flag; arr[right - 1] = temp2; //將兩邊的數組繼遞歸處理 doQuickSort(arr, left, left_index - 1); doQuickSort(arr, left_index + 1, right); } } }
/** * 七、堆排序 * <p> * 原理:<p>將數組構建爲大頂堆或小頂堆,而後依次將頂元素存於數組,直至排序完成</p> * 實現:<p>構建大/小頂堆,交換元素</p> * 複雜度:{@code O(nlogn)} */ public static void heapSort(int[] arr) { //構建大頂堆 for (int i = arr.length / 2 - 1; i >= 0; i--) { build(arr, i, arr.length); } //交換元素 for (int j = arr.length - 1; j > 0; j--) { //交換 int temp = arr[0]; arr[0] = arr[j]; arr[j] = temp; build(arr, 0, j); } } /** * 調整大頂堆(僅是調整過程,創建在大頂堆已構建的基礎上) * * @param arr 數組 * @param i 第一個非葉子節點下標 */ public static void build(int[] arr, int i, int length) { int temp = arr[i]; for (int k = i * 2 + 1; k < length; k = k * 2 + 1) { if (k + 1 < length && arr[k] < arr[k + 1]) { k++; } if (arr[k] > temp) { arr[i] = arr[k]; i = k; } else { break; } } arr[i] = temp; }