資料結構 排序(二)

2022-08-31 18:24:09 字數 4327 閱讀 9987

歸併排序最壞情況下時間複雜度為o(nlogn),它所使用的比較次數幾乎是最優的。同時歸併排序是分治策略和遞迴策略的乙個非常好的例項。歸併排序的基本操作是合併兩個已經排序的表。對於乙個要排序的陣列,歸併演算法首先將陣列分為兩部分,然後分別對這兩部分遞迴呼叫自己,這個遞迴演算法的遞迴終止條件是陣列長度為一,此時結束遞迴然後返回,演算法執行合併兩個陣列的操作。合併操作也比較簡單,最簡單的方式是使用3個陣列a,b,c。其中a和b是要合併的陣列,c是合併後的陣列。演算法遍歷a和b,從a和b中選擇較小的元素放到c中,直到a和b中都沒有元素為止。下面給出乙個例程:

//驅動例程

void mergesort(elementtype a,int n)

elementtype *tmparray;

tmparray=malloc(n*sizeof(elementtype));

if(tmparray!=null)

msort(a,tmparray,0,n-1);

free(temparray);

else

error();

//遞迴呼叫的例程

void msort(elementtype a,elementtype tmparray,int left,int right)

int center;

if(leftcenter=(left+right)/2;

msort(a,tmparray,left,center);

msort(a,tmparray,center+1,right);

merge(a,tmparray,left,center+1,right);

其中的關鍵在於merge函式,這個函式負責合併兩個已排序陣列,這個函式的精妙之處在於使用了乙個大的活動陣列作為臨時陣列儲存排序數字,而不是使用很多小的區域性陣列作為c陣列,這樣對於小記憶體時比較適用,例程如下:

void merge(elementtype a,elementtype tmparray,int lpos,int rpos,int rightend)

int i,leftend,numelements,tmppos;

//進行合併要使用的變數

leftend=rpos-1;

tmppos=lpos;

numelements=rightend-lpos+1;

/*主迴圈*/

while(tmppos<=leftend&&rpos<=rightend)

if(a[lpos]<=a[rpos])

tmparray[tmppos++]=a[lpos++];

else

tmparray[tmppos++]=b[rpos++];

//如果還有沒有放到tmparray中的元素放到臨時陣列中

while(lpos<=leftend)

tmparray[tmppos++]=a[lpos++];

while(rpos<=rightend)

tmparray[tmppos++]=a[rpos++];

//將臨時陣列中的資料拷貝回待排序陣列

for(i=0;ia[rightend]=tmparray[rightend];

歸併排序就是將問題分為小的問題,先解決小的問題,然後將小問題的結果合併到一起。在外排序中,歸併排序的思想就是外排序思想的基石。

快速排序是目前已知的速度最快的演算法,它的平均執行時間是o(nlogn),最壞情況下的時間複雜度為o(n*n),但是這種最壞情況可以很容易的避免。雖然理論上快速排序的時間複雜度並不比歸併排序少,但在實際執行過程中它的速度高於歸併排序。首先研究一下快速排序的基本思路(要排序的陣列為s):

如果s中元素個數是0或者1,則返回。

取s中任一元素v,稱之為樞紐元。

將s中剩餘元素分為兩個不相交的集合s1=和s2=。

返回。其中第3步中樞紐元的選擇是演算法的關鍵,這個選擇導致了演算法的時間複雜度的區別,好的選擇可以是s1與s2保持均衡,類似與二叉查詢樹中保持平衡一樣。一種好的選擇是三數中值分割法,選擇待排序陣列中的最左邊最右邊和中間的資料元素的中值。這種選擇方法一般就可以保證快速排序的快速執行。在確定樞紐元後,就需要對陣列進行分割(將不同大小的資料元素放到不同的集合中),分割陣列有很多種方式,這裡描述一種比價簡便可行的分割方法:首先,將選擇的樞紐元與最後位置的元素進行互換,將樞紐元放到陣列的最後位置。然後從陣列的開始位置和樞紐元的前乙個位置開始,將陣列中的元素進行分割,將樞紐元排除到分割的外面,在分割的最後對樞紐元進行處理。設開始位置為i,樞紐元前乙個位置為j,則i向陣列後面移動,j向陣列前面移動,i在遇到大於樞紐元的數值時停止,j在遇到小於樞紐元的數值時停止,如果ij。最後將i所指位置的元素與樞紐元交換位置。經過這樣分割後,在樞紐元左側的元素都小於樞紐元,右邊的都大於樞紐元。值得注意的是在遇到與樞紐元相等的元素時i與j是否應該停止。這種情況下,i於j應該有相同的行為,否則分割出的兩個子陣列會產生傾斜。經分析可知,如果i和j在遇到相同元素時不停止,則會產生不平衡的劃分,如果停止產生的劃分較為均衡,但是會產生更多的交換。停止操作導致的時間複雜度為o(nlogn),不平衡的時間複雜度為o(n*n)。因此選擇在遇到與樞紐元相等的元素時停止的機制。需要注意的是,當陣列很小時,快速排序的執行速度小於插入排序,原因在於快速排序使用到了遞迴,導致更長的時間。因此在陣列較小時(n<=10)應該選擇插入排序。下面研究下快速排序的例程:

void quicksort(elementtype a,int n)

qsort(a,0,n-1);

這個是快速排序演算法的驅動例程,主要負責呼叫真正的快排演算法,並向快排演算法中傳遞要排序的陣列和陣列的左端和右端,qsort演算法主要負責選取樞紐元和分割資料元素的工作。其中選取樞紐元使用三數中值分割法,具體例程如下:

elementtype median3(elementtype a,int left,int right)

int center=(left+right)/2;

if(a[left]>a[center])

swap(&a[left],&a[center]);

if(a[left]>a[right])

swap(&a[left],&a[right]);

if(a[center]>a[right])

swap(&a[center],&a[right]);

swap(&a[center],&a[right-1]);

return a[right-1];

從例程可以看出,這個例程不僅返回了樞紐元,而且將進行判斷的三個數字進行了交換,將最小的元素放到最左邊,最大的元素放到最右邊,樞紐元放到最大元素的左邊。這樣在後續的資料元素分割過程中,可以不用從最左邊和最右邊的位置開始判斷,而是從最左邊元素的右邊元素和樞紐元的左邊元素開始判斷,減少進行的判斷次數,實際上,這一優化極大地減少了執行時間。下面研究下qsort排序演算法的例程:

#define cutoff (3)

void qsort(elementtype a,int left,int right)

int i,j;

element pivot;

if(left+cutoff<=right)

pivot=median3(a,left,right);

i=left;                               //最左邊的位置

j=right-1;                         //樞紐元的位置

for(;;)

while(a[++i]while(a[--j]>pivot){}

if(iswap(&a[i],&a[j]);

else

break;

swap(&a[i],&a[right-1]);//將樞紐元換到中間位置處

qsort(a,left,i-1);

qsort(a,i+1,right);

else       //排序元素過少時,使用插入排序(也是乙個遞迴終止條件)

insertionsort(a+left,right-left+1);

分析可知,快速排序最壞情況下的時間複雜度為o(n*n),平均情況和最好情況下時間複雜度都為o( nlogn)。可以將快速排序法稍加改造應用到選擇問題上。

當對大型結構排序時,在需要交換時,資料的交換是非常昂貴的操作,這種情況可以使用指標來進行交換,從而減少資料交換的昂貴操作,稱為間接排序。另外,在基於比較的排序演算法中,利用決策樹可以證明,基於比較的排序演算法時間複雜度的下界為o(nlogn)。在特殊情況下,排序演算法的時間複雜度可以降低到線性時間複雜度,最為典型的是桶排序。這樣的排序需要額外的資訊,要排序的資料a1到an必須小於m,則建立乙個大小為m的陣列count並且初始化為0,讀入a陣列後,每個ai都執行count[ai]++。這樣讀完a後,掃瞄count輸出排序後的陣列,桶排序的時間複雜度為o(m+n)。另外,當要排序的資料無法全部讀入記憶體的情況下,需要使用外排序的演算法,簡單來說就是使用歸併排序的思想,首先排序幾塊,然後將每乙個排序後的塊結果合併起來。

資料結構之排序(二)

本次所介紹的排序分別是堆排序 歸併排序 快速排序。void swap int a,int i,int j 交換下標為i和j的兩個元素的值 void printa int a,int len 列印陣列 堆排序 堆排序是指利用堆這種資料結構所設計的一種排序演算法,它是選擇排序的一種。在從小到大排序中,需...

資料結構與演算法 排序(二)

接上篇,繼續介紹歸併排序與快速排序。歸併排序使用的是分治策略,核心思想也比較簡單。要排序乙個序列,首先從中間將序列一分為二,然後對劃分的前後兩個序列進行排序,再將排序好的序列合而為一,這樣原有的序列就已有序了。可見,歸併排序的核心在於合併有序序列。以下圖為例,有兩個待合併有序序列 5,6,8,9,與...

資料結構 排序

小小總結了下 希望別不記得 排序型別 排序方法 平均時間 最壞時間 最好時間 穩定空間 插入直接插入 o n2 o n2 o n 穩定o 1 希爾排序 o n3 2 增量序列最後為1,只有公因子1 不穩o 1 選擇簡單選擇 o n2 o n2 o n 穩定o 1 堆排序o n lb n o n lb...