常見排序算法總結與實現(冒泡、插入、選擇、希爾、堆排序、歸並、快排)
本文使用Java實現這幾種排序算法。
以下是對排序算法總體的介紹。
冒泡排序
- 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個。
- 對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對。這步做完后,最后的元素會是最大的數。
- 針對所有的元素重復以上的步驟,除了最后一個。
- 持續每次對越來越少的元素重復上面的步驟,直到沒有任何一對數字需要比較。
時間復雜度:O(n^2),最優時間復雜度:O(n),平均時間復雜度:O(n^2)
public static void bubbleSort(Comparable[] a) { int j, flag; Comparable temp; for (int i = 0; i < a.length; i++) { flag = 0; for (j = 1; j < a.length - i; j++) { if (a[j].compareTo(a[j - 1]) < 0) { temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; flag = 1; } } // 如果沒有交換,代表已經排序完畢,直接返回 if (flag == 0) { return; } } }
插入排序
- 從第一個元素開始,該元素可以認為已經被排序
- 取出下一個元素,在已經排序的元素序列中從后向前掃描
- 如果該元素(已排序)大於新元素,將該元素移到下一位置
- 重復步驟3,直到找到已排序的元素小於或者等於新元素的位置
- 將新元素插入到該位置后
- 重復步驟2~5
時間復雜度:O(n^2),最優時間復雜度:O(n),平均時間復雜度:O(n^2)
下面展示了兩種插入排序的實現,第二種方法減少了交換次數。
public static void insertionSort(Comparable[] a) { int length = a.length; Comparable temp; for (int i = 1; i < length; i++) { for (int j = i; j > 0 && a[j].compareTo(a[j - 1]) < 0; j--) { temp = a[j]; a[j] = a[j - 1]; a[j - 1] = temp; } } }
// 對實現Comparable的類型進行排序,先將大的元素都向右移動,減少一半交換次數
public static void insertionSort(Comparable[] a) { int length = a.length; Comparable temp; int j; for (int i = 1; i < length; i++) { temp = a[i]; for (j = i; j > 0 && temp.compareTo(a[j - 1]) < 0; j--) { a[j] = a[j - 1]; } a[j] = temp; } }
選擇排序
首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再從剩余未排序元素中繼續尋找最小元素,然后放到已排序序列的末尾。
時間復雜度:O(n^2),最優時間復雜度:O(n^2),平均時間復雜度:O(n^2)
public static void selectionSort1(Comparable[] a) { int length = a.length; int min; Comparable temp; for (int i = 0; i < length; i++) { min = i; for (int j = i + 1; j < length; j++) { if (a[j].compareTo(a[min]) < 0) { min = j; } } temp = a[min]; a[min] = a[i]; a[i] = temp; } }
希爾排序
希爾排序通過將比較的全部元素分為幾個區域來提升插入排序的性能。這樣可以讓一個元素可以一次性地朝最終位置前進一大步。然后算法再取越來越小的步長進行排序,算法的最后一步就是普通的插入排序,但是到了這步,需排序的數據幾乎是已排好的了(此時插入排序較快)。
時間復雜度:根據步長而不同,最優時間復雜度:O(n),平均時間復雜度:根據步長而不同
public static void shellSort(Comparable[] a) { int length = a.length; int h = 1; Comparable temp; while (h < length / 3) { h = 3 * h + 1; } while (h >= 1) { for (int i = h; i < length; i++) { for (int j = i; j >= h && a[j].compareTo(a[j - h]) < 0; j -= h) { temp = a[j]; a[j] = a[j - h]; a[j - h] = temp; } } h /= 3; } }
堆排序
- 創建最大堆(Build_Max_Heap):將堆所有數據重新排序
- 堆排序(HeapSort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算
時間復雜度:O(nlogn),最優時間復雜度:O(nlogn),平均時間復雜度:O(nlogn)
public static void heapSort(Comparable[] a) { int length = a.length; Comparable temp; for (int k = length / 2; k >= 1; k--) { sink(a, k, length); } while (length > 0) { temp = a[0]; a[0] = a[length - 1]; a[length - 1] = temp; length--; sink(a, 1, length); } } private static void sink(Comparable[] a, int k, int n) { Comparable temp; while (2 * k <= n) { int j = 2 * k; if (j < n && a[j - 1].compareTo(a[j]) < 0) { j++; } if (a[k - 1].compareTo(a[j - 1]) >= 0) { break; } temp = a[k - 1]; a[k - 1] = a[j - 1]; a[j - 1] = temp; k = j; } }
歸並排序
歸並操作(merge),也叫歸並算法,指的是將兩個已經排序的序列合並成一個序列的操作。歸並排序算法依賴歸並操作。
時間復雜度:O(nlogn),最優時間復雜度:O(n),平均時間復雜度:O(nlogn),空間復雜度O(n)
自頂向下的歸並排序
private static Comparable[] aux; // 自頂向下 public static void mergeSort(Comparable[] a) { aux = new Comparable[a.length]; mergeSort(a, 0, a.length - 1); } public static void mergeSort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int mid = (lo + hi) >>> 1; mergeSort(a, lo, mid); mergeSort(a, mid + 1, hi); merge(a, lo, mid, hi); } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (aux[j].compareTo(aux[i]) < 0) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } }
自底向上的歸並排序
private static Comparable[] aux; // 自底向上 public static void mergeSort(Comparable[] a) { int length = a.length; aux = new Comparable[length]; for (int sz = 1; sz < length; sz = sz + sz) { for (int lo = 0; lo < length - sz; lo += sz + sz) { merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, length - 1)); } } } public static void merge(Comparable[] a, int lo, int mid, int hi) { int i = lo, j = mid + 1; for (int k = lo; k <= hi; k++) { aux[k] = a[k]; } for (int k = lo; k <= hi; k++) { if (i > mid) { a[k] = aux[j++]; } else if (j > hi) { a[k] = aux[i++]; } else if (aux[j].compareTo(aux[i]) < 0) { a[k] = aux[j++]; } else { a[k] = aux[i++]; } } }
快速排序
- 從數列中挑出一個元素,稱為"基准"(pivot),
- 重新排序數列,所有元素比基准值小的擺放在基准前面,所有元素比基准值大的擺在基准的后面(相同的數可以到任一邊)。在這個分區結束之后,該基准就處於數列的中間位置。這個稱為分區(partition)操作。
- 遞歸地(recursive)把小於基准值元素的子數列和大於基准值元素的子數列排序。
時間復雜度:O(n^2),最優時間復雜度:O(nlogn),平均時間復雜度:O(nlogn)
快排的時間復雜度跟選取基准的方法有關,一下是默認選擇了第一個元素作為基准,隨機性較大。
可以在序列中選取開始中間結尾三個數的中位數作為基准,進行優化。
public static void quickSort(Comparable[] a) { quickSort(a, 0, a.length - 1); } public static void quickSort(Comparable[] a, int lo, int hi) { if (hi <= lo) { return; } int j = partition(a, lo, hi); quickSort(a, lo, j - 1); quickSort(a, j + 1, hi); } public static int partition(Comparable[] a, int lo, int hi) { int i = lo, j = hi + 1; Comparable temp; Comparable v = a[lo]; while (true) { while (a[++i].compareTo(v) < 0) { if (i == hi) { break; } } while (v.compareTo(a[--j]) < 0) { if (j == lo) { break; } } if (i >= j) { break; } temp = a[i]; a[i] = a[j]; a[j] = temp; } temp = a[lo]; a[lo] = a[j]; a[j] = temp; return j; }