快速排序和歸併排序比較

2021-06-27 08:03:05 字數 4654 閱讀 4611

by:             潘雲登

date:          2009-7-12

email:         [email protected]

homepage: 

對於商業目的下對本文的任何行為需經作者同意。

寫在前面

1.          本文內容對應《演算法導論》(第2版)》第2章和第7章。

2.          比較了歸併排序與快速排序之間的不同策略,啟發對分治演算法的深入思考。

分治法有很多演算法在結構上是遞迴的:為了解決乙個給定的問題,演算法要一次或多次地遞迴呼叫其自身來解決相關的子問題。這些演算法通常採用分治策略(divide-and-conquier):將原問題劃分成n個規模較小而結構與原問題相似的子問題;遞迴地解決這些子問題,然後再合併其結果,就得到原問題的解。分治模式在每一層遞迴上都有三個步驟:

²         分解(divide):將原問題分解成一系列子問題;

²         解決(conquer):遞迴地解各子問題。若子問題足夠小,則直接求解;

²         合併:將子問題的結果合併成原問題的解。

自底向上的歸併排序

歸併排序演算法完全依照分治模式,直觀的操作如下:

²         分解:將n個元素分成各含n/2個元素的子串行;

²         解決:用歸併排序法對兩個子串行遞迴地排序;

²         合併:合併兩個已排序的子串行以得到排序結果。

觀察下面的例子,可以發現:歸併排序在分解時,只是單純地將原問題分解為兩個規模減半的子問題;在分解過程中,沒有任何對原問題所含資訊的利用,沒有任何嘗試對問題求解的動作;這種分解持續進行,直到子問題規模降足夠小(為1),這時子問題直接得解;然後,自底向上地合併子問題的解,這時才真正利用原問題的特定資訊,執行求解動作,對元素進行比較。

4 2 5 7 1 2 6 3

4 | 2  |  5 | 7   |   1 | 2  |  6 | 3

4 2 5 7   |   1 2 6 3

2 4  |  5 7   |   1 2  |  3 6

4 2  |  5 7   |   1 2  |  6 3

2 4 5 7   |   1 2 3 6

4 | 2  |  5 | 7   |   1 | 2  |  6 | 3

1 2 2 3 4 5 6 7

這種自底向上分治策略的程式設計模式如下:

如果問題規模足夠小,直接求解,否則

單純地分解原問題為規模更小的子問題,並持續這種分解;

執行求解動作,將子問題的解合併為原問題的解。

由於在自底向上的歸併過程中,每一層需要進行i組n/i次比較,而且由於進行的是單純的對稱分解,總的層數總是lg n,因此,歸併排序在各種情況下的時間代價都是θ(n lg n)。試想,能夠加大分組的力度,即每次將原問題分解為大於2的子問題,來降低執行時間?

歸併排序演算法的**如下:

* p: 左陣列第乙個元素下標

* q: 左陣列最後乙個元素下標

* r: 右陣列最後乙個元素下標

void merge_no_sentinel(int *array, int p, int q, int r)

int n1, n2, i, j, k;

int *left=null, *right=null;

n1 = q-p+1;

n2 = r-q;

left = (int *)malloc(sizeof(int)*(n1));

right = (int *)malloc(sizeof(int)*(n2));

for(i=0; ileft[i] = array[p+i];

for(j=0; jright[j] = array[q+1+j];

i = j = 0;

k = p;

while(iif(left[i] <= right[j])

array[k++] = left[i++];

else

array[k++] = right[j++];

for(; iarray[k++] = left[i];

for(; jarray[k++] = right[j];

free(left);

free(right);

left = null;

right = null;

void merge_sort(int *array, int p, int r)

int q;

if(p < r)

q = (int)((p+r)/2);

merge_sort(array, p, q);

merge_sort(array, q+1, r);

merge_no_sentinel(array, p, q, r);

自頂向下的快速排序

快速排序也是基於分治策略,它的三個步驟如下:

²         分解:陣列a[p..r]被劃分為兩個(可能空)子陣列a[p..q-1]和a[q+1..r],使得a[p..q-1]中的每個元素都小於等於a(q),而且,小於等於a[q+1..r]中的元素,下標q也在這個分解過程中進行計算;

²         解決:通過遞迴呼叫快速排序,對子陣列a[p..q-1]和a[q+1..r]排序;

²         合併:因為兩個子陣列是就地排序的,將它們的合併並不需要操作,整個a[p..r]已排序。

可以看到:快速排序與歸併排序不同,對原問題進行單純的對稱分解;其求解動作在分解子問題開始前進行,而問題的分解基於原問題本身包含的資訊;然後,自頂向下地遞迴求解每個子問題。可以通過下面的例子,觀察快速排序的執行過程。由於在快速排序過程中存在不是基於比較的位置交換,因此,快速排序是不穩定的。

4 2 5 7 1 2 6 | 3

2 1 2   |   3   |   7 4 5 6

1  |  2  |  2   |   3   |   4 5  |  6  |  7

1  |  2  |  2   |   3   |   4 | 5  |  6  |  7

這種自頂向下分治策略的程式設計模式如下:

如果問題規模足夠小,直接求解,否則

執行求解動作,將原問題分解為規模更小的子問題;

遞迴地求解每個子問題;

因為求解動作在分解之前進行,在對每個子問題求解之後,不需要合併過程。

快速排序的執行時間與分解是否對稱有關,而後者又與選擇了哪乙個元素來進行劃分有關。如果劃分是對稱的,則執行時間與歸併排序相同,為θ(n lg n)。如果每次分解都形成規模為n-1和0的兩個子問題,快速排序的執行時間將變為θ(n2)。快速排序的平均情況執行時間與其最佳情況相同,為θ(n lg n)。

快速排序演算法的**如下:

void swap(int *a, int *b)

int temp;

temp = *a;

*a = *b;

*b = temp;

* p: 陣列第乙個元素的下標

* r: 陣列最後乙個元素的下標

* 返回值為分組後主元的下標

int partition(int *array, int p, int r)

int i, j, pivot;

pivot = array[r];

i = p-1;

for(j=p; j<=r-1; j++)

if(array[j] <= pivot)

i++;

swap(&array[i], &array[j]);

swap(&array[i+1], &array[r]);

return i+1;

void quick_sort(int *array, int p, int r)

int q;

if(p < r)

q = partition(array, p, r);

quick_sort(array, p, q-1);

quick_sort(array, q+1, r);

通常,我們可以向乙個演算法中加入隨機化成分(參考第5章內容),以便對於所有輸入,它均能獲得較好的平均情況效能。將這種方法用於快速排序時,不是始終採用a[r]作為主元,而是從子陣列a[p..r]中隨機選擇乙個元素,即將a[r]與從a[p..r]中隨機選出的乙個元素交換。

#include

#include

int randomized_partition(int *array, int p, int r)

int i;

srand(time(null));

i = rand()%(r-p)+p;

swap(&array[i], &array[r]);

return partition(array, p, r);

void randomized_quick_sort(int *array, int p, int r)

int q;

if(p < r)

q = randomized_partition(array, p, r);

randomized_quick_sort(array, p, q-1);

randomized_quick_sort(array, q+1, r);

歸併排序和快速排序

歸併排序 先將問題分解為小問題即乙個個子序列,再將子串行按順序合併。class mergesort mergesort a,0 a.length 1 for int t a public static void mergesort int a,int m,int n public static vo...

歸併排序和快速排序

歸併排序的陣列排序任務可以如下完成 1 把前一半排序 2 把後一半排序 3 把兩半歸併到乙個新的有序陣列,然後再拷貝回原陣列,排序完成。include using namespace std void merge int a,int s,int m,int e,int tmp while p1 m ...

快速排序和歸併排序

遞推公式 merge sort p,r merge merge sort p,q merge sort q 1 r 終止條件 p r 10組測試資料 for let i 0 i 10 i 生成10個隨機元素的測試陣列 function gettestdata return ret 排序函式 func...