算法-排序-歸併排序

核心思想

歸併排序的核心思想是,對兩個有序的集合進行排序。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數據結構和算法》-第六章-遞歸-歸併排序

https://zh.wikipedia.org/wiki...

相關文章
相關標籤/搜索