七、排序 (2)
基于分治思想的排序算法:歸并排序、快速排序
一、歸并排序(Merge Sort)
1、基本思想
基于分治(分而治之)思想,歸并排序算法不斷地將原數(shù)組分成大小相等的兩個子數(shù)組(可能相差1),最終當(dāng)劃分的子數(shù)組大小為1時;然后對前后兩部分分別排序,再將劃分的有序子數(shù)組合并成一個更大的有序數(shù)組。
- 分治,顧名思義,就是分而治之,將一個大問題分解成小的子問題來解決。小的子問題解決了,大問題也就解決了。
2、合并相鄰有序子序列(治)
例如:求解數(shù)組 a[p…r] 等價(jià)于 求解有序的a[p…q]和a[q+1…r]的合并,其中 q= (p+r)/2。
過程:
3、實(shí)現(xiàn)
void merge_sort(int *data, int start, int end, int *result) {if(1 == end - start)//如果區(qū)間中只有兩個元素,則對這兩個元素進(jìn)行排序{if(data[start] > data[end]){int temp = data[start];data[start] = data[end];data[end] = temp;}return;}else if(0 == end - start)//如果只有一個元素,則不用排序return;else{//繼續(xù)劃分子區(qū)間,分別對左右子區(qū)間進(jìn)行排序 merge_sort(data,start,(end-start+1)/2+start,result);merge_sort(data,(end-start+1)/2+start+1,end,result);//開始?xì)w并已經(jīng)排好序的start到end之間的數(shù)據(jù)merge(data,start,end,result);//把排序后的區(qū)間數(shù)據(jù)復(fù)制到原始數(shù)據(jù)中去for(int i = start;i <= end;++i)data[i] = result[i];} }void merge(int *data,int start,int end,int *result) {int left_length = (end - start + 1) / 2 + 1;//左部分區(qū)間的數(shù)據(jù)元素的個數(shù)int left_index = start;int right_index = start + left_length;int result_index = start;while(left_index < start + left_length && right_index < end+1){//對分別已經(jīng)排好序的左區(qū)間和右區(qū)間進(jìn)行合并if(data[left_index] <= data[right_index])result[result_index++] = data[left_index++];elseresult[result_index++] = data[right_index++];}while(left_index < start + left_length)result[result_index++] = data[left_index++];while(right_index < end+1)result[result_index++] = data[right_index++]; }4、分析
(1)穩(wěn)定排序算法
- 關(guān)鍵在于合并相鄰有序子序列的部分。
- 在合并的過程中,若a[p…q]和a[q+1…r]之間有值相同的元素,那么將a[p…q]中的元素放入tmp數(shù)組中,就使得值相同的元素,在合并前后的先后順序不變。
(2)時間復(fù)雜度
a、遞歸方法的時間復(fù)雜度
遞歸方法:一個問題A可以分解為多個子問題B、C,那么求解問題A可分解為求解B、C。問題B、C解決之后,我們將B、C的結(jié)果合并成問題A的結(jié)果。
定義求解問題A的時間是T(a),求解問題B、C的時間分別是 T(b) 和 T(c) ,則可得遞歸公式:
T(a) = T(b) + T(c) + K其中,K 等于將兩個子問題 B、C 的結(jié)果合并成問題A的結(jié)果所消耗的時間==》遞歸代碼的時間復(fù)雜度也可以寫成遞推公式
b、歸并排序的時間復(fù)雜度
- 假設(shè)對 n 個元素進(jìn)行歸并排序需要 T(n),那分解成兩個子數(shù)組排序的時間都是 T(n/2);
- 合并相鄰有序子序列的時間復(fù)雜度是 O(n) 。
可得 ==》
T(1) = C; //n=1 時,只需要常量級的執(zhí)行時間,所以表示為 C。 T(n) = 2 * T(n/2) + n; // n > 1分解可得 ==》
T(n) = 2 * T(n/2) + n;= 2 * (2 * T(n/4) + n/2) + n = 4 * T(n/4) + 2 * n= 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n = 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n……= 2^k * T(n/2^k) + k * n……==》當(dāng) T(n/2^k) = T(1) 求得
==》k=log2n
==》將 k 值帶入,可得 T(n) = Cn + nlog2n
==》時間復(fù)雜度:T(n) = O(nlogn)
(3)空間復(fù)雜度——O(n)
每次合并操作都需申請額外的內(nèi)存空間,但在完成之后,臨時內(nèi)存空間就被釋放掉。
==》 在任意時刻,CPU只會有一個函數(shù)在執(zhí)行,也就是只有一個臨時內(nèi)存空間在使用 ==》最大不會超過 n 個數(shù)據(jù)的大小:O(n)
二、快速排序 (QuickSort)
1、基本思想
基于分治思想,快速排序算法:選擇一個基準(zhǔn)數(shù),通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨(dú)立的兩部分;其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小。然后,再按此方法對這兩部分?jǐn)?shù)據(jù)分別進(jìn)行快速排序,整個排序過程可以遞歸進(jìn)行,以此達(dá)到整個數(shù)據(jù)變成有序序列。
快速排序的流程:
(1) 從數(shù)列中挑出一個基準(zhǔn)值。
(2) 將所有比基準(zhǔn)值小的擺放在基準(zhǔn)前面,所有比基準(zhǔn)值大的擺在基準(zhǔn)的后面(相同的數(shù)可以到任一邊);在這個分區(qū)退出之后,該基準(zhǔn)就處于數(shù)列的中間位置。
(3) 遞歸地把"基準(zhǔn)值前面的子數(shù)列"和"基準(zhǔn)值后面的子數(shù)列"進(jìn)行排序。
2、偽代碼
partition(A, p, r) {key := A[r];i := p;for j := p to r-1 do {if A[j] < key {swap A[i] with A[j]i := i + 1}}swap A[i] with A[r]return i }通過游標(biāo) i 把 A[p…r-1] 分成兩部分。A[p…i-1] 的元素都是小于 key 的,我們暫且叫它“已處理區(qū)間”,A[i…r-1] 是“未處理區(qū)間”。我們每次都從未處理的區(qū)間 A[i…r-1] 中取一個元素 A[j],與 key 對比,如果小于 key,則將其加入到已處理區(qū)的尾部,也就是 A[i] 的位置。
3、性能分析
- 不穩(wěn)定
- 原地排序——空間復(fù)雜度為O(1)
- 時間復(fù)雜度:大部分情況下的時間復(fù)雜度都可以做到 O(nlogn),只有在極端情況下,才會退化到 O(n2)。
總結(jié)
- 上一篇: 七、排序(1)
- 下一篇: 七、排序(3)——线性排序