排序算法系列——歸併排序

歸併排序的核心思想同上一篇介紹的快速排序,都是採用了分治法的思想。其基本思想是將一個待排序序列,劃分成兩個子序列,然後將這兩個子序列排好序之後合併,並遞歸的將子序列劃分爲更小的子序列,一直到只有一個元素的子序列,然後自底向上兩兩合併。
由於歸併排序的核心就是合併兩個數組,所以先看看如何合併兩個有序的數組,並保證合併後的數組是有序的。
數組合並
首先同時遍歷兩個數組(A,B),並將兩個數組的第一個元素進行對比,假設A的第一個元素較小,則將A[0]複製到合併後的數組中,然後比較A[1]與B[0],如果B[0]較小,則將B[0]複製到合併後的數組中,然後比較A[1]與B[1],如果A[1]較小,則將A[1]複製到合併後的數組中,繼續比較A[2]與B[0]。如此重複,直至某一個數組全部複製到合併後的數組,然後將另一個數組中剩下的元素按順序複製到合併後的數組,這樣整個合併就完成了。
基本思想
歸併排序主要採用分治法的思想,將一個問題劃分成多個子問題,然後遞歸解決各個子問題。主要就是將一個待排序序列劃分成兩個子序列,然後再繼續對子序列進行劃分,一直劃分到只有一個元素爲止,然後自下往上兩兩合併子序列,最終完成排序。
實現要點
實現的要點主要就是如何完成合並,上面已經詳細介紹了合併的方法,這裏也不敘述了。直接看下面的實現代碼吧。
Java實現

package com.vicky.sort;

import java.util.Random;

/**
 * 歸併排序
 * 
 * 時間複雜度:O(nlgn)
 * 
 * 空間複雜度:O(n)
 * 
 * 穩定性:穩定
 * 
 * @author Vicky
 * 
 */
public class MergeSort {
    public static <T extends Comparable<T>> void sort(T[] data) {
        long start = System.nanoTime();
        if (null == data) {
            throw new NullPointerException("data");
        }
        if (data.length == 1) {
            return;
        }
        Object[] newData = new Object[data.length];
        devideSort(newData, data, 0, data.length - 1);
        System.out.println("use time:" + (System.nanoTime() - start) / 1000000);
    }

    /**
     * 分治法排序
     * 
     * @param <T>
     * @param newData
     * @param data
     * @param start
     * @param end
     */
    private static <T extends Comparable<T>> void devideSort(Object[] newData,
            T[] data, int start, int end) {
        int middle = (start + end) / 2;
        if (middle > start) {
            devideSort(newData, data, start, middle);
        }
        if (middle + 1 < end) {
            devideSort(newData, data, middle + 1, end);
        }
        mergeArray(newData, data, start, middle, middle + 1, end);
    }

    /**
     * 合併兩個數組
     * 
     * @param <T>
     * @param newData
     *            用於輔助
     * @param data
     *            被合併的數組
     * @param start1
     * @param end1
     * @param start2
     * @param end2
     */
    @SuppressWarnings("unchecked")
    private static <T extends Comparable<T>> void mergeArray(Object[] newData,
            T[] data, int start1, int end1, int start2, int end2) {
        int i = start1, j = start2, index = start1;
        while (i <= end1 || j <= end2) {
            if (i > end1) {// 第一個數組已經全部合併到新的數組,則將第二個數組剩下的元素按順序直接複製到新的數組
                newData[index++] = data[j++];
                continue;
            }
            if (j > end2) {// 第二個數組已經全部合併到新的數組,則將第一個數組剩下的元素按順序直接複製到新的數組
                newData[index++] = data[i++];
                continue;
            }
            // 將兩個數組從第一位開始比較,取出較小的複製到新的數組,同時繼續將較大的元素與較小的元素所在的數組後續的元素進行對比
            if (data[i].compareTo(data[j]) <= 0) {
                // 此處i<=j的時候也選擇i的原因是爲了保證穩定性,因爲i在原數組中是在j前面的,
                // 所以即使i==j我們也選擇i保證排序後i依舊在j前面
                newData[index++] = data[i++];
            } else {
                newData[index++] = data[j++];
            }
        }
        // 將排好序的元素複製回原數組
        for (i = start1; i <= end2; i++) {
            data[i] = (T) newData[i];
        }
    }

    public static void main(String[] args) {
        Random ran = new Random();
        Integer[] data = new Integer[10000];
        Integer[] data2 = new Integer[10000];
        for (int i = 0; i < data.length; i++) {
            data[i] = ran.nextInt(1000000);
            data2[i] = data[i];
        }
        MergeSort.sort(data);
        QuickSort.sort(data2);
        SortUtils.printArray(data);
        SortUtils.printArray(data2);
    }
}

效率分析
(1)時間複雜度
O(nlgn)
對長度爲n的文件,需進行lgn 趟二路歸併,每趟歸併的時間爲O(n),故其時間複雜度無論是在最好情況下還是在最壞情況下均是O(nlgn)。
(2)空間複雜度
O(n)
合併的時候需要O(n)長度的輔助數組進行合併,所以歸併排序的空間複雜度是O(n)。
(3)穩定性
穩定
排序中有可能發生兩個相同元素交換的地方就是在合併的時候,所以我們可以在合併的時候認爲的控制其穩定性,具體方式在上面代碼的mergeArray()中有註釋。

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