歸併排序的核心思想是,對兩個有序的集合進行排序。java
實現步驟,以下:
1.有序集合a
2.有序集合b
3.對集合a和集合b進行歸併排序,排序以後放入集合c,而且集合c有序。算法
有2種實現方案,
1.遞歸
2.非遞歸編程
歸併排序能夠實現對2個有序的集合進行排序,而且把排序以後的數據放到第3個集合。代碼以下。數組
package sort.mergeSort; /** * * <pre> * 類的名稱: * 類的做用:歸併排序 * 1.2個有序的集合 * 2.放入第三個集合而且仍然有序 * </pre> * * @author gzh * @date 2017年11月28日下午6:07:47 */ public class MergeSortNotRecursion { public static void main(String[] args) { // 定義數據 int[] a = { 1, 3, 5, 7, 9 }; int[] b = { 2, 4, 6, 8, 10 }; int[] c = new int[10]; // 排序 mergeSort(a, a.length, b, b.length, c); // 打印數據 outData(c); } /** * * <pre> * 歸併排序 * </pre> * * @param a 第一個集合 * @param sizeA 第一個集合的大小 * @param b 第二個集合 * @param sizeB 第二個集合的大小 * @param c 第三個集合 * @author gzh * @date 2017年11月28日下午6:13:04 */ private static void mergeSort(int[] a, int sizeA, int[] b, int sizeB, int[] c) { // 遍歷,直到遍歷完集合a或集合b int indexA = 0, indexB = 0, indexC = 0; while (indexA < sizeA && indexB < sizeB) { if (a[indexA] < b[indexB]) { c[indexC++] = a[indexA++]; } else { c[indexC++] = b[indexB++]; } } // 若是集合a沒有遍歷完,繼續遍歷a while (indexA < sizeA) { c[indexC++] = a[indexA++]; } // 若是集合b沒有遍歷完,繼續遍歷b while (indexB < sizeB) { c[indexC++] = b[indexB++]; } } /** * * <pre> * 打印數據 * </pre> * * @param c * @author gzh * @date 2017年11月28日下午6:10:36 */ private static void outData(int[] c) { for (int i = 0; i < c.length; i++) { System.out.print(c[i] + " "); } } } 日誌 1 2 3 4 5 6 7 8 9 10
可是,實際編程的時候,通常是要求對一個集合進行排序。數據結構
實現的核心,仍是歸併排序算法。數據結構和算法
不過,這裏的歸併排序算法,須要稍微修改一下,由於剛纔上面的算法是對2個排序的集合進行排序,而後再放入第3個集合,總共涉及到3個集合。spa
修改以後的歸併排序算法,是直接對待排序的集合進行排序,也就是說,只涉及到一個集合。具體實現細節,基本同樣。有一點須要注意的是,在歸併排序類的內部,也就是排序算法使用到了一個臨時集合。設計
最後總結一下,所謂歸併排序的遞歸實現,就是遞歸調用歸併排序方法。代碼以下。日誌
package sort.mergeSort; /** * * <pre> * 類的名稱: * 類的做用:歸併排序(遞歸實現) * </pre> * * @author gzh * @date 2017年11月29日上午9:44:23 */ public class MergeSortRecursion { // 定義數據 private static int[] a = { 9, 1, 3, 2, 4, 0, 7}; public static void main(String[] args) { // 排序 int[] temp = new int[a.length]; mergeSortRecursion(temp, 0, a.length - 1); } private static int space = 0; /** * * <pre> * 遞歸調用歸併排序 * </pre> * * @param temp * 臨時集合 * @param lowerBound * 當前遞歸的起始位置 * @param upperBound * 當前遞歸的結束位置 * @author gzh * @date 2017年11月29日上午9:57:00 */ private static void mergeSortRecursion(int[] temp, int lowerBound, int upperBound) { if (space!=0) { //第一次調用時,不須要打印空格 outSpace(++space); } System.out.print("當前遞歸開始:" + lowerBound + "~" + upperBound); outData(lowerBound,upperBound); if (lowerBound == upperBound) { // 若是隻有一個元素,遞歸結束 outSpace(space); System.out.print("當前遞歸結束(只有一個元素,遞歸結束):" + lowerBound + "~" + upperBound); outData(lowerBound,upperBound); return; } else { // 不然,遞歸調用合併排序 int middle = (lowerBound + upperBound) / 2; outSpace(++space); System.out.print("排序左邊:" + lowerBound + "~" + middle); outData(lowerBound,middle); mergeSortRecursion(temp, lowerBound, middle); outSpace(--space); System.out.print("排序右邊:" + (middle + 1) + "~" + upperBound); outData((middle + 1),upperBound); mergeSortRecursion(temp, middle + 1, upperBound); mergeSort(temp, lowerBound, middle, middle + 1, upperBound); space-=2; outSpace(space); System.out.print("當前遞歸結束:" + lowerBound + "~" + upperBound); outData(lowerBound,upperBound); } } /** * * <pre> * 輸出指定範圍的元素 * </pre> * @param lowerBound 開始位置 * @param upperBound 結束位置 * @author gzh * @date 2017年11月29日下午2:04:12 */ private static void outData(int lowerBound, int upperBound) { System.out.print("——"); for (int i = lowerBound; i <= upperBound; i++) { System.out.print(a[i] + " "); } System.out.println(); } /** * * <pre> * 打印空格 * </pre> * @param n 空格數量 * @author gzh * @date 2017年11月29日下午12:59:27 */ private static void outSpace(int n) { for (int i = 0; i < n; i++) { System.out.print(" "); } } /** * * <pre> * 歸併排序 * </pre> * * @param temp * 臨時集合 * @param firstStart * 第一個集合的起始位置 * @param firstEnd 第一個集合的結束位置 * @param indexSecond * 第二個集合的起始位置 * @param secondEnd * 第二個集合的結束位置 * @author gzh * @date 2017年11月29日上午10:01:47 */ private static void mergeSort(int temp[], int firstStart, int firstEnd, int indexSecond, int secondEnd) { //保存臨時值 int indexFirstTemp = firstStart; //計算排序元素的數量 int n = secondEnd-firstStart + 1; int sizeFirst = indexSecond - 1; // 計算第一個集合的大小 // 遍歷,直到遍歷完集合a或集合b int i = 0; while (firstStart <= sizeFirst && indexSecond <= secondEnd) { if (a[firstStart] < a[indexSecond]) { temp[i++] = a[firstStart++]; } else { temp[i++] = a[indexSecond++]; } } // 若是集合a沒有遍歷完,繼續遍歷 while (firstStart <= sizeFirst) { temp[i++] = a[firstStart++]; } // 若是集合b沒有遍歷完,繼續遍歷 while (indexSecond <= secondEnd) { temp[i++] = a[indexSecond++]; } // 複製數據到原始集合 for (i = 0; i < n; i++) { a[indexFirstTemp+i] = temp[i]; } } /** * * <pre> * 打印數據 * </pre> * * @param c 集合 * @author gzh * @date 2017年11月28日下午6:10:36 */ private static void outData(int[] c) { for (int i = 0; i < c.length; i++) { System.out.print(c[i] + " "); } } } 日誌 當前遞歸開始:0~6——9 1 3 2 4 0 7 排序左邊:0~3——9 1 3 2 當前遞歸開始:0~3——9 1 3 2 排序左邊:0~1——9 1 當前遞歸開始:0~1——9 1 排序左邊:0~0——9 當前遞歸開始:0~0——9 當前遞歸結束(只有一個元素,遞歸結束):0~0——9 排序右邊:1~1——1 當前遞歸開始:1~1——1 當前遞歸結束(只有一個元素,遞歸結束):1~1——1 當前遞歸結束:0~1——1 9 排序右邊:2~3——3 2 當前遞歸開始:2~3——3 2 排序左邊:2~2——3 當前遞歸開始:2~2——3 當前遞歸結束(只有一個元素,遞歸結束):2~2——3 排序右邊:3~3——2 當前遞歸開始:3~3——2 當前遞歸結束(只有一個元素,遞歸結束):3~3——2 當前遞歸結束:2~3——2 3 當前遞歸結束:0~3——1 2 3 9 排序右邊:4~6——4 0 7 當前遞歸開始:4~6——4 0 7 排序左邊:4~5——4 0 當前遞歸開始:4~5——4 0 排序左邊:4~4——4 當前遞歸開始:4~4——4 當前遞歸結束(只有一個元素,遞歸結束):4~4——4 排序右邊:5~5——0 當前遞歸開始:5~5——0 當前遞歸結束(只有一個元素,遞歸結束):5~5——0 當前遞歸結束:4~5——0 4 排序右邊:6~6——7 當前遞歸開始:6~6——7 當前遞歸結束(只有一個元素,遞歸結束):6~6——7 當前遞歸結束:4~6——0 4 7 當前遞歸結束:0~6——0 1 2 3 4 7 9
上面介紹的遞歸實現,設計思路是:
1.合併排序算法
2.遞歸調用合併排序算法code
非遞歸實現,設計思路同樣,也是:
1.合併排序算法
2.非遞歸調用合併排序算法
重點來了,遞歸實現和非遞歸的實現,相同點都是基於合併排序算法,不一樣的地方只是在於怎麼去調用合併排序算法。
遞歸調用,就是遞歸方法不斷地調用本身,遞歸結束的標誌是當前待排序的集合只有一個元素。
非遞歸調用,就是採用循環遍歷的思想(即迭代),去不斷地調用合併排序算法,迭代結束的標誌是當前待排序的集合大小等於待排序集合的大小。
package sort.mergeSort; import java.util.Arrays; /** * * <pre> * 類的名稱: * 類的做用:歸併排序(非遞歸實現,即採用多層遍歷的思想) * </pre> * * @author gzh * @date 2017年11月30日上午9:53:56 */ public class MergeSortNotRecursion { private static int[] a = { 9, 1, 3, 2, 4}; public static void main(String[] args) { merge_sort(a); outData(a); } /** * * <pre> * 歸併排序(非遞歸實現,即採用多層遍歷的思想) * </pre> * @param arr 待排序的集合 * @author gzh * @date 2017年11月30日上午9:59:15 */ public static void merge_sort(int[] arr) { int len = arr.length; int[] result = new int[len]; int block, start; int low,mid,high=0; // 原版代碼的迭代次數少了一次,沒有考慮到奇數列數組的狀況 for (block = 1; block < len * 2; block *= 2) { for (start = 0; start < len; start += 2 * block) { low = start; mid = (start + block) < len ? (start + block) : len; high = (start + 2 * block) < len ? (start + 2 * block) : len; // 兩個塊的起始下標及結束下標 int start1 = low, end1 = mid; int start2 = mid, end2 = high; //排序範圍 System.out.print("當前排序開始:" + start + "~" + (high-1)); //排序範圍 outData(arr,start,(high-1)); //排序元素 //第一個集合 outSpace(1); System.out.print("第一個集合:" + start1 + "~" + (end1-1)); //排序範圍 outData(arr,start1,(end1-1)); //排序元素 //第二個集合 outSpace(1); System.out.print("第二個集合:" + start2 + "~" + (end2-1)); //排序範圍 outData(arr,start2,(end2-1)); //排序元素 // 開始對兩個block進行歸併排序 while (start1 < end1 && start2 < end2) { // result[low++] = arr[start1] < arr[start2] ? arr[start1++] // : arr[start2++]; if (arr[start1] < arr[start2]) { result[low++] = arr[start1++]; } else { result[low++] = arr[start2++]; } } while (start1 < end1) { result[low++] = arr[start1++]; } while (start2 < end2) { result[low++] = arr[start2++]; } System.out.print("當前排序結束:" + start + "~" + (high-1)); //排序範圍 outData(result,start,(high-1)); //排序元素 } //內循環每一輪排序以後,把當前這一輪的排序結果result複製給待排序的集合arr // int[] temp = arr; // arr = result; // result = temp; // for (int i = 0; i < result.length; i++) { //System.arraycopy(),至關因而循環賦值,即只複製值,不改變引用地址的值 // arr[i] = result[i]; // } System.arraycopy(result, 0, arr, 0, result.length); //System.arraycopy()不會改變對象引用arr的值,即arr仍是指向方法傳入參數時的那個集合對象(即待排序的集合對象) // arr = Arrays.copyOf(result, result.length); //Arrays.copyOf()排序以後,會改變對象引用arr的值,即arr會從新指向一個新的集合對象——此時,待排序的集合對象和arr已經沒有關係 outData(arr); System.out.println(); System.out.println(); } } /** * * <pre> * 打印數據 * </pre> * * @param c * @author gzh * @date 2017年11月28日下午6:10:36 */ private static void outData(int[] c) { for (int i = 0; i < c.length; i++) { System.out.print(c[i] + " "); } } // /** // * // * <pre> // * 輸出指定範圍的元素 // * </pre> // * @param lowerBound 開始位置 // * @param upperBound 結束位置 // * @author gzh // * @date 2017年11月29日下午2:04:12 // */ // private static void outData(int[] arr, int lowerBound, int upperBound) { // System.out.print("——"); // // for (int i = lowerBound; i <= upperBound; i++) { // System.out.print(a[i] + " "); // } // // System.out.println(); // } /** * * <pre> * 打印數據 * </pre> * @param arr * @param lowerBound * @param upperBound * @author gzh * @date 2017年11月30日上午11:22:25 */ private static void outData(int[] arr, int lowerBound, int upperBound) { System.out.print("——"); for (int i = lowerBound; i <= upperBound; i++) { System.out.print(arr[i] + " "); } System.out.println(); } /** * * <pre> * 打印空格 * </pre> * @param n 空格數量 * @author gzh * @date 2017年11月29日下午12:59:27 */ private static void outSpace(int n) { for (int i = 0; i < n; i++) { System.out.print(" "); } } } 日誌 當前排序開始:0~1——9 1 第一個集合:0~0——9 第二個集合:1~1——1 當前排序結束:0~1——1 9 當前排序開始:2~3——3 2 第一個集合:2~2——3 第二個集合:3~3——2 當前排序結束:2~3——2 3 當前排序開始:4~4——4 第一個集合:4~4——4 第二個集合:5~4—— 當前排序結束:4~4——4 1 9 2 3 4 當前排序開始:0~3——1 9 2 3 第一個集合:0~1——1 9 第二個集合:2~3——2 3 當前排序結束:0~3——1 2 3 9 當前排序開始:4~4——4 第一個集合:4~4——4 第二個集合:5~4—— 當前排序結束:4~4——4 1 2 3 9 4 當前排序開始:0~4——1 2 3 9 4 第一個集合:0~3——1 2 3 9 第二個集合:4~4——4 當前排序結束:0~4——1 2 3 4 9 1 2 3 4 9 當前排序開始:0~4——1 2 3 4 9 第一個集合:0~4——1 2 3 4 9 第二個集合:5~4—— 當前排序結束:0~4——1 2 3 4 9 1 2 3 4 9 1 2 3 4 9
1.遞歸實現
合併排序算法有一個while循環,因此速度是N。
遞歸調用的時候,不斷地對待排序集合進行折半,拆分紅2個更小的集合,因此速度是logN。
因此,遞歸實現的速度是N * logN。
2.非遞歸實現
兩層嵌套循環,因此速度是N*N。
《java數據結構和算法》-第六章-遞歸-歸併排序