之前看了選擇和插入排序,這兩個算法是的時間復雜度均為O(n^2),而隨着問題規模n的增大,插入和選擇排序都比較慢。
歸並排序時的時間復雜度為O(nlgn) 其主要思想是分治法(divide and conquer),分就是要將n個元素的序列划分為兩個序列,再將兩個序列划分為4個序列,
直到每個序列只有一個元素,最后,再將兩個有序序列歸並成一個有序的序列。
例如兩個序列:
要歸並成一個有序的序列,按照我們常規的方法,我們每次從兩個列表開頭元素選取較小的一個,直到某一個列表到達底部,再將另一個剩下部分順序取出。其實如果將每個元素最后添加一個最大值,則無需判斷是否達到列表盡頭。
代碼如下:merge函數的功能為將A中[low,mid],[mid+1,high]歸並成一個有序的片段。
template<class T> void merge(T A[],int low,int mid,int high) { //low to mid as the left array mid+1 to high as the right array int llen = mid-low+2; int rlen = high-mid+1; T *left = (T*)new T[llen]; T *right = (T*)new T[rlen]; //copy the low to mid to the temp array left for (int i=0;i<llen-1;i++) { left[i] = A[low+i]; } for (i=0;i<rlen-1;i++) { right[i] = A[mid+1+i]; } //set the sentinel left[llen-1] = numeric_limits<T>::max(); right[rlen-1]= numeric_limits<T>::max(); //merge the two array and copy to A[low,high]; int j = 0; int k = 0; for (i = low; i < high+1 ;i++) { if (left[j] < right[k]) { A[i] = left[j]; j++; } else { A[i] = right[k]; k++; } }
delete [] left;
delete [] right; }
歸並排序就是多次調用merge
template<class T> merge_sort(T A[],int low,int high) { int mid = (low+high)/2; if (low < high) { merge_sort(A,low,mid); merge_sort(A,mid+1,high); merge(A,low,mid,high); } }
這是一個遞歸算法,這個算法的理解其實可以借助下面這個圖:
對於原始的數組2,1,3,8,5,7,6,4,10,在整個過程執行的是順序是途中紅色編號1-20。雖然我們描述中說的是程序先分解,再歸並,但實際過程是一邊分解一邊歸並,前半部分分先排好序,后半部分再拍好,最后整個歸並為一個完整的序列,途中的merge過程它所在層的兩個序列的merge過程:下圖展示了每個merge過程對作用於數組的哪部分(紅色)。
整個過程就像一個動態的樹,執行順序就是對樹的先序遍歷順序。
最后實驗驗證,對10000個倒序的數組進行排序,直接插入排序需1024ms,二歸並排序只需20ms。