排序演算法總結(含動圖演示和Java程式碼實現)
阿新 • • 發佈:2018-12-06
本文將圍繞氣泡排序、桶排序、計數排序、堆排序、插入排序、並歸排序、快速排序和選擇排序,按照描述、時間複雜度(最壞情況)、動態圖展示和程式碼實現來講解。本文預設排序為從小到大。
本文相關程式碼已上傳至github,歡迎關注https://github.com/zhuzhenke/common-algorithms
公用方法
SortUtils
public class SortUtils { public static void check(int[] sortingData) { if (sortingData == null || sortingData.length == 0) { throw new IllegalArgumentException("sortingData can not be empty!"); } } public static void swap(int[] swapArray, int i, int j) { check(swapArray); if (i < 0 || i > swapArray.length - 1) { throw new ArrayIndexOutOfBoundsException("illegal index i:" + i); } if (j < 0 || j > swapArray.length - 1) { throw new ArrayIndexOutOfBoundsException("illegal index j:" + j); } int temp = swapArray[i]; swapArray[i] = swapArray[j]; swapArray[j] = temp; } public static int getMaxValue(int[] sortingData) { check(sortingData); int max = sortingData[0]; for (int value : sortingData) { if (value < 0) { throw new IllegalArgumentException("value could not be negative:" + value); } if (value > max) { max = value; } } return max; } }
氣泡排序
描述
- 比較前後兩個數的大小,如果前者大於後者,則交換兩個數的位置
- 最大的數字在進行一輪比較和交換後,會出現在陣列的最後一個位置
- 每一輪之後,可減少最大數字的比較。重複上述操作直到沒有需要比較的元素為止
時間複雜度
O(n^2)
動態圖展示
程式碼實現
public int[] sort(int[] sortingData) { SortUtils.check(sortingData); for (int j = sortingData.length - 1; j > 0; j--) { for (int i = 0; i < j; i++) { if (sortingData[i] > sortingData[i + 1]) { SortUtils.swap(sortingData, i, i + 1); } } } return sortingData; }
桶排序
描述
- 挑選陣列中最大的數字,設定預設分配的桶數,得到每個桶容納的數字範圍。如最大是10,桶是4個,則每個桶最大容納3個數字。第0個桶放0、1、2、3,第1個桶方4、5、6,第2桶方7,8,9,以此類推
- 對每個桶內進行氣泡排序或選擇排序
- 遍歷所有桶,依次取出每個桶中的元素,得到的就是一個排好序的陣列
時間複雜度
O(n^2)
動態圖展示
程式碼實現
@Override public int[] sort(int[] sortingData) { SortUtils.check(sortingData); int bucketSize = (int) Math.round(Math.sqrt(sortingData.length)) + 1; int[][] buckets = new int[bucketSize][]; int max = SortUtils.getMaxValue(sortingData); double avgContain = Math.ceil((double) max / (double) bucketSize); for (int value : sortingData) { int bucketIndex = (int) Math.ceil(value / avgContain) - 1; if (bucketIndex < 0) { bucketIndex = 0; } int[] bucketIndexs = buckets[bucketIndex]; if (bucketIndexs == null || bucketIndexs.length == 0) { bucketIndexs = new int[1]; bucketIndexs[0] = value; buckets[bucketIndex] = bucketIndexs; } else { int[] newBucketIndexs = new int[bucketIndexs.length + 1]; System.arraycopy(bucketIndexs, 0, newBucketIndexs, 0, bucketIndexs.length); newBucketIndexs[bucketIndexs.length] = value; buckets[bucketIndex] = newBucketIndexs; } } Sort sort = new InsertionSort(); for (int a = 0; a < buckets.length; a++) { int[] bucket = buckets[a]; if (bucket == null || bucket.length == 0) { continue; } bucket = sort.sort(bucket); buckets[a] = bucket; } int[] result = new int[sortingData.length]; int resultIndex = 0; for (int[] bucket : buckets) { if (bucket == null || bucket.length == 0) { continue; } for (int bucketValue : bucket) { result[resultIndex++] = bucketValue; } } return result; }
計數排序
描述
- 獲取陣列中最大的值(這個值需要在可控範圍,最好是在10000以內)
- 建立一個長度為最大值加1的計數陣列,
- 遍歷待排序陣列,將元素的值落入到計數陣列的下標元素,將下標元素的值加1
- 遍歷下標陣列,將後一個元素的值標為當前元素值加前一個元素的值(用於排序後的陣列下標)。
- 建立一個長度跟待排序陣列大小相同的結果陣列
- 遍歷待排序陣列,取出待排序元素,對應計數陣列的下標元素,並放在計數元素值的前一個位置,並將計數元素值減1
時間複雜度
O(n)
動態圖展示
程式碼實現
private int[] sort2(int[] sortingData) {
int maxValue = SortUtils.getMaxValue(sortingData);
//get max,check all numbers must be bigger or equal 0
int[] count = new int[maxValue + 1];
//count every number
for (int value : sortingData) {
count[value] = count[value] + 1;
}
for (int i = 1; i < count.length; i++) {
count[i] = count[i] + count[i - 1];
}
//output
int[] result = new int[sortingData.length];
for (int value : sortingData) {
result[count[value] - 1] = value;
count[value] = count[value] - 1;
}
return result;
}
堆排序
描述
- 為了方便理解,將陣列元素對映成二叉樹,array[0]對一個根節點,array[1]為根的左節點,array[2]為根的右節點,一次類推
- 從最後一個元素開始遍歷到第一個,讓其與父節點比較大小,如果子節點大,則交換位置。(另外一種方式是從中間元素開始比較,得到當前元素和子元素的最大值,這樣則遍歷深度為logn,這種方式屬於推薦方式)
- 遍歷一輪結束後,則最大值已經位於index為0的位置,這時交換index為0和最後一位的位置,則最大值確定。下一輪比較從最大值的前一個index下標元素開始遍歷,依次進行遍歷
- 最後全部遍歷完成,則得到一個拍好序的陣列
時間複雜度
O(nlogn)
動態圖展示
程式碼實現
public int[] sort(int[] sortingData) {
int highIndex = sortingData.length - 1;
while (highIndex > 0) {
for (int i = 1; i <= highIndex; i++) {
sortBiggestToIndex0(sortingData, i);
}
SortUtils.swap(sortingData, 0, highIndex);
highIndex--;
}
return sortingData;
}
public static void sortBiggestToIndex0(int[] sortingData, int sortIndex) {
while (sortIndex > 0 && sortingData[sortIndex] > sortingData[(sortIndex - 1) / 2]) {
SortUtils.swap(sortingData, sortIndex, (sortIndex - 1) / 2);
sortIndex = (sortIndex - 1) / 2;
}
}
插入排序
描述
- 插入排序類似於撲克摸牌排序
- 第一次只有一種牌,牌是有序的。當摸到第二張牌時,則插入到已有的排好序的牌中,此時前兩張牌有序
- 依次進行同樣的操作,摸到第n張牌時,前n-1張牌已經有序,進行插入到合適位置即可
時間複雜度
O(n^2)
動態圖展示
程式碼實現
public int[] sort(int[] sortingData) {
SortUtils.check(sortingData);
for (int i = 1; i < sortingData.length; i++) {
int currentValue = sortingData[i];
int j = i;
while (j - 1 >= 0 && currentValue < sortingData[j - 1]) {
sortingData[j] = sortingData[j - 1];
j--;
}
sortingData[j] = currentValue;
}
return sortingData;
}
並歸排序
描述
- 並歸排序利用遞迴思想,遞迴思想的核心在於找到一個模式,分解為子模式,子模式又可以分解子模式,則對於最小子模式,可以直接求解。
- 首先會將待排序陣列分為兩份,兩份分別排好序後進行合併
- 兩份中的每一份,又可以查分為更小的一份,直到每份只有一個元素,則此份為已排好序的子陣列。對兩個子陣列進行合併的排序
時間複雜度
O(nlogn)
動態圖展示
程式碼實現
@Override
public int[] sort(int[] sortingData) {
SortUtils.check(sortingData);
splitDate(sortingData, 0, sortingData.length - 1);
return sortingData;
}
private void splitDate(int[] sortingData, int start, int end) {
if (end - start < 1) {
return;
}
int middle = (start + end) / 2;
splitDate(sortingData, start, middle);
splitDate(sortingData, middle + 1, end);
mergeData(sortingData, start, middle, end);
}
private void mergeData(int[] sortingData, int start, int middle, int end) {
int[] left = Arrays.copyOfRange(sortingData, start, middle + 1);
int[] right = Arrays.copyOfRange(sortingData, middle + 1, end + 1);
int i = 0;
int j = 0;
for (int k = start; k <= end; k++) {
if (i < left.length && j < right.length) {
if (left[i] <= right[j]) {
sortingData[k] = left[i];
i++;
} else {
sortingData[k] = right[j];
j++;
}
} else {
if (i >= left.length) {
sortingData[k] = right[j];
j++;
} else if (j >= right.length) {
sortingData[k] = left[i];
i++;
}
}
}
}
快速排序
描述
- 選擇待排序陣列的中元元素,一般選擇第一個或最後一個元素,將陣列拆分為兩部分,左邊部分元素小於等於中元元素,右邊部分元素大於中元元素
- 繼續將子陣列按中元元素進行拆分,直到全部排好序位置
時間複雜度
O(n^2)
動態圖展示
程式碼實現
@Override
public int[] sort(int[] sortingData) {
SortUtils.check(sortingData);
quickSort(sortingData, 0, sortingData.length - 1);
return sortingData;
}
private void quickSort(int[] sortingData, int start, int end) {
if (start > end) {
return;
}
int middle = getQuickSortMiddle(sortingData, start, end);
quickSort(sortingData, start, middle - 1);
quickSort(sortingData, middle + 1, end);
}
/**
* one side
*/
public int getQuickSortMiddle(int[] sortingData, int start, int end) {
int i = start;
int pivot = end;
for (int j = start; j < end; j++) {
if (sortingData[j] < sortingData[pivot]) {
SortUtils.swap(sortingData, i, j);
i++;
}
}
SortUtils.swap(sortingData, i, pivot);
return i;
}
選擇排序
描述
- 從第一個元素開始遍歷,記錄最小值和對應元素下標,遍歷一輪則可以得到最小值,將這個值與下標為0的元素交換,則完成第一輪最小值輸出
- 從第2個進行同樣的遍歷操作,遍歷取剩餘元素的最小值,將這個值與下標為1的元素交換
- 從第index個開始進行遍歷操作,遍歷取剩餘元素的最小值,將這個值與下標為index的元素交換
- 遍歷直到最後一個元素位置,得到一個排好序的陣列
時間複雜度
O(n^2)
動態圖展示
程式碼實現
public int[] sort(int[] sortingData) {
SortUtils.check(sortingData);
for (int index = 0; index < sortingData.length; index++) {
int smallestIndex = index;
int smallestValue = sortingData[index];
for (int j = index; j < sortingData.length; j++) {
if (sortingData[j] < smallestValue) {
smallestValue = sortingData[j];
smallestIndex = j;
}
}
sortingData[smallestIndex] = sortingData[index];
sortingData[index] = smallestValue;
}
return sortingData;
}