歸併排序:
基本原理:與插入排序使用的“增量”方法不同,歸併排序使用另外一種策略:分治法
分治策略基本思想:將原問題劃分成n個規模較小而結構與原問題相似的小問題;遞迴地解決這些子問題,然後再合并其結果,就得到原問題的解。
分治模式在每一層遞迴上都有三個步驟;
分解(Divide):將原問題分解成這一系列子問題。
解決(Conquer):遞迴地解各子問題。若子問題足夠小,則直接求解。
合并(Combine):將子問題的結果合并成原問題的解。
合并排序演算法完全依照了上述模式,直觀地操作如下:
分解:將n個元素分成各含n/2個元素的子序列;
解決:用合并排序法對兩個子序列遞迴地排序;
合并:合并兩個已排序的子序列以得到排序結果;
時間複雜度:O(nlgn)
代碼實現:
#include<stdio.h>#define N 10 int A[N] ;void Merge(/*A*/int p,int q,int r) ;void MergeSort(/*A*/int p,int r) ;int main(void){int i,n ;freopen("in.txt","r",stdin) ;while(scanf("%d",&n) != EOF){for(i = 0 ; i < n ; i++){scanf("%d",&A[i]) ;}printf("You have input the List:\n") ;for(i = 0 ; i < n ; i++){printf("%-3d",A[i]) ;}printf("\n") ;MergeSort(/*A*/0,n-1) ; //not n but n-1printf("After Sort:\n") ;for(i = 0 ; i < n ; ++i){printf("%-3d",A[i]) ;}printf("\n\n") ;}return 0 ;}void MergeSort(/*A*/int p,int r) {int q = 0 ;if(p < r){q = p + (r-p)/2 ; //noticeMergeSort(/*A*/p,q) ;MergeSort(/*A*/q+1,r) ;Merge(/*A*/p,q,r) ;}}void Merge(/*A*/p,q,r) {int L[N], R[N] ;int i,j,k ;int nLeft,nRight ;nLeft = q + 1 - p ; //noticenRight = r - q ; //noticefor(i = 0 ; i < nLeft ; ++i ){L[i] = A[p+i] ;}for(j = 0 ; j < nRight ; ++j){R[j] = A[q+j+1] ; //notice}i = 0 ;j = 0 ;for(k = p ; k < r && i<nLeft && j<nRight ; k++ ){if(L[i] <= R[j]){A[k] = L[i] ; i++ ;}else{A[k] = R[j] ; j++ ;}}if(i >= nLeft){for(; k <= r ; ++k,++j) {A[k] = R[j] ;}}else{for(; k <= r ; ++k,++i){A[k] = L[i] ;}}}
以上結論摘自《演算法導論》
----------------------------------------------------------------------------
演算法導論思考題:2-4逆序對
d)給出一個演算法,它能用O(nlgn)的最壞情況已耗用時間,確定n個元素的任何排列中逆序對的數目。(提示:修改合并排序)
答:其實就是合并兩個子序列時,出現右邊元素小於左邊元素的情況,亦即R[j]<L[i]時,出現逆序對。此時L[i+1...n1]裡的元素均比R[j]大,而R[j]又在它們的後面。所以,此時的逆序對數:n1-i。後面的元素以此類推。
代碼實現:
#include<stdio.h>#define N 10 int A[N] ;int nTotalInversion = 0 ;void Merge(/*A*/int p,int q,int r) ;void MergeSort(/*A*/int p,int r) ;int main(void){int i,n ;freopen("in.txt","r",stdin) ;while(scanf("%d",&n) != EOF){nTotalInversion = 0 ;for(i = 0 ; i < n ; i++){scanf("%d",&A[i]) ;}printf("You have input the List:\n") ;for(i = 0 ; i < n ; i++){printf("%-3d",A[i]) ;}printf("\n") ;MergeSort(/*A*/0,n-1) ; //not n but n-1printf("The Inversions are %d.\n\n",nTotalInversion) ;}return 0 ;}void MergeSort(/*A*/int p,int r) {int q = 0 ;if(p < r){q = p + (r-p)/2 ; //noticeMergeSort(/*A*/p,q) ;MergeSort(/*A*/q+1,r) ;Merge(/*A*/p,q,r) ;}}void Merge(/*A*/p,q,r) {int L[N], R[N] ;int i,j,k ;int nLeft,nRight ;nLeft = q + 1 - p ; //noticenRight = r - q ; //noticefor(i = 0 ; i < nLeft ; ++i ){L[i] = A[p+i] ;}for(j = 0 ; j < nRight ; ++j){R[j] = A[q+j+1] ; //notice}i = 0 ;j = 0 ;for(k = p ; k < r && i<nLeft && j<nRight ; k++ ){if(L[i] <= R[j]){A[k] = L[i] ; i++ ;}else{nTotalInversion += nLeft - i ; //hereA[k] = R[j] ; j++ ;}}if(i >= nLeft){for(; k <= r ; ++k,++j) {A[k] = R[j] ;}}else{for(; k <= r ; ++k,++i){A[k] = L[i] ;}}}
練習題6.5-8
大概思路:先利用每個鏈表的頭元素來建立一個最大堆或者最大堆,然後再做類似堆排序的操作。但是這裡要注意的是用到了額外空間數組B來儲存排序後的元素。而且在交換樹根和堆的最後一個元素之後,還要檢查鏈表是否為空白,從而更新heap-size[A]的值。時間複雜度:O(nlgk)
虛擬碼實現:
K-Road-MegerSort(A,k,n){BuildMinHeap(A,k) ; //use the K List's first data to build the heapi <- n ;j <- 0 ;nHeapSize <- k ;while i > 0do B[j] <- A[1]->data ; j <- j + 1 ; if A[1]->next = NULL //if the one List is empty. then exchange A[1] <-> A[nHeapSize] ; nHeapSize <- nHeapSize - 1 ; MinHeapify(A,1) ; else then MinHeapify(A,1) ; i <- i - 1 ; }