承接上文基礎排序算法—冒泡,插入,選擇,相比之下,歸併排序和快速排序更爲高效,時間複雜度均爲O(nlogn),相比簡單排序的O(n^2)好了不少,下面介紹一下這兩種算法的思路,實現和主要指標.主要思路來自<數據結構與算法之美>java
在歸併排序採用分冶的思想,使用遞歸實現.描述以下git
- 開始
歸併排序
- 若是數組元素小於兩個,無需排序,結束
- 不然須要排序,
歸併排序
數組左側,歸併排序
數組右側,按序合併左右側
採用的是自頂至下的思路,例如[1,5,3,7,4,6]
,左側爲[1,5,3]
,右側爲[7,4,6]
,左側歸併排序後爲[1,3,5]
,右側歸併排序後爲[4,6,7]
,按序合併後[1,3,4,5,6,7]
這裏的關鍵是只在主流程思考,不要試圖代入到遞歸的子流程中,引用一句: "編寫遞歸代碼的關鍵是,只要遇到遞歸,咱們就把它抽象成一個遞推公式,不用想一層層的調用關係,不要試圖用人腦去分解遞歸的每一個步驟" ,遞歸公式以下github
merge_sort(arr[low,high))=merge_sort(arr[low,mid))+merge_sort(arr[mid,high) merge(arr,low,mid,high)
java實現以下算法
public void sort(Comparable[] arr) { if (arr.length <= 1) { return; } mergeSort(arr, 0, arr.length); } private void mergeSort(Comparable[] arr, int low, int high) { if (high - low <= 1) { return; } int mid = low + (high - low) / 2; mergeSort(arr, low, mid); mergeSort(arr, mid, high); merge(arr, low, mid, high); } private void merge(Comparable[] arr, int low, int mid, int high) { Comparable[] left = Arrays.copyOfRange(arr, low, mid); Comparable[] right = Arrays.copyOfRange(arr, mid, high); int l = 0, r = 0; int pos = low; for (int i = 0; i < (high - low); i++) { Comparable next = null; if (l == left.length) { next = right[r++]; } else if (r == right.length) { next = left[l++]; } else if (left[l].compareTo(right[r]) <= 0) { next = left[l++]; } else { next = right[r++]; } arr[pos++] = next; } }
merge方法將兩個有序數組合併爲一個有序數組,最後的判斷邏輯有一點繁瑣,王爭文中有提到能夠用哨兵簡化,也許是我使用方法不對,使用哨兵後感受反而更麻煩了,最終採用了繁瑣的寫法.數組
對於遞歸實現,分析時間複雜度時也要推導出公式,對左右數組歸併排序數量級都是一半,所以爲T(n/2),merge()方法是對兩個數組按序排列,時間複雜度爲O(n), 當n=1時,時間複雜度爲常數,由此可得數據結構
T(n) = T(n/2) + T(n/2) + n = 2*T(n/2) + n T(1) = C
下面對公式進行代入ui
T(n) = 2*T(n/2) + n = 2*(2*T(n/4) + n/2) + n = 2*(2*(2*T(n/8)+n/4) + n/2) + n = 2^3*T(n/2^3) + 3n = 2^k*T(n/2^k) + kn 當T(n/2^k) == T(1) 即n/2^k == 1 可得k=logn(底數爲2),代入到T(n)中得 T(n) = n*C +logn*n
因此歸併排序的時間複雜度爲O(nlogn),根據歸併排序的思路,它的算法複雜度不受數組順序影響.code
不是,merge過程當中須要對左右子數組複製進行歸併,儘管遞歸過程當中會不斷申請額外空間,可是同一時間申請的最大額外空間爲O(n),空間複雜度爲O(n)排序
歸併排序數組元素的移動只發生在merge階段,當左右相等時,咱們會優先選擇左側(left[l].compareTo(right[r]) <= 0),所以是穩定的遞歸
快排經過partition(劃分)將數組劃分爲中間值的左側和右側.保證左側<=中間值<右側,再對左側右側(不包含中間值所在下標)遞歸處理,實現排序.
快排的遞歸僞代碼以下
quick_sort(arr,low,high)={ if (high-low<=1) return mid = partition(arr,low,high) quick_sort(arr,low,mid) quick_sort(arr,mid+1,high) }
java代碼以下
public void sort(Comparable[] arr) { if (arr.length <= 1) { return; } quickSort(arr, 0, arr.length); } private void quickSort(Comparable[] arr, int low, int high) { if (high - low <= 1) { return; } int mid = partition(arr, low, high); quickSort(arr, low, mid); quickSort(arr, mid + 1, high); } private int partition(Comparable[] arr, int low, int high) { Comparable cmpValue = arr[high - 1]; int i = low; for (int j = low; j < high - 1; j++) { if (arr[j].compareTo(cmpValue) <= 0) { swap(arr, i++, j); } } swap(arr, i, high - 1); return i; }
快速排序的核心在於partition(劃分),這裏有兩種方式:
和歸併排序相似,時間複雜度的遞推公式爲
T(n) = n + T(n/2) + T(n/2) = n + 2*T(n/2) T(1) = C
推到過程同上,時間複雜度也是O(nlogn)
是的,快速排序過程當中不佔用額外空間
不穩定,從上文基礎排序算法—冒泡,插入,選擇中咱們得知選擇排序是不穩定的,快速排序的劃分過程採用的事選擇排序的思想,所以也是不穩定的
從實際使用來講,快速排序的效率更高,通常的庫實現也會優先選擇它.
源碼地址github