排序算法之——歸並排序(兩種方法及其優化)


本文將圍繞代碼從多個方面分析歸並算法,歸並的操作很簡單,稍加思考便能深刻理解。

1、算法思想:

要將一個數組排序,可以(遞歸地)將數組分成兩半分別排序,然后將兩邊歸並起來。歸並算法最吸引人的地方是它能保證將任意長度為N的數組排序的時間與NlgN成正比。

主要缺點是需要與N成正比的額外空間。

 (示意圖1)

 

2、原地歸並的抽象方法

實現歸並最直截了當的方法是將兩個數組歸並到第三個數組,實現的方法很簡單,從左到右逐一比較兩數組的第一位元素,將小的一個放入第三個數組(假設兩數組已經有序),完成操作后第三個數組就是有序的。了解了思路,我們直接看代碼。

 1     public static void merge(Comparable[] a, int lo, int mid, int hi) {  2         int i = lo, j = mid + 1;  3         for (int k = lo; k <= hi; k++) {  4             aux[k] = a[k];  5  }  6         for (int k = lo; k <= hi; k++) {  7             if (i > mid) {  8                 a[k] = aux[j++];  9             } else if (j > hi) { 10                 a[k] = aux[i++]; 11             } else if (less(aux[i], aux[j])) { 12                 a[k] = aux[i++]; 13             } else { 14                 a[k] = aux[j++]; 15  } 16  } 17 
18     }

主要操作就是第二個for循環里的四個判斷:

1、數組1走完(將數組2當前元素放入數組3)

2、數組2走完(將數組1當前元素放入數組3)

3、數組1當前元素小於數組2當前元素(將數組1當前元素放入數組3)

4、數組2當前元素小於等於數組1當前元素(將數組2當前元素放入數組3)

 

(示意圖2:將數組1和組2歸並到組3)

 

 

3、自頂向下的歸並排序

如果能將兩個子數組排序,就能通過並歸兩個子數組來對整個數組排序,這一切是通過遞歸實現的,也叫遞歸歸並。直接看代碼:

 

 1 public class Merge{  2     private static Comparable[] aux;  3     public static void sort(Comparable[] a) {  4         aux = a.clone();// 一次性分配空間
 5         sort(a,0, a.length - 1);  6  }  7 
 8     private static void sort(Comparable[] a,int lo, int hi) {  9         if (hi <= lo) { 10             return; 11  } 12         int mid = lo + (hi - lo) / 2; 13         sort(aux,a, lo, mid);//左半邊排序
14         sort(aux,a, mid + 1, hi);//右半邊排序
15         merge(a,aux,lo, mid, hi);//歸並結果(參考原地歸並的抽象方法)
16  } 17 }

 

 

 

示意圖:

(示意圖3)

上圖只是merge方法的軌跡,sort方法也極為重要,要想理解就必須知道sort方法調用的軌跡(這里請讀者自己先寫出sort的軌跡再看下面的答案)

 

sort(a,0,7)

將左半部分排序

sort(a,0,3)

sort(a,0,1)

merge(a,0,0,1)

sort(a,2,3)

merge(a,2,2,3)

將右半部分排序

sort(a,4,7)

sort(a,4,5)

merge(a,4,4,5)

sort(a,6,7)

merge(a,6,6,7)

歸並結果

merge(a,0,3,7)

 

4、自底向上的歸並排序

我們已經知道,自頂向下采用的是遞歸的方法,而自底向上則是循序漸進得解決問題,采用了循環的方法。通過下圖可以很容易看出兩種方式的區別:

 

下面上代碼:

1     public static void sort(Comparable[] a) { 2         int n = a.length; 3         aux = new Comparable[n]; 4         for (int sz = 1; sz < n; sz = sz + sz) { 5             for (int lo = 0; lo < n - sz; lo += sz + sz) { 6                 merge(a, lo, lo + sz - 1, Math.min(lo + 2 * sz - 1, n - 1));// 最后一次並歸的第二個子數組可能比第一個小此時lo+2*sz-1越界
7  } 8  } 9     }

 

讀者自行考慮自底向上方法的運行軌跡。

 

5、三項優化(代碼在后面的代碼演示中)

①對小規模子數組使用插入排序

用不同的方法處理小規模數組能改進大多遞歸算法的性能,在小數組上上,插入排序可能比並歸排序更快。

②測試數組是否有序

根據歸並排序的特點,每次歸並的兩個小數組都是有序的,當a[mid]<=a[mid+1]時我們可以跳過merge方法,這樣並不影響排序的遞歸調用。

③不將元素復制到輔助數組

我們可以節省將數組復制到輔助數組的時間,這需要一些技巧。先克隆原數組到輔助數組,然后在之后的遞歸交換輸入數組和輔助數組的角色(通過看代碼更容易理解)

 

(畫方框的為每次的輸出數組)

 

 

6、代碼演示(java):

 1 public class Merge implements Comparable<Merge> {// 歸並排序(優化前)  2 private static Comparable[] aux;  3  4 private static boolean less(Comparable v, Comparable w) {  5 return v.compareTo(w) < 0;  6  }  7  8  @Override  9 public int compareTo(Merge arg0) { 10 // TODO Auto-generated method stub 11 return 0; 12  } 13 14 public static void merge(Comparable[] a, int lo, int mid, int hi) {// 原地歸並的抽象方法 15 int i = lo, j = mid + 1; 16 for (int k = lo; k <= hi; k++) { 17 aux[k] = a[k]; 18  } 19 for (int k = lo; k <= hi; k++) { 20 if (i > mid) { 21 a[k] = aux[j++]; 22 } else if (j > hi) { 23 a[k] = aux[i++]; 24 } else if (less(aux[j], aux[i])) { 25 a[k] = aux[j++]; 26 } else { 27 a[k] = aux[i++]; 28  } 29  } 30  } 31 32 public static void sort(Comparable[] a) { 33 aux = new Comparable[a.length]; 34 sort(a, 0, a.length - 1); 35  } 36 37 private static void sort(Comparable[] a, int lo, int hi) { 38 /* 39  * 自頂向下的並歸排序 三個改進 40 */ 41 if (hi <= lo) { 42 return; 43  } 44 int mid = lo + (hi - lo) / 2; 45  sort(a, lo, mid); 46 sort(a, mid + 1, hi); 47  merge(a, lo, mid, hi); 48  } 49 50 private static void exch(Comparable[] a, int j, int i) { 51 // TODO Auto-generated method stub 52  Comparable temp; 53 temp = a[j]; 54 a[j] = a[i]; 55 a[i] = temp; 56  } 57 58 public static void main(String[] args) { 59 Merge mg = new Merge(); 60 Comparable a[] = { 8, 1, 6, 8, 4, 6, 9, 7, 1, 2, 3, 4, 8, 5, 2, 6, 4, 3, 8 }; 61  mg.sort(a); 62 for (int i = 0; i < a.length; i++) { 63 System.out.print(a[i] + " "); 64  } 65  } 66 }

 

 

 1 public class MergeX implements Comparable<Merge> {// 歸並排序(優化后)
 2     private static Comparable[] aux;  3 
 4     private static boolean less(Comparable v, Comparable w) {  5         return v.compareTo(w) < 0;  6  }  7 
 8  @Override  9     public int compareTo(Merge arg0) { 10         // TODO Auto-generated method stub
11         return 0; 12  } 13 
14     public static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {// 原地歸並的抽象方法
15         int i = lo, j = mid + 1; 16         // for (int k = lo; k <= hi; k++) { 17         // aux[k] = a[k]; 18         // }
19         for (int k = lo; k <= hi; k++) { 20             if (i > mid) { 21                 a[k] = aux[j++]; 22             } else if (j > hi) { 23                 a[k] = aux[i++]; 24             } else if (less(aux[j], aux[i])) { 25                 a[k] = aux[j++]; 26             } else { 27                 a[k] = aux[i++]; 28  } 29  } 30  } 31 
32     public static void sort(Comparable[] a) { 33         aux = a.clone();// 一次性分配空間
34         sort(a, aux, 0, a.length - 1); 35  } 36 
37     private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) { 38         /*
39  * 自頂向下的並歸排序 三個改進 40          */
41         // if (hi <= lo) { 42         // return; 43         // }
44         int mid = lo + (hi - lo) / 2; 45         if (hi - lo <= 7) {// 對小規模子數組使用插入排序 46             //System.out.println("insert!");
47  insertionSort(a, lo, hi); 48             return; 49  } 50  sort(aux, a, lo, mid); 51         sort(aux, a, mid + 1, hi); 52         if (!less(aux[mid + 1], aux[mid])) {// 已經有序時跳過merge(a中lo到mid mid到hi分別都是有序的)
53             System.arraycopy(aux, lo, a, lo, hi-lo+1); 54             return; 55  } 56  merge(a, aux, lo, mid, hi); 57  } 58 
59     private static void insertionSort(Comparable[] a, int lo, int hi) { 60         for (int i = lo; i <= hi; i++) 61             for (int j = i; j > lo && less(a[j], a[j - 1]); j--) 62                 exch(a, j, j - 1); 63  } 64 
65     private static void exch(Comparable[] a, int j, int i) { 66         // TODO Auto-generated method stub
67  Comparable temp; 68         temp = a[j]; 69         a[j] = a[i]; 70         a[i] = temp; 71  } 72 
73     public static void main(String[] args) { 74         MergeX mgx = new MergeX(); 75         Comparable a[] = { 8, 1, 6, 8, 4, 6, 9,7,1, 2, 3,4,8,5,2,6,4,3,8}; 76  mgx.sort(a); 77         for (int i = 0; i < a.length; i++) { 78             System.out.print(a[i] + " "); 79  } 80  } 81 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM