归并排序(Merge Sort)

归并排序是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(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的单元。

归并排序的算法其实要做两件事:分解,合并。
根据分解的方式不同,实现方法可分为两种:递归法,迭代法。前者比较常用。

(1)用递归实现:
也叫二路归并,先把待排序区间[s,t]以中点二分,接着把左边子区间排序,再把右边子区间排序,最后把左区间和右区间用一次归并操作合并成有序的区间[s,t]。
归并排序(Merge Sort)_第1张图片

(2)用迭代实现:
1 个元素的表总是有序的。所以将有n个元素的数组分成 n 个拥有 1 个元素的有序子序列。对子序列两两合并可得 n/2 个子序列,所得子序列除最后一个子序列长度可能为1 外,其余子表长度均为2。再进行两两合并,直到最后得到一个拥有n个元素的有序序列。
归并排序(Merge Sort)_第2张图片

时间复杂度
归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(n*log2n)。

C语言版完整代码如下:

/** * 合并相邻两个序列 * *  @param sourceArray 包含要合并的两个序列的数组 *  @param tempArray 临时数组,保存合并的序列 *  @param startIndex 要合并的第一个序列的第一个元素的下标 *  @param midIndex 要合并的第一个序列的最后一个元素的下标,而 midIndex+1 是第二个序列的第一个元素的下标 *  @param endIndex 第二个序列的最后一个元素的下标 */
void Merge(int sourceArray[], int tempArray[], int startIndex, int midIndex, int endIndex){
    // idx1,idx2 分别为要合并的第一、二个序列的下标,tmpIdx 为保存合并序列的临时数组的下标
    int idx1 = startIndex, idx2 = midIndex + 1, tmpIdx = startIndex;

    // 只要两个序列都还有元素,则取各自最小的元素出来比较,把较小的存入临时序列
    while (idx1 != midIndex + 1 && idx2 != endIndex + 1) {
        tempArray[tmpIdx++] = (sourceArray[idx1] < sourceArray[idx2]) ? sourceArray[idx1++] : sourceArray[idx2++];
    }

    // 如果第一个序列还有元素,则一一放进临时序列
    while (idx1 != midIndex + 1) {
        tempArray[tmpIdx++] = sourceArray[idx1++];
    }

    // 如果第二个序列还有元素,则一一放进临时序列
    while (idx2 != endIndex + 1) {
        tempArray[tmpIdx++] = sourceArray[idx2++];
    }

    // 将临时序列的元素复制到原数组中
    for (int i = startIndex; i <= endIndex; i++) {
        sourceArray[i] = tempArray[i];
    }
}
/** * 归并排序的递归实现 * *  @param sourceArray 要排序的整个数组 *  @param tempArray 临时数组,保存合并的序列 *  @param startIndex 要排序的序列的第一个元素的下标 *  @param endIndex 要排序的序列的最后一个元素的下标 */
void MergeSort_Recursion(int sourceArray[], int tempArray[], int startIndex, int endIndex){
    int minIndex;
    if (startIndex < endIndex) {
        // 取中间下标
        minIndex = (startIndex + endIndex) / 2;

        // 对左边数组进行归并排序
        MergeSort_Recursion(sourceArray, tempArray, startIndex, minIndex);

        // 对右边数组进行归并排序
        MergeSort_Recursion(sourceArray, tempArray, minIndex + 1, endIndex);

        // 把两个有序的序列,合并成一个有序的序列
        Merge(sourceArray, tempArray, startIndex, minIndex, endIndex);
    }
}
/** * 归并排序的迭代实现 * *  @param sourceArray 要排序的整个数组 *  @param tempArray 临时数组 *  @param length 数组总长度 */
void MergeSort_Iteration(int sourceArray[], int tempArray[], int length){
    // 要合并的两序列的总长度,初始化为1
    int len = 1;
    while (len < length) {
        // 第一个序列的长度
        int subLen = len;
        len = subLen * 2;
        int startIndex = 0;

        // 将数组中所有长为 len 的两个相邻子序列合并一次
        while (startIndex + len < length) {
            Merge(sourceArray, tempArray, startIndex, startIndex + subLen - 1, startIndex + len -1);
            startIndex = startIndex + len;
        }

        // 如果剩下的元素比一个子序列长,但比两个序列短,则当做两个不等长的子序列来合并
        if (startIndex + subLen < length) {
            Merge(sourceArray, tempArray, startIndex, startIndex + subLen - 1, length - 1);
        }

        // 把合并过一趟的序列复制到原序列
        for (int i = 0; i < length; i++) {
            sourceArray[i] = tempArray[i];
        }        
    }
}

调用:

int main(int argc, const char * argv[]) {

    int arr[8] = {30, 10, 40, 80, 70, 20, 50, 60};
    int tmpArr[8];

    //MergeSort_Recursion(arr, tmpArr, 0, 7);
    MergeSort_Iteration(arr, tmpArr, 8);

    return 0;
}

//迭代实现的打印结果:
//Before sorting:
//30 10 40 80 70 20 50 60
//MergeSort_Iteration:
//sublen = 1: 10 30 40 80 20 70 50 60
//sublen = 2: 10 30 40 80 20 50 60 70
//sublen = 4: 10 20 30 40 50 60 70 80

你可能感兴趣的:(算法,归并排序)