Fork me on GitHub

数据结构——归并排序

系列文章:数据结构与算法系列——从菜鸟到入门 

简述

归并排序是将两个或两个以上的有序表组合成一个新的有序表。

其基本思想是:先将 N 个数据看成 N 个长度为 1 的表,将相邻的表成对合并,得到长度为 2 的 N/2 个有序表,进一步将相邻的合并,得到长度为 4 的 N/4 个有序表,以此类推,直到所有数据均合并成一个长度为 N 的有序表为止。每一次归并过程称做一趟。

那么归并排序就可以分解为,分解归并两个子问题。

分解

分解方式分为“从上往下”“从下往上”两种方式。如下图所示:

1.“从上往下”的归并排序:

分解:将当前空间一分为二,即求分裂点 mid=(low+high)/2;
求解:递归地对两个子区间 a[low...mid]和 a[mid+1...high]进行归并排序。递归的结束条件为子区间长度为 1(或low>=high)。

/**
 * 分解
 * 归并排序(从上至下)
 */
private static void mSort(int[] arr,int low,int high){
    if (low >= high) {
        return;
    }
    int mid = (high+low)/2;
    mSort(arr, low, mid); // 继续分解左子区间
    mSort(arr, mid+1, high); // 继续分解右子区间
    merge(arr, low, mid, high); // 合并两个子区间
}

2.“从下往上”的归并排序:

将待排序的数列分成若干个长度为 1 的子数列,然后将这些数列两两合并;得到若干个长度为 2 的有序数列,再将这些数列两两合并,得到长度为 4 的有序数列,直到合并成一个完整数列为止。这就得到了我们想要的排序结果。

/**
 * 分解
 * 归并排序(从下往上)
 */
private static void mSort2(int[] A, int len) {
    for (int i = 1;i < len;i *= 2) { // i 为每次合并子数组的长度;每次为上一次的2倍
        int step = i; // 要合并的子数组的长度
        int low; // 当前左区间的起始索引
        int group = step*2;// 两个子数组合并后的长度
        for (low = 0;low+group-1 < len;low+=group) {
            merge(A, low, low+step-1, low+group-1);
        }
        if (low+step-1 < len-1) { // 子数组量为奇数个,剩余一个子数组没有配对
            merge(A, low, i+step-1, len-1);
        }
    }
}

归并

将两个子区间 arr[low...mid]和 arr[mid+1...high]归并为一个有序的区间 arr[low...high]。

在归并的过程中需要申请一个临时数组空间,将待排序的两数组顺序的保存在该临时空间中。最后将有序的临时空间覆盖到原数组中。此时数组就变为局部有序了。

/**
 * 归并
 */
private static void merge(int[] arr, int low, int mid, int high) {
    int[] tempArr = new int[high - low + 1]; // 临时空间
    int tempIndex = 0;  // 临时空间的下标
    int left = low;     // 左区间起始索引
    int right = mid+1;  // 右区间起始索引
    while (left<=mid && right<=high) {
        if (arr[left] < arr[right]) {
            tempArr[tempIndex++] = arr[left++];
        }else {
            tempArr[tempIndex++] = arr[right++];
        }
    }
    // 将左区间中剩余元素添加至临时空间
    while (left <= mid) {
        tempArr[tempIndex++] = arr[left++];
    }
    // 将右区间中剩余元素添加至临时空间
    while (right <= high) {
        tempArr[tempIndex++] = arr[right++];
    }
    // 此时的临时空间为有序序列
    // 将临时空间覆盖至原数组相应的位置
    tempIndex-=1;
    while (tempIndex>=0) {
        arr[low+tempIndex] = tempArr[tempIndex];
        tempIndex--;
    }
}

性能分析

时间复杂度:

归并排序的时间复杂度是O(nlgn)。

假设被排序的数组的长度为n,遍历一趟的时间复杂度为O(n),那么排序过程需要遍历多少次?

归并排序的形式就是二叉树,需要遍历的次数就是二叉树的深度,根据完全二叉树的性质,其深度为lgn,则得出时间复杂度为O(n*lgn)。

稳定性:

归并排序是稳定的算法,它满足稳定算法的定义。

算法稳定性——假设在数列中存在 a[i]=a[j],若在排序之前,a[i]在 a[j]前面;并且排序之后,a[i]仍然在 a[j]前面。则这个排序算法是稳定的。

实现源码

    /**
     * 归并排序
     * @param A
     * @param n
     * @return
     */
    public int[] mergeSort(int[] A, int n) {
        int low = 0;
        int high = n-1;
        mSort(A, low, high);
        return A;
    }

    /**
     * 分解
     * 归并排序(从上至下)
     */
    private static void mSort(int[] arr,int low,int high){
        if (low >= high) {
            return;
        }
        int mid = (high+low)/2;
        mSort(arr, low, mid); // 继续分解左子区间
        mSort(arr, mid+1, high); // 继续分解右子区间
        merge(arr, low, mid, high); // 合并两个子区间
    }

    /**
     * 分解
     * 归并排序(从下往上)
     */
    private static void mSort2(int[] A, int len) {
        for (int i = 1;i < len;i *= 2) { // i 为每次合并子数组的长度;每次为上一次的2倍
            int step = i; // 要合并的子数组的长度
            int low; // 当前左区间的起始索引
            int group = step*2;// 两个子数组合并后的长度
            for (low = 0;low+group-1 < len;low+=group) {
                merge(A, low, low+step-1, low+group-1);
            }
            if (low+step-1 < len-1) { // 子数组量为奇数个,剩余一个子数组没有配对
                merge(A, low, i+step-1, len-1);
            }
        }
    }

    /**
     * 合并
     * 将两个子区间arr[low...mid]和 arr[mid+1...high]归并为一个有序的区间a[low...high]。
     */
    private static void merge(int[] arr, int low, int mid, int high) {
        int[] tempArr = new int[high - low + 1]; // 临时空间
        int tempIndex = 0;  // 临时空间的下标
        int left = low;     // 左区间起始索引
        int right = mid+1;  // 右区间起始索引
        while (left<=mid && right<=high) {
            if (arr[left] < arr[right]) {
                tempArr[tempIndex++] = arr[left++];
            }else {
                tempArr[tempIndex++] = arr[right++];
            }
        }
        // 将左区间中剩余元素添加至临时空间
        while (left <= mid) {
            tempArr[tempIndex++] = arr[left++];
        }
        // 将右区间中剩余元素添加至临时空间
        while (right <= high) {
            tempArr[tempIndex++] = arr[right++];
        }
        // 此时的临时空间为有序序列
        // 将临时空间覆盖至原数组相应的位置
        tempIndex-=1;
        while (tempIndex>=0) {
            arr[low+tempIndex] = tempArr[tempIndex];
            tempIndex--;
        }
    }
View Code

参考资料

[1] 归并排序

[2] 数据结构与算法分析——Java语言描述, 7.5 - 归并排序

posted @ 2017-03-16 12:34  郑斌blog  阅读(1274)  评论(0编辑  收藏  举报