基礎算法學習筆記—歸併排序

由於最近本人要參加面試,複習了一下本科學習過的一些重要的基礎算法,在此總結歸納知識點,內容主要參考自百度百科並做了一些相應的修改。

歸併排序

1)算法思想:歸併排序是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱爲二路歸併。

歸併過程爲:比較a[i]和a[j]的大小,若a[i]≤a[j],則將第一個有序表中的元素a[i]複製到r[k]中,並令i和k分別加上1;否則將第二個有序表中的元素a[j]複製到r[k]中,並令j和k分別加上1,如此循環下去,直到其中一個有序表取完,然後再將另一個有序表中剩餘的元素複製到r中從下標k到下標t的單元。歸併排序的算法我們通常用遞歸實現,先把待排序區間[s,t]以中點二分,接着把左邊子區間排序,再把右邊子區間排序,最後把左區間和右區間用一次歸併操作合併成有序的區間[s,t]。

2)算法介紹:歸併操作(merge),也叫歸併算法,指的是將兩個順序序列合併成一個順序序列的方法。

如 設有數列{6,202,100,301,38,8,1}

初始狀態:6,202,100,301,38,8,1

第一次歸併後:{6,202},{100,301},{8,38},{1},比較次數:3;

第二次歸併後:{6,100,202,301},{1,8,38},比較次數:4;

第三次歸併後:{1,6,8,38,100,202,301},比較次數:4;

總的比較次數爲:3+4+4=11,;

逆序數爲14;

歸併操作的工作原理如下:

第一步:申請空間,使其大小爲兩個已經排序序列之和,該空間用來存放合併後的序列

第二步:設定兩個指針,最初位置分別爲兩個已經排序序列的起始位置

第三步:比較兩個指針所指向的元素,選擇相對小的元素放入到合併空間,並移動指針到下一位置

重複步驟3直到某一指針超出序列尾

將另一序列剩下的所有元素直接複製到合併序列尾

3)用途:

速度僅次於快速排序,爲穩定排序算法,一般用於對總體無序,但是各子項相對有序的數列。

4)代碼實現:

public classMergeSort {
         /**
          * 歸併排序
          * 簡介:將兩個(或兩個以上)有序表合併成一個新的有序表 即把待排序序列分爲若干個子序列,每個子序列是有序的。然後再把有序子序列合併爲整體有序序列
          * 時間複雜度爲O(nlogn)
          * 穩定排序方式
          * @param nums 待排序數組
          * @return 輸出有序數組
          */
         public static int[] sort(int[] nums,int low, int high) {
                   int mid = (low + high) / 2;
                   if (low < high) {
                            // 左邊
                            sort(nums, low,mid);
                            // 右邊
                            sort(nums, mid + 1,high);
                            // 左右歸併
                            merge(nums, low,mid, high);
                   }
                   return nums;
         }
 
         public static void merge(int[] nums,int low, int mid, int high) {
                   int[] temp = new int[high -low + 1];
                   int i = low;// 左指針
                   int j = mid + 1;// 右指針
                   int k = 0;
 
                   // 把較小的數先移到新數組中
                   while (i <= mid &&j <= high) {
                            if (nums[i] <nums[j]) {
                                     temp[k++] =nums[i++];
                            } else {
                                     temp[k++] =nums[j++];
                            }
                   }
 
                   // 把左邊剩餘的數移入數組
                   while (i <= mid) {
                            temp[k++] =nums[i++];
                   }
 
                   // 把右邊邊剩餘的數移入數組
                   while (j <= high) {
                            temp[k++] =nums[j++];
                   }
 
                   // 把新數組中的數覆蓋nums數組
                   for (int k2 = 0; k2 <temp.length; k2++) {
                            nums[k2 + low] =temp[k2];
                   }
         }
}

5)複雜度分析

歸併排序的最好、最壞和平均時間複雜度都是O(nlogn),而空間複雜度是O(n)歸併排序算法比較佔用內存,但卻是效率高且穩定的排序算法。(若從空間複雜度來考慮:首選堆排序,其次是快速排序,最後是歸併排序。若從穩定性來考慮,應選取歸併排序,因爲堆排序和快速排序都是不穩定的。若從平均情況下的排序速度考慮,應該選擇快速排序。 

可以說合並排序是比較複雜的排序,特別是對於不瞭解分治法基本思想的同學來說可能難以理解。總時間=分解時間+解決問題時間+合併時間。分解時間就是把一個待排序序列分解成兩序列,時間爲一常數,時間複雜度o(1).解決問題時間是兩個遞歸式,把一個規模爲n的問題分成兩個規模分別爲n/2的子問題,時間爲2T(n/2).合併時間複雜度爲o(n)。總時間T(n)=2T(n/2)+o(n).這個遞歸式可以用遞歸樹來解,其解是o(nlogn).此外在最壞、最佳、平均情況下歸併排序時間複雜度均爲o(nlogn).從合併過程中可以看出合併排序穩定。 

用遞歸樹的方法解遞歸式T(n)=2T(n/2)+o(n):假設解決最後的子問題用時爲常數c,則對於n個待排序記錄來說整個問題的規模爲cn。

 

從這個遞歸樹可以看出,第一層時間代價爲cn,第二層時間代價爲cn/2+cn/2=cn.....每一層代價都是cn,總共有logn+1層。所以總的時間代價爲cn*(logn+1).時間複雜度是o(nlogn).

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章